--[[
  Copyright (c) 2024 by Plexim GmbH
  All rights reserved.

  A free license is granted to anyone to use this software for any legal
  non safety-critical purpose, including commercial applications, provided
  that:
  1) IT IS NOT USED TO DIRECTLY OR INDIRECTLY COMPETE WITH PLEXIM, and
  2) THIS COPYRIGHT NOTICE IS PRESERVED in its entirety.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  SOFTWARE.
--]]

U = require('common.utils')

local TS = {}

-- wrapper function that takes the target
-- to provide target specific variations
function TS.getFunctions(T)
  local FUNCS = {}

  function FUNCS.ts_getEpwmSetupCode(unit, params, T)
    local code = ''

    if params.soca_sel ~= nil then
      local socCode = [[
        EPwm%(unit)iRegs.ETSEL.bit.SOCASEL = %(SOCASEL)i;
        EPwm%(unit)iRegs.ETPS.bit.SOCPSSEL = 1;
        EPwm%(unit)iRegs.ETSOCPS.bit.SOCAPRD2 = %(SOCAPRD)i;
        EPwm%(unit)iRegs.ETSEL.bit.SOCAEN = 1;
        ]] % {
        unit = unit,
        SOCASEL = params.soca_sel,
        SOCAPRD = params.soca_prd or 1,
      }
      
      if params.soca_sel >= 4 then
        -- SOCA event is triggered by CTRU = CMPC or CTRD = CMPC
        socCode = socCode..[[
          EPwm%(unit)iRegs.ETSEL.bit.SOCASELCMP = 1;
          EPwm%(unit)iRegs.CMPC = 1; // to ensure first SOCA event
          ]] % {
          unit = unit,
        }
      end
      code = code..socCode
    end

    if params.int_sel ~= nil then
      local intConfigCode = [[
        EPwm%(unit)iRegs.ETSEL.bit.INTSEL = %(INTSEL)i;
        EPwm%(unit)iRegs.ETPS.bit.INTPSSEL = 1;
        EPwm%(unit)iRegs.ETINTPS.bit.INTPRD2 = %(INTPRD)i;
        EPwm%(unit)iRegs.ETSEL.bit.INTEN = 1;  // enable INT
        ]] % {
        unit = unit,
        INTSEL = params.int_sel,
        INTPRD = params.int_prd or 1,
      }
      code = code..intConfigCode
    end

    if params.sync ~= nil then
      code = code..[[
        EPwm%(unit)iRegs.TBCTL.bit.PHSEN = %(phsen)i;
        ]] % {
        unit = unit, 
        phsen = params.sync.phsen,
      }
      if T.targetMatches({'2837x', '28004x'}) then
        code = code..[[
          EPwm%(unit)iRegs.TBCTL.bit.SYNCOSEL = %(synco_sel)i;
          ]] % {
          unit = unit, 
          synco_sel = params.sync.synco_sel,
        }
      elseif T.targetMatches({'2838x', '28003x', '280013x', '28P55x', '28P65x', '29H85x'}) then
        code = code..[[
          EPwm%(unit)iRegs.EPWMSYNCOUTEN.all = %(synco_sel)i;
          ]] % {
          unit = unit, 
          synco_sel = params.sync.synco_sel,
        }
      else
        U.throwUnhandledTargetError()
      end

      if params.sync.phsen ~= 0 then
        code = code..[[
          EPwm%(unit)iRegs.TBCTL2.bit.PRDLDSYNC = 1; // load TBPRD at zero and SYNC
          ]] % {unit = unit}
      end

      if params.sync.synci_sel ~= nil then
        local syncSelCode = ''
        if T.targetMatches('28004x') then
          syncSelCode = [[
            EALLOW;
            SyncSocRegs.SYNCSELECT.bit.EPWM%(unit)iSYNCIN = %(synci_sel)i;
            EDIS;
          ]]
        elseif T.targetMatches({'2837x'}) then
          -- Do nothing for this target
        elseif T.targetMatches({'2838x', '28003x', '280013x', '28P55x', '28P65x', '29H85x'}) then
          syncSelCode = [[
            EPwm%(unit)iRegs.EPWMSYNCINSEL.bit.SEL = %(synci_sel)i;
            ]]
        else
          U.throwUnhandledTargetError()
        end

        code = code..syncSelCode % {unit = unit, synci_sel = params.sync.synci_sel}
      end
    end

    return code
  end

  function FUNCS.ts_getCmpssLowComparatorEpwmTripSetupCode(unit, params)
    U.enforceParamContract(
      params,
      {
        filterPrescale = U.isReal, -- TODO: Be more specific about these datatypes?
        filterThreshold = U.isReal,
        filterWindow = U.isReal,
        threshold = U.isReal,
        invertCompare = U.isBoolean,
        opt_ramp = {                 -- FIX ME: Define a contract for a ramp in a common location?
          peripheralSyncEpwmUnit = U.isNonNegativeIntScalar,
          rampDecrement = U.isReal,  -- TODO: Be more specific about these datatypes?
          desiredRamp = U.isReal,
          actualRamp = U.isReal,
        },
      }
    )
    
    local code = [[
      CMPSS_configLowComparator(CMPSS%(unit)i_BASE, CMPSS_INSRC_DAC%(opt_invertFlag)s);
      ]] % {
      unit = unit,
      opt_invertFlag = (params.invertCompare ~= nil) and (params.invertCompare) and ' | CMPSS_INV_INVERTED' or ''
    }
    if params.opt_ramp then
      local ramp = params.opt_ramp
      if T.targetMatches({'2838x', '28003x', '28004x', '2837x', '280013x'}) then
        error('Ramp on low comparator not supported for this target.')
      elseif T.targetMatches({'29H85x', '28P55x', '28P65x'}) then
        code = code..[[
          // configuring ramp to %(actualRamp)f A/s (desired: %(desiredRamp)f A/s)
          CMPSS_configRampLow(CMPSS%(unit)i_BASE, CMPSS_RAMP_DIR_DOWN, 0, %(rampDecrement)i, 0, %(peripheralSyncEpwmUnit)i, true);

          CMPSS_configDACLow(CMPSS%(unit)i_BASE, CMPSS_DACVAL_SYSCLK |
                          CMPSS_DACSRC_RAMP);
          ]] % {
          unit = unit,
          desiredRamp = ramp.desiredRamp,
          actualRamp = ramp.actualRamp,
          rampDecrement = ramp.rampDecrement,
          peripheralSyncEpwmUnit = ramp.peripheralSyncEpwmUnit,
        }
      else
        U.throwUnhandledTargetError()
      end
    else
      if T.targetMatches({'29H85x', '28P55x', '28P65x'}) then
        code = code..[[
          CMPSS_configDACLow(CMPSS%(unit)i_BASE, CMPSS_DACVAL_SYSCLK |
                          CMPSS_DACSRC_SHDW);
          ]] % {
          unit = unit,
        }
      elseif T.targetMatches('280013x') then
        code = code..[[
            CMPSS_configDAC(CMPSS%(unit)i_BASE, CMPSS_DACVAL_SYSCLK |
                            CMPSS_DACSRC_SHDW);
            ]] % {
          unit = unit,
        }
      elseif T.targetMatches({'2838x', '28003x', '28004x', '2837x'}) then
        code = code..[[
        CMPSS_configDAC(CMPSS%(unit)i_BASE, CMPSS_DACREF_VDDA | CMPSS_DACVAL_SYSCLK |
                        CMPSS_DACSRC_SHDW);
        ]] % {
          unit = unit,
        }
      else
        U.throwUnhandledTargetError()
      end

      if params.threshold ~= nil then
        local threshold_dac = math.floor(4096 / 3.3 * params.threshold + 0.5)
        if threshold_dac < 0 then
          threshold_dac = 0
        elseif threshold_dac > 4095 then
          threshold_dac = 4095
        end
        code = code..[[
            CMPSS_setDACValueLow(CMPSS%(unit)i_BASE, %(threshold_dac)i); // %(threshold)f V
          ]] % {
          unit = unit,
          threshold_dac = threshold_dac,
          threshold = params.threshold,
        }
      end
    end
    if (params.filterPrescale ~= nil) and (params.filterWindow ~= nil) and (params.filterThreshold ~= nil) then
      code = code..[[
      CMPSS_configFilterLow(CMPSS%(unit)i_BASE, %(filterPrescale)i, %(filterWindow)i, %(filterThreshold)i);
      CMPSS_initFilterLow(CMPSS%(unit)i_BASE);

      CMPSS_configOutputsLow(CMPSS%(unit)i_BASE, CMPSS_TRIP_FILTER);
      ]] % {
        unit = unit,
        filterPrescale = params.filterPrescale - 1,
        filterWindow = params.filterWindow,
        filterThreshold = params.filterThreshold,
      }
    else
      code = code..[[
        CMPSS_configOutputsLow(CMPSS%(unit)i_BASE, CMPSS_TRIP_ASYNC_COMP);
        ]] % {
        unit = unit,
      }
    end
    return code
  end

  function FUNCS.ts_getCmpssHighComparatorEpwmTripSetupCode(unit, params, T)
    U.enforceParamContract(
      params,
      {
        filterPrescale = U.isReal, -- TODO: Be more specific about these datatypes?
        filterThreshold = U.isReal,
        filterWindow = U.isReal,
        threshold = U.isReal,
        invertCompare = U.isBoolean,
        opt_ramp = { -- FIX ME: Define a contract for a ramp in a common location?
          peripheralSyncEpwmUnit = U.isNonNegativeIntScalar,
          rampDecrement = U.isReal, -- TODO: Be more specific about these datatypes?
          desiredRamp = U.isReal,
          actualRamp = U.isReal,
        },
      }
    )

    local opt_DACREF_VDDA = ''
    local opt_RAMP_DIR_DOWN_arg = ''
    local opt_High = ''

    if T.targetMatches({'2837x', '2838x', '28003x', '28004x', '29H85x'}) then
      opt_DACREF_VDDA = 'CMPSS_DACREF_VDDA |'
    elseif not T.targetMatches({'280013x', '28P55x', '28P65x'}) then
      U.throwUnhandledTargetError()
    end
    if T.targetMatches({'28P55x', '28P65x', '29H85x'}) then
      opt_RAMP_DIR_DOWN_arg = ' CMPSS_RAMP_DIR_DOWN,'  -- additional arg in one function
      opt_High = 'High'                                -- several function names include 'High' for this target
    elseif not T.targetMatches({'2837x', '2838x', '28003x', '28004x', '280013x'}) then
      U.throwUnhandledTargetError()
    end

    local code = [[
      CMPSS_configHighComparator(CMPSS%(unit)i_BASE, CMPSS_INSRC_DAC%(opt_invertFlag)s);
      ]] % {
      unit = unit,
      opt_invertFlag = (params.invertCompare ~= nil) and (params.invertCompare) and ' | CMPSS_INV_INVERTED' or ''
    }
    if params.opt_ramp then
      local ramp = params.opt_ramp
      code = code..[[
        // configuring ramp to %(actualRamp)f A/s (desired: %(desiredRamp)f A/s)
        CMPSS_configRamp%(opt_High)s(CMPSS%(unit)i_BASE,%(opt_RAMP_DIR_DOWN_arg)s 0, %(rampDecrement)i, 0, %(peripheralSyncEpwmUnit)i, true);

        CMPSS_configDAC%(opt_High)s(CMPSS%(unit)i_BASE, %(opt_DACREF_VDDA)s CMPSS_DACVAL_SYSCLK |
                        CMPSS_DACSRC_RAMP);
        ]] % {
        unit = unit,
        desiredRamp = ramp.desiredRamp,
        actualRamp = ramp.actualRamp,
        rampDecrement = ramp.rampDecrement,
        peripheralSyncEpwmUnit = ramp.peripheralSyncEpwmUnit,
        opt_DACREF_VDDA = opt_DACREF_VDDA,
        opt_High = opt_High,
        opt_RAMP_DIR_DOWN_arg = opt_RAMP_DIR_DOWN_arg,
      }
    else
      code = code..[[
        CMPSS_configDAC%(opt_High)s(CMPSS%(unit)i_BASE, %(opt_DACREF_VDDA)s CMPSS_DACVAL_SYSCLK |
                        CMPSS_DACSRC_SHDW);
        ]] % {
        unit = unit,
        opt_DACREF_VDDA = opt_DACREF_VDDA,
        opt_High = opt_High,
      }
      if params.threshold ~= nil then
        local threshold_dac = math.floor(4096 / 3.3 * params.threshold + 0.5)
        if threshold_dac < 0 then
          threshold_dac = 0
        elseif threshold_dac > 4095 then
          threshold_dac = 4095
        end
        code = code..[[
          CMPSS_setDACValueHigh(CMPSS%(unit)i_BASE, %(threshold_dac)i); // %(threshold)f V
          ]] % {
          unit = unit,
          threshold_dac = threshold_dac,
          threshold = params.threshold,
        }
      end
    end
    if (params.filterPrescale ~= nil) and (params.filterWindow ~= nil) and (params.filterThreshold ~= nil) then
      code = code..[[
        CMPSS_configFilterHigh(CMPSS%(unit)i_BASE, %(filterPrescale)i, %(filterWindow)i, %(filterThreshold)i);
        CMPSS_initFilterHigh(CMPSS%(unit)i_BASE);

        CMPSS_configOutputsHigh(CMPSS%(unit)i_BASE, CMPSS_TRIP_FILTER);
        ]] % {
        unit = unit,
        filterPrescale = params.filterPrescale - 1,
        filterWindow = params.filterWindow,
        filterThreshold = params.filterThreshold,
      }
    else
      code = code..[[
        CMPSS_configOutputsHigh(CMPSS%(unit)i_BASE, CMPSS_TRIP_ASYNC_COMP);
        ]] % {
        unit = unit,
      }
    end
    return code
  end

  return FUNCS  -- return the functions
end

return TS
