--[[
  Copyright (c) 2021 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:getEnableCode()
    -- TODO: maybe we need to issue this statement once for each task?
    if self.enable_code_generated == nil 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 OutputSignal = StringList: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)

    -- check if global tripzone object exists
    self.tripzones_obj = self:getGlobalBlockInstance('tripzones')

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

    self.isDeprecated = (Block.Mask.IsDeprecated == 1)

    local ts = Block.Task['SampleTime']
    if ts[1] == 0 then
      U.error('Invalid sample time.')
    end
    self.sample_time = ts[1]
    self.task_name = Block.Task.Name
    -- this property is a global setting and can be used an accessed by other
    -- objects (e.g. epwm)
    self.force_safe = (Block.Mask.PwmSafeState == 1) -- {'Forced inactive', 'Floating'}

    if U.enforceMask_newComboBox('EnableSignal', 
                     {'Digital output', 'None'}).equals('Digital output') then
      self.enable_gpio = U.enforceMask(U.isNonNegativeIntScalar, 'EnableSignalGpio')
      self.cbx_enablePolarity = U.enforceMask_newComboBox('EnableSignalPolarity',
                                              {'active low', 'active high'})
      
      globals.target.allocateGpio(self.enable_gpio, {}, Require)
      globals.syscfg:addEntry('gpio', {
        unit = self.enable_gpio,
        cbx_direction = U.newComboBox('OUTPUT',
                                      U.ENUM.TI.DIO_TYPE),
        cbx_outputType = U.newComboBox('PUSH_PULL',
                                       U.ENUM.OUTPUT_TYPES),
        core = self.cpu + 1
      })
    end

    self.trip_zones_configured = {}
    if self.isDeprecated then
      if self.tripzones_obj:isAnyTripZoneOrGroupConfigured() then
        U.error('Please replace deprecated Powerstage Protection block.')
      end
      for z = 1, 3 do
        if Block.Mask['tz%d_gpio' % {z}] == Block.Mask['tz%d_gpio' % {z}] then
          self['tz%d_gpio' % {z}] = Block.Mask['tz%d_gpio' % {z}]
          globals.target.allocateGpio(Block.Mask['tz%d_gpio' % {z}], {}, Require)
          self.trip_zones_configured[z] = true
        end
      end
    end

    -- trip zone activation
    self.trip_zones = {}
    local z = 1
    while Block.Mask['TripZone%dMode' % {z}] do
      local tripZoneMode = 'TripZone%dMode' % {z}
      local cbx_tripZoneMode = U.enforceMask_newComboBox(tripZoneMode,
                                             {'ignore', 'cbc', 'osht'})
      if not cbx_tripZoneMode.equals('ignore') then
        if not self.tripzones_obj:isTripZoneConfigured(z) then
          U.error([[
            When @param:TripZone%dMode: is enabled, you must also @param:Tz%dEnable:2: under 'Coder Options -> Target -> Protections'.]] %
            {z, z})
        end
        self.trip_zones[z] = cbx_tripZoneMode.asString() -- FIX ME: switch trip_zones away from magic strings
      end
      z = z + 1
    end

    -- trip signal group activation
    self.trip_signal_groups = {}
    local s = 1
    while Block.Mask['TripSig%sMode' % {string.char(64 + s)}] ~= nil do
      local group = string.char(64 + s)
      local maskVar = 'TripSig%sMode' % {group}
      if Block.Mask[maskVar] == 2 then -- 
        if globals.target.getTargetParameters().epwms.type < 4 then
          U.error([[
            Trip signals are not supported by this target (%(target)s).
          ]] % {
            target = globals.target.getFamilyPrefix(),
          })
        end

        self.trip_signal_groups[group] = 'osht'
      end
      s = s + 1
    end

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

    OutputSignal:append('PLXHAL_PWR_isEnabled()')

    if self:targetUsesDriverLib() then
      -- for deprecated block
      for z = 1, 3 do
        if self['tz%d_gpio' % {z}] ~= nil then
          Require:add('XBAR_INPUT', z)
          globals.syscfg:addEntry('input_xbar', {
            gpio = self['tz%d_gpio' % {z}],
            input = z,
            core = self.cpu + 1
          })
          globals.syscfg:addEntry('gpio', {
            unit = self['tz%d_gpio' % {z}],
            cbx_direction = U.newComboBox('INPUT',
                                          U.ENUM.TI.DIO_TYPE),
            cbx_pullType = U.newComboBox('TRISTATE',
                                         U.ENUM.PULL_TYPES),
            core = self.cpu + 1
          })
        end
      end
    end

    return {
      InitCode = InitCode,
      OutputCode = OutputCode,
      OutputSignal = {OutputSignal},
      Require = Require,
      UserData = {bid = self:getId()}
    }
  end

  function Powerstage:isDeprecated()
    return self.isDeprecated
  end

  function Powerstage:isTripZoneConfigured(zone)
    return (self.trip_zones_configured[zone]) or
       (self.tripzones_obj:isTripZoneConfigured(zone))
  end

  function Powerstage:getTripZoneMode(zone)
    return self.trip_zones[zone]
  end

  function Powerstage:getTripSignalGroupModes()
    return self.trip_signal_groups
  end

  function Powerstage:finalizeThis(c)
    for group, _ in pairs(self.trip_signal_groups) do
      if not self.tripzones_obj:isTripSignalGroupConfigured(group) then
        local maskVar = 'TripSig%sMode' % {group}
        U.error([[
          When @param:%(maskVar)s: 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})

      end
      self.trip_signal_groups[group] = 'osht'
    end

    -- for deprecated block
    if self:targetUsesPinsets() then
      for z = 1, 3 do
        if self['tz%d_gpio' % {z}] then
          c.PreInitCode:append([[
            PLX_PWR_configureTZGpio(%d, %d);]] % {z, self['tz%d_gpio' % {z}]})
        end
      end
    end

    local ps_rate = math.floor(1 / self.sample_time + 0.5)

    c.PreInitCode:append([[
      {
        PLX_PWR_sinit();]])
    if self.enable_gpio ~= nil then
      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 = self.cbx_enablePolarity.equals('active low')
           and 'true'
           or 'false',
        enGpio = self.enable_gpio,
        psRate = ps_rate,
      })
    else
      c.PreInitCode:append([[
        PLX_PWR_configure(0, %d);]] % {ps_rate})
    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
