--[[
  Copyright (c) 2021-2023 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.
--]]--
local Module = {}
local U = require('common.utils')

local static = {}

function Module.getBlock(globals, cpu)

  local Cmpss = require('common.block').getBlock(globals, cpu)
  if not static[cpu] then
    static[cpu] = {
      numInstances = 0,
      instances = {},
      finalized = false,
    }
  end
  Cmpss["instance"] = static[cpu].numInstances
  static[cpu].numInstances = static[cpu].numInstances + 1

  function Cmpss:createImplicit(comp, params, req, opt_label)
    assert(U.isNonNegativeIntScalar(comp))
    assert(U.isTable(req))
    assert(opt_label == nil or U.isString(opt_label))
    U.enforceParamContract(
      params,
      {
        analogPin = U.isString, -- for example "A14"
        tripSignal = U.isNonNegativeIntScalar,
        opt_compHigh = {
          opt_pinMux = U.isNonNegativeIntScalar,
          threshold = U.isReal,
          invertCompare = U.isBoolean,
          opt_ramp = {
            peripheralSyncEpwmUnit = U.isNonNegativeIntScalar,
            rampDecrement = U.isReal, -- TODO: Be more specific about  these datatypes?
            desiredRamp = U.isReal,
            actualRamp = U.isReal,
            b_rampIsIncrementing = U.isBoolean,
          },
        },
        opt_compLow = {
          opt_pinMux = U.isNonNegativeIntScalar,
          threshold = U.isReal,
          invertCompare = U.isBoolean,
        },
      }
    )
    req:add("CMPSS", comp, opt_label)
    self:logLine('CMPSS%i implicitly created.' % {comp})
    table.insert(static[self.cpu].instances, self.bid)

    self.comp = comp
    self.analogPin = params.analogPin
    self.tripSignal = params.tripSignal
    self.compHigh = params.opt_compHigh
    self.compLow = params.opt_compLow

    -- configure CMPSS and EPWWM_XBAR multiplexer settings
    do
      --[[
      The following is based on this assumption of register ordering
      XBAR_EPWM_MUX00_CMPSS1_CTRIPH          = 0x0000,
      XBAR_EPWM_MUX00_CMPSS1_CTRIPH_OR_L     = 0x0001,
      [...]
      XBAR_EPWM_MUX01_CMPSS1_CTRIPL          = 0x0200,
      ]]--

      local xbarEpwmMuxBase
      if (self.comp < 9) then 
        xbarEpwmMuxBase = (self.comp - 1) * 2
      -- CMPSS 9 and greater use different muxes
      else
        xbarEpwmMuxBase = ((self.comp - 9) * 2) + 58
      end

      local xbarEpwmMuxConfigs = {}
      local xbarEpwmMuxUnits = {}
      local hpmux, lpmux
      if self.compHigh and self.compLow then
        if self:targetMatches({'28P55x', '28P65x'}) then
          table.insert(xbarEpwmMuxConfigs, 'XBAR_EPWM_MUX%02i_CMPSS%i_CTRIPH' % {xbarEpwmMuxBase, self.comp})
          table.insert(xbarEpwmMuxUnits, xbarEpwmMuxBase)
          table.insert(xbarEpwmMuxConfigs, 'XBAR_EPWM_MUX%02i_CMPSS%i_CTRIPL' % {xbarEpwmMuxBase + 1, self.comp})
          table.insert(xbarEpwmMuxUnits, xbarEpwmMuxBase+1)
        elseif self:targetMatches({'29H85x'}) then
          table.insert(xbarEpwmMuxConfigs, 'XBAR_EPWM_CMPSS%i_CTRIPH' % {self.comp})
          table.insert(xbarEpwmMuxUnits, xbarEpwmMuxBase)
          table.insert(xbarEpwmMuxConfigs, 'XBAR_EPWM_CMPSS%i_CTRIPL' % {self.comp})
          table.insert(xbarEpwmMuxUnits, xbarEpwmMuxBase+1)
        elseif self:targetMatches({'2837x', '2838x', '28003x', '28004x', '280013x'}) then
          table.insert(xbarEpwmMuxConfigs, 'XBAR_EPWM_MUX%02i_CMPSS%i_CTRIPH_OR_L' % {xbarEpwmMuxBase, self.comp})
          table.insert(xbarEpwmMuxUnits, xbarEpwmMuxBase)
        else
          U.throwUnhandledTargetError()
        end
        hpmux = self.compHigh.opt_pinMux
        lpmux = self.compLow.opt_pinMux
      elseif self.compHigh then
        if self:targetMatches({'29H85x'}) then
          table.insert(xbarEpwmMuxConfigs, 'XBAR_EPWM_CMPSS%i_CTRIPH' % {self.comp})
        elseif self:targetMatches({'2837x', '2838x', '28003x', '28004x', '280013x', '28P55x', '28P65x'}) then
          table.insert(xbarEpwmMuxConfigs, 'XBAR_EPWM_MUX%02i_CMPSS%i_CTRIPH' % {xbarEpwmMuxBase, self.comp})
        else
          U.throwUnhandledTargetError()
        end
        table.insert(xbarEpwmMuxUnits, xbarEpwmMuxBase)
        hpmux = self.compHigh.opt_pinMux 
      elseif self.compLow then
        if self:targetMatches({'29H85x'}) then
          table.insert(xbarEpwmMuxConfigs, 'XBAR_EPWM_CMPSS%i_CTRIPL' % {self.comp})
        elseif self:targetMatches({'2837x', '2838x', '28003x', '28004x', '280013x', '28P55x', '28P65x'}) then
          table.insert(xbarEpwmMuxConfigs, 'XBAR_EPWM_MUX%02i_CMPSS%i_CTRIPL' % {xbarEpwmMuxBase + 1, self.comp})
        else
          U.throwUnhandledTargetError()
        end
        table.insert(xbarEpwmMuxUnits, xbarEpwmMuxBase+1)
        lpmux = self.compLow.opt_pinMux
      end

      globals.syscfg:addPeripheralEntry('cmpss', {
        unit = self.comp,
        opt_hpmux = hpmux,
        opt_lpmux = lpmux,
        core = globals.target.getTiCpuNumber(self.cpu)
      })
  
      globals.syscfg:addPeripheralEntry('epwm_xbar', {
        trip = self.tripSignal,
        mux = xbarEpwmMuxUnits,
        muxconf = xbarEpwmMuxConfigs,
        core = globals.target.getTiCpuNumber(self.cpu)
      })
    end

    -- set pin to analog mode if required
    do
      local analog_map = globals.target.getTargetParameters().adcs.analog_mapping
      if analog_map and analog_map[params.analogPin] then
        globals.syscfg:addPeripheralEntry('analog_port', {
          pins = {
            analog_map[params.analogPin],
          }
        })
      end
    end
  end

  function Cmpss:checkMaskParameters()
    U.error("Explicit use of comparator via target block not supported.")
  end

  function Cmpss:p_getDirectFeedthroughCode()
    U.error("Explicit use of comparator via target block not supported.")
  end

  function Cmpss:finalizeThis(c)
    local highConfigCode = '// high comparator not used'
    local lowConfigCode = '// low comparator not used'
    do
      --[[
        Per TRM, we need to ensure that the trip pulse width is at least 3xTBCLK.
        This can be achieved by using the CMPSS FILTER path, but will delay the trip detection.
        Alternatively, we could use the LATCH path, but then the latch would to be cleared by PWMSYNC
        a few TBCLK (3?) before the end of the switching period (using CMPC or CMPD). This in turn,
        requires a delay of the ramp start. LATCH clearing is also affected by an advisory. Too convoluted.
      --]]
      local glitchLatencyInSysTick = math.ceil(3 * globals.target.getCleanSysClkHz() / globals.target.getPwmClock())
      local filterPrescale = 1
      -- filter latency = 1 + prescale(1+threshold)
      local filterThreshold = math.ceil((glitchLatencyInSysTick - 1)/filterPrescale) - 1
      local filterWindow = filterThreshold

      if self.compHigh then
        highConfigCode = globals.target.getCmpssHighComparatorEpwmTripSetupCode(
          self.comp,
          {
            filterPrescale = filterPrescale,
            filterThreshold = filterThreshold,
            filterWindow = filterWindow,
            threshold = self.compHigh.threshold,
            invertCompare = self.compHigh.invertCompare,
            opt_ramp = self.compHigh.opt_ramp,
          })
      end
      if self.compLow then
        lowConfigCode = globals.target.getCmpssLowComparatorEpwmTripSetupCode(
          self.comp, {
            filterPrescale = filterPrescale,
            filterThreshold = filterThreshold,
            filterWindow = filterWindow,
            threshold = self.compLow.threshold,
            invertCompare = self.compLow.invertCompare,
          })
      end
    end

    c.PreInitCode:append([[
    {
      SysCtl_enablePeripheral(SYSCTL_PERIPH_CLK_CMPSS%(unit)i);
      CMPSS_enableModule(CMPSS%(unit)i_BASE);
      %(highConfigCode)s
      %(lowConfigCode)s
    }
    ]] % {
      unit = self.comp,
      highConfigCode = highConfigCode,
      lowConfigCode = lowConfigCode,
    })
  end

  function Cmpss:finalize(c)

    if static[self.cpu].finalized then
      return
    end

    c.Include:append('cmpss.h')
    c.Include:append('sysctl.h')

    for _, bid in ipairs(static[self.cpu].instances) do
      local comp = globals.instances[bid]
      if comp:getCpu() == self.cpu then
        comp:finalizeThis(c)
      end
    end

    static[self.cpu].finalized = true
  end

  return Cmpss
end

return Module
