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

local static = {}

function Module.getBlock(globals, cpu)

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

  function Timer:AllocateUnit()
    local units = globals.target.getTargetParameters()['cpu_timers']
    for k, v in pairs(units) do
      if static[self.cpu].unitsAllocated[k] == nil then
        table.insert(static[self.cpu].unitsAllocated, v)
        return v
      end
    end
  end

  function Timer:createImplicitTimer(params)
    self.unit = Timer:AllocateUnit()
    if self.unit == nil then
      U.error('Unable to allocate CpuTimer.')
    end
    self.period = math.max(1, math.floor(
                               globals.target.getTimerClock() / params['f'] +
                                   0.5))
    if self.period > 0x100000000 then
      U.error(
          "Unable to achieve the desired timer frequency (%f Hz is too low)." %
              {params['f']})
    end
    self.frequency = globals.target.getTimerClock() / self.period
    static[self.cpu].instances[self.unit] = self.bid
    self:logLine('CPUTIMER%i implicitly created.' % {self.unit})
  end

  function Timer:checkMaskParameters()
    if not U.isPositiveScalar(Block.Mask.Frequency) then
      U.error('Timer frequency [Hz]" must be a positive real scalar value.')
    end
  end

  function Timer:p_getDirectFeedthroughCode()
    local Require = ResourceList:new()
    local InitCode = U.CodeLines:new()
    local OutputCode = U.CodeLines:new()
    local OutputSignal = StringList:new()

    self.unit = Timer:AllocateUnit()
    if self.unit == nil then
      U.error("No spare timer available.")
    end
    -- important: finalize code assumes timer 0
    if self.unit ~= 0 then
      -- important: timer 0 is assumed in finalize call
      U.error("Only CPUTimer 0 is currently supported.")
    end
    static[self.cpu].instances[self.unit] = self.bid

    Require:add('CPU%iTIMER' % {self.cpu + 1}, self.unit)

    -- accuracy of frequency settings
    local freqDesired = Block.Mask.Frequency

    local period = math.max(
      1,
      math.floor(globals.target.getTimerClock() / freqDesired + 0.5))

    if period > 0x100000000 then
      U.error(
        'Unable to achieve the desired timer frequency (%f Hz is too low).' %
        {freqDesired})
    end
    local achievableF = globals.target.getTimerClock() / period
  

    if Block.Mask.FreqTolerance == 1 then  -- enforce exact value (no rounding)
      U.enforceExactFrequencyTolerance(
        {
          freqDesired = freqDesired,
          freqWidgetLink = '@param:Frequency:',
          freqTolWidgetLink = '@param:FreqTolerance:',
          freqAchievable = achievableF,
          descriptor = 'timer frequency',
        }
      )
    end

    self['frequency'] = achievableF
    self['period'] = period

    OutputSignal:append("{modtrig = {bid = %i}}" % {Timer:getId()})
    OutputSignal:append("{adctrig = {bid = %i}}" % {Timer:getId()})

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

  function Timer:p_getNonDirectFeedthroughCode()
    return {}
  end

  function Timer:setSinkForTriggerSource(sink)
    if sink ~= nil then
      if self[sink.type] == nil then
        self[sink.type] = {}
      end
      table.insert(self[sink.type], globals.instances[sink.bid])
    end
  end

  function Timer:propagateTriggerSampleTime(ts)
    if self['modtrig'] ~= nil then
      for _, b in ipairs(self['modtrig']) do
        local f = b:propagateTriggerSampleTime(1 / self['frequency'])
      end
    end
    if self['adctrig'] ~= nil then
      for _, b in ipairs(self['adctrig']) do
        local f = b:propagateTriggerSampleTime(1 / self['frequency'])
      end
    end
  end

  function Timer:requestImplicitTrigger(ts)
    local achievableTs = 1 / self['frequency']
    self:logLine('Offered trigger generator at %f Hz' % {1 / achievableTs})
    return achievableTs
  end

  function Timer:finalizeThis(c)
    local isModTrigger = false
    local isAdcTrigger = false
    local isClaTrigger = false

    if self['modtrig'] ~= nil then
      for _, b in ipairs(self['modtrig']) do
        if b:blockMatches('tasktrigger') then
          isModTrigger = true
        end
        if b:blockMatches('cla') then
          isClaTrigger = true
        end
      end
    end

    if self['adctrig'] ~= nil then
      isAdcTrigger = true
    end

    if isModTrigger or isAdcTrigger or isClaTrigger then
      c.PreInitCode:append('{')
      local isr
      if isModTrigger then
        isr = '%s_baseTaskInterrupt' % {Target.Variables.BASE_NAME}
      end
      c.PreInitCode:append(globals.target.getCpuTimerSetupCode(self.unit, {
        period = self.period,
        isr = isr
      }))
      c.PreInitCode:append('}')
      if not self:targetMatches({'29H85x'}) then
        c.TimerSyncCode:append('CpuTimer%iRegs.TCR.bit.TSS = 0;' % {self.unit})
      else
        c.TimerSyncCode:append('CPUTimer_startTimer(CPUTIMER%i_BASE);' % {self.unit})        
      end
    end
  end

  function Timer:generateTaskTriggerIsrCode(f)
    -- note: this is hard-coded for CPUTimer0
    local itFunction
    if not self:targetMatches({'29H85x'}) then
      itFunction = [[
        interrupt void %(isr)s_baseTaskInterrupt(void)
        {
          PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
          IER |= M_INT1;
          DISPR_dispatch();
        }
      ]]
    else
      itFunction = [[
        __attribute__((interrupt("INT"))) void %(isr)s_baseTaskInterrupt(void);
        void %(isr)s_baseTaskInterrupt(void)
        {
          DISPR_dispatch();
        }
      ]]
    end
    f.Declarations:append("%s\n" % {itFunction % {
      isr = Target.Variables.BASE_NAME
    }})
    if not self:targetMatches({'29H85x'}) then
      f.Declarations:append([[
        #pragma CODE_SECTION(%(base_name)s_hasBaseTaskOverrunOccurred, "ramfuncs")
        static inline bool %(base_name)s_hasBaseTaskOverrunOccurred() {
          return PieCtrlRegs.PIEIFR1.bit.INTx7;
        }
      ]] % {
        base_name = Target.Variables.BASE_NAME,
      })
    else
      f.Declarations:append([[
        __attribute__((section(".ramfuncs"))) static inline bool %(base_name)s_hasBaseTaskOverrunOccurred() {
          return (HWREGB(PIPE_BASE + PIPE_O_INT_CTL_L(INT_TIMER0)) & PIPE_INT_CTL_L_FLAG) != 0U;
        }
      ]] % {
        base_name = Target.Variables.BASE_NAME,
      })
    end
    if not self:targetMatches({'29H85x'}) then
      f.InterruptEnableCode:append('IER |= M_INT1;')
    else
      f.InterruptEnableCode:append('CPUTimer_enableInterrupt(CPUTIMER%i_BASE);' % {self.unit})
    end
  end
  function Timer:finalize(c)
    if static[self.cpu].finalized then
      return
    end

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

    static[self.cpu].finalized = true
  end

  function Timer:registerTriggerRequest(trig)
    
  end

  return Timer

end

return Module
