--[[
  Copyright (c) 2021-2025 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 U = require('common.utils')

local static = {}

local Module = {}

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

  function Powerstage:checkMaskParameters()
    -- check if global tripzone object exists
    self.tripzonesObj = self:getGlobalBlockInstance('tripzones')

    if not self.tripzonesObj then
      error(
      'TSP exception: TZs tripzones object not found, should have been created in Coder.Initialize().')
    end

    -- Main Tab:
    local cbx_enableSignal = U.enforceMask_newComboBox('EnableSignal',
                                                       {'Digital output', 'None'})
    if cbx_enableSignal.equals('Digital output') then
      self.opt_psEnableDO = {
        gpio = U.enforceMask(U.isNonNegativeIntScalar, 'EnableSignalGpio'),
        cbx_polarity = U.enforceMask_newComboBox(
          'EnableSignalPolarity', {'active low', 'active high'}),
      }
    end

    self.forceSafe = U.enforceMask_newComboBox(
      'PwmSafeState',
      {'Forced inactive', 'Floating'}).equals('Forced inactive')

    -- Protections Tab:
    self.cbx_tripZoneModes = {} -- array indexed by tripzone
    self.cbx_groupReactions = {} -- map indexed by groupChar

    for z = 1, 3 do
      local tripZoneModeVarName = 'TripZone%dMode' % {z}
      local cbx_tripZoneMode = U.enforceMask_newComboBox(
        tripZoneModeVarName, {'ignore', 'cbc', 'osht'})
      if not cbx_tripZoneMode.equals('ignore') then
        if not self.tripzonesObj:isTripZoneConfigured(z) then
          U.error([[
              When @param:%s: is enabled, you must also @param:Tz%dEnable:2: under 'Coder Options -> Target -> Protections'.]] %
            {tripZoneModeVarName, z})
        end   
      end
      self.cbx_tripZoneModes[z] = cbx_tripZoneMode
    end

    -- trip signal group activation
    local groups = {'A', 'B', 'C'}
    for _, group in ipairs(groups) do
      local tripSigNModeVarName = 'TripSig%sMode' % {group}
      self.cbx_groupReactions[group] = U.enforceMask_newComboBox(
        tripSigNModeVarName,
        {'ignore', 'osht'})
      if self.cbx_groupReactions[group].equals('osht') then
        if globals.target.getTargetParameters().epwms.type < 4 then
          U.error([[
              Trip signals are not supported by this target (%(target)s). @param:%(varName)s: must be set to 'Ignore'.
            ]] % {
            target = globals.target.getFamilyPrefix(),
            varName = tripSigNModeVarName,
          })
        end
      end
    end

  end

  function Powerstage:getEnableCode()
    if not self.enable_code_generated then
      -- only do this once
      self.enable_code_generated = true
      return 'PLXHAL_PWR_syncdPwmEnable();'
    end
  end

  function Powerstage:p_getDirectFeedthroughCode()
    local Require = ResourceList:new()
    local InitCode = U.CodeLines:new()
    local OutputCode = U.CodeLines:new()

    -- there can only be one powerstage protection block per CPU
    if self.cpu == 0 then
      Require:add('Powerstage Control')
    elseif self.cpu == 1 then
      Require:add('Powerstage Control CPU2')
    end

    table.insert(static[self.cpu].instances, self.bid)

    local ts = Block.Task.SampleTime
    if ts[1] == 0 then
      U.error('Invalid sample time.')
    end
    self.sampleTime = ts[1]
    self.taskName = Block.Task.Name

    if self.opt_psEnableDO then
      local psEnable = self.opt_psEnableDO
      globals.target.allocateGpio(psEnable.gpio, {}, Require)
      globals.syscfg:addPeripheralEntry('gpio', {
        unit = psEnable.gpio,
        cbx_direction = U.newComboBox('OUTPUT',
                                      U.ENUM.TI.DIO_TYPE),
        cbx_outputType = U.newComboBox('PUSH_PULL',
                                       U.ENUM.TI.OUTPUT_TYPES),
        core = globals.target.getTiCpuNumber(self.cpu)
      })
    end

    OutputCode:append([[
      if((%(inputSignal)s) > 0) {
        PLXHAL_PWR_setEnableRequest(true);
      } else {
        PLXHAL_PWR_setEnableRequest(false);
      }
     ]] % {inputSignal = Block.InputSignal[1][1]})

    return {
      InitCode = InitCode,
      OutputCode = OutputCode,
      OutputSignal = {
        [1] = {'PLXHAL_PWR_isEnabled()'},
      },
      Require = Require,
      UserData = {bid = self:getId()}
    }
  end

  -- PUBLIC FUNCTIONS FOR PWM MODULES
  function Powerstage:forceSafeEnabled()
    return self.forceSafe
  end

  function Powerstage:getTripZoneModeCombo(z)
    return self.cbx_tripZoneModes[z]
  end

  function Powerstage:getTripSignalGroupModeComboMap()
    return self.cbx_groupReactions
  end


  function Powerstage:finalizeThis(c)
    for group, cbx_mode in pairs(self.cbx_groupReactions) do
      -- Confirm all Reactions configured in the powerstage are
      -- also configured in Coder Options
      if  not cbx_mode.equals('ignore')
      and not self.tripzonesObj:isTripSignalGroupConfigured(group) then
        local maskVar = 'TripSig%sMode' % {group}
        U.error([[
          When @param:%(maskVar)s:3: is enabled, at least one of the following trip sources must be configured to 'Emit trip signal' %(group)s:

            - One of the analog trip signals under 'Coder Options -> Target -> Protections', for example @param:AnTrip1Enable:2: (or one of the other analog trip signals), must be configured to 'Emit trip signal' %(group)s, or
            - An SDFM block with comparator powerstage protections must be configured to 'Emit trip signal' %(group)s.

          ]] % {maskVar = maskVar, group = group}, {self:getPath()})
      end
    end

    local psRate = math.floor(1 / self.sampleTime + 0.5)

    c.PreInitCode:append([[
      {
        PLX_PWR_sinit();]])
    if self.opt_psEnableDO then
      local psEnable = self.opt_psEnableDO
      c.PreInitCode:append([[
        PLX_DIO_sinit();
        static PLX_DIO_Obj_t doutObj;
        PLX_DIO_Handle_t doutHandle = PLX_DIO_init(&doutObj, sizeof(doutObj));
        PLX_DIO_OutputProperties_t props = {0};
        props.enableInvert = %(invert)s;
        PLX_DIO_configureOut(doutHandle, %(enGpio)d, &props);
        PLX_PWR_configure(doutHandle, %(psRate)d);
    ]] % {
        invert = psEnable.cbx_polarity.equals('active low')
           and 'true'
           or 'false',
        enGpio = psEnable.gpio,
        psRate = psRate,
      })
    else
      c.PreInitCode:append([[
        PLX_PWR_configure(0, %d);]] % {psRate})
    end
    c.PreInitCode:append([[
      }]])
  end

  function Powerstage:finalize(c)
    if static[self.cpu].finalized then
      return
    end

    c.Include:append('plx_power.h')

    c.Declarations:append([[
      void PLXHAL_PWR_setEnableRequest(bool aEnable) {
        PLX_PWR_setEnableRequest(aEnable);
        PLX_PWR_runFsm();
      }

      bool PLXHAL_PWR_isEnabled() {
        return PLX_PWR_isEnabled();
      }

      void PLXHAL_PWR_syncdPwmEnable() {
        PLX_PWR_syncdSwitchingEnable();
      }
      ]])

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

    static[self.cpu].finalized = true
  end

  return Powerstage
end

return Module
