--[[
  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,
          rampStepVal = U.isNonNegativeIntScalar,   
          desiredRamp = U.isNonNegativeScalar,
          actualRamp = U.isNonNegativeScalar,
          b_rampIsIncrementing = U.isBoolean,
          rampClkDivider = U.isPositiveIntScalar,
        },
      }
    )

    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_DIR_arg = ' CMPSS_RAMP_DIR_DOWN'  -- default setting for ramp direction
      local ramp = params.opt_ramp
      if ramp.rampClkDivider > 1 and not T.getTargetParameters().comps.b_supportsRampClkDivider then
        error('Ramp clock divider not supported for the %(target)s target.'
          % {target = T.getFamilyPrefix()})
      end
      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  
        if params.opt_ramp.b_rampIsIncrementing then
          RAMP_DIR_arg = 'CMPSS_RAMP_DIR_UP'
        end
        code = code..[[
          // configuring ramp to %(actualRamp)f A/s (desired: %(desiredRamp)f A/s)
          CMPSS_configRampLow(CMPSS%(unit)i_BASE, %(RAMP_DIR_arg)s, 0, %(rampStepVal)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,
          rampStepVal = ramp.rampStepVal,
          peripheralSyncEpwmUnit = ramp.peripheralSyncEpwmUnit,
          RAMP_DIR_arg = RAMP_DIR_arg,
        }
        
        if ramp.rampClkDivider > 1 then
          code = code..[[
            // configuring COMPL ramp clock divider to SYSCLK/%(rampClkDivider)d
            CMPSS_setRampClockDividerLow(CMPSS%(unit)i_BASE, CMPSS_RAMP_CLOCK_DIV%(rampClkDivider)d);
            ]] % {
            unit = unit,
            rampClkDivider = ramp.rampClkDivider,
          }
        end
      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,
          rampStepVal = U.isNonNegativeIntScalar,  
          desiredRamp = U.isNonNegativeScalar,
          actualRamp = U.isNonNegativeScalar,
          b_rampIsIncrementing = U.isBoolean,
          rampClkDivider = U.isPositiveIntScalar,
        },
      }
    )

    local opt_DACREF_VDDA = ''
    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_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
      if ramp.rampClkDivider > 1 and not T.getTargetParameters().comps.b_supportsRampClkDivider then
        error('Ramp clock divider not supported for the %(target)s target.'
          % {target = T.getFamilyPrefix()})
      end
      if ramp.b_rampIsIncrementing and not T.getTargetParameters().comps.b_supportsRampDirection then
        error('Incrementing ramp on high comparator not supported for the %(target)s target.'
          % {target = T.getFamilyPrefix()})
      end
      if T.getTargetParameters().comps.b_supportsRampDirection then
        local RAMP_DIR_arg = 'CMPSS_RAMP_DIR_DOWN'  -- default setting for ramp direction
        if ramp.b_rampIsIncrementing then
          RAMP_DIR_arg = 'CMPSS_RAMP_DIR_UP'
        end
        code = code..[[
          // configuring ramp to %(actualRamp)f A/s (desired: %(desiredRamp)f A/s)
          CMPSS_configRampHigh(CMPSS%(unit)i_BASE, %(RAMP_DIR_arg)s, 0, %(rampStepVal)i, 0, %(peripheralSyncEpwmUnit)i, true);

          CMPSS_configDACHigh(CMPSS%(unit)i_BASE, %(opt_DACREF_VDDA)s CMPSS_DACVAL_SYSCLK |
                          CMPSS_DACSRC_RAMP);
          ]] % {
          unit = unit,
          desiredRamp = ramp.desiredRamp,
          actualRamp = ramp.actualRamp,
          rampStepVal = ramp.rampStepVal,
          peripheralSyncEpwmUnit = ramp.peripheralSyncEpwmUnit,
          opt_DACREF_VDDA = opt_DACREF_VDDA,
          RAMP_DIR_arg = RAMP_DIR_arg,
        }
        if ramp.rampClkDivider > 1 then
          code = code..[[
            // configuring COMPH ramp clock divider to SYSCLK/%(rampClkDivider)d
            CMPSS_setRampClockDividerHigh(CMPSS%(unit)i_BASE, CMPSS_RAMP_CLOCK_DIV%(rampClkDivider)d);
            ]] % {
            unit = unit,
            rampClkDivider = ramp.rampClkDivider,
          }
        end
      else
        -- These targets only support down ramp; CMPSS_configRamp takes no direction argument
        code = code..[[
          // configuring ramp to %(actualRamp)f A/s (desired: %(desiredRamp)f A/s)
          CMPSS_configRamp(CMPSS%(unit)i_BASE, 0, %(rampStepVal)i, 0, %(peripheralSyncEpwmUnit)i, true);

          CMPSS_configDAC(CMPSS%(unit)i_BASE, %(opt_DACREF_VDDA)s CMPSS_DACVAL_SYSCLK |
                          CMPSS_DACSRC_RAMP);
          ]] % {
          unit = unit,
          desiredRamp = ramp.desiredRamp,
          actualRamp = ramp.actualRamp,
          rampStepVal = ramp.rampStepVal,
          peripheralSyncEpwmUnit = ramp.peripheralSyncEpwmUnit,
          opt_DACREF_VDDA = opt_DACREF_VDDA,
        }
      end

    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
