--[[
  Copyright (c) 2022 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 CAN_CONFIG = require('common.can_config')
local U = require('common.utils')

local static = {}
local Module = {}

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

  function MCan:checkMaskParameters()
  end

  function MCan:createImplicit(mcanUnitAsInt)
    self.mcanUnitAsInt = mcanUnitAsInt
    self.mcanUnitAsChar = string.char(65 + self.mcanUnitAsInt)

    static[self.cpu].instances[self.mcanUnitAsInt] = self.bid

    self.rxMailboxes = {}
    self.numRxMailboxes = 0
    self.txMailboxes = {}
    self.numTxMailboxes = 0
    self.isConfigured = false

    self:logLine('MCAN %s implicitly created.' % {self.mcanUnitAsChar})
  end

  function MCan:configureMCAN(params, req)

    local settingsContract = {
      samplePoint = U.isNonNegativeScalar,
      bitRate = U.isPositiveIntScalar,
      opt_bitLengthTq = U.isNonNegativeIntScalar,
      -- Resynchronization Jump Width
      opt_sjwTq = U.isNonNegativeIntScalar,
    }
    U.enforceParamContract(
      params,
      {
        gpio = {U.isFixedLengthArrayOf, 2, U.isNonNegativeIntScalar},
        autoBusOn = U.isBoolean, -- not used, but part of the mask structure
        nomSettings = settingsContract,
        opt_dataSettings = settingsContract,
        
        opt_ssp = {                         -- Secondary Sampling Point
          tdcf = U.isNonNegativeIntScalar,  -- Transmitter Delay Compensation Filter
          tdco = U.isNonNegativeIntScalar,  -- Transmitter Delay Compensation Offset
        },
      })

    self.gpio = params.gpio

    -- newer device supports MCAN A and B as opposted to just "MCAN"
    local canCharForMacro
    if self:targetMatches({'28P55x', '28P65x', '29H85x'}) then
      canCharForMacro = self.mcanUnitAsChar
    elseif self:targetMatches({'2838x', '28003x'}) then
      canCharForMacro = '' -- These targets have only one MCAN
    else
      U.throwUnhandledTargetError('MCAN not yet supported for this target.')
    end

    local rxGpio = 'GPIO_%d_MCAN%s_RX' % {self.gpio[1], canCharForMacro}
    local txGpio = 'GPIO_%d_MCAN%s_TX' % {self.gpio[2], canCharForMacro}

    if (not globals.target.validateAlternateFunction(rxGpio)) 
    or (not globals.target.validateAlternateFunction(txGpio)) then
      U.error('Invalid GPIO configured for CAN communication.')
    end

    globals.syscfg:addEntry('mcan', {
      unit = self.mcanUnitAsInt,
      pins = self.gpio,
      pinconf = {rxGpio, txGpio},
      core = self.cpu + 1
    })

    -- claim pins
    globals.target.allocateGpio(self.gpio[1], {}, req)
    globals.target.allocateGpio(self.gpio[2], {}, req)

    -- TODO: make configurable!
    self.clk = globals.target.getCleanSysClkHz()  -- for now

    self.nominalBitTiming = CAN_CONFIG.determineCanBitTiming({
      clk = self.clk,
      baud = params.nomSettings.bitRate,
      baudParamName = 'NomBitRate',
      brpMax = 512,
      tseg1Range = {2, 256},
      tseg2Range = {1, 128},
      sjwRange = {1, 128},
      samplePoint = params.nomSettings.samplePoint,
      -- advanced configuration
      opt_bitLengthTq = params.nomSettings.opt_bitLengthTq,
      opt_sjw = params.nomSettings.opt_sjwTq
    })

    if params.opt_dataSettings then
      local ds = params.opt_dataSettings
      self.opt_dataBitTiming = CAN_CONFIG.determineCanBitTiming({
        clk = self.clk,
        baud = ds.bitRate,
        baudParamName = 'DataBitRate',
        brpMax = 32,
        tseg1Range = {1, 32},
        tseg2Range = {1, 16},
        sjwRange = {1, 16},
        samplePoint = ds.samplePoint,
        -- advanced configuration
        opt_bitLengthTq = ds.opt_bitLengthTq,
        opt_sjw = ds.opt_sjwTq,
      })

      -- SSP configuration
      if params.opt_ssp then
        self.opt_ssp = params.opt_ssp
      else
        -- automatic configuration at middle of bit
        self.opt_ssp = {
          tdcf = 0,
          tdco = math.floor((1 + self.opt_dataBitTiming.tseg1 + self.opt_dataBitTiming.tseg2) / 2)
        }
      end
    end

    self.isConfigured = true
  end

  function MCan:canPortConfigured()
    return self.isConfigured
  end

  function MCan:getTxMailbox()
    if self.numTxMailboxes == 32 then
      U.error('Only 32 MCAN mailboxes available for transmit.')
    end
    local mbox = self.numTxMailboxes
    self.numTxMailboxes = self.numTxMailboxes + 1
    return mbox
  end

  function MCan:setupTxMailbox(mbox, params)
    assert(U.isNonNegativeIntScalar(mbox))
    U.enforceParamContract(
      params,
      {
        canId = {U.isScalarInClosedInterval, 0, 0x1FFF0000},
        isExtId = U.isBoolean,
        width = U.isNonNegativeIntScalar,
        dlc = U.isNonNegativeIntScalar,
        brsEnabled = U.isBoolean,
      })

    self.txMailboxes[mbox] = {
      canId = params.canId,
      isExtId = params.isExtId,
      width = params.width,
      dlc = params.dlc,
      brsEnabled = params.brsEnabled,
    }
  end

  function MCan:getRxMailbox()
    local maxNumMailboxes
    if self:targetMatches({'2838x', '28003x'}) then
      maxNumMailboxes = 32
    elseif self:targetMatches({'28P55x', '28P65x', '29H85x'}) then
      maxNumMailboxes = 21 -- Due to smaller message RAM on these targets.
    else
      U.throwUnhandledTargetError()
    end
    if self.numRxMailboxes == maxNumMailboxes then
      U.error('Only %d MCAN mailboxes available for receive.' % {maxNumMailboxes})
    end
    local mbox = self.numRxMailboxes
    self.numRxMailboxes = self.numRxMailboxes + 1
    return mbox
  end

  function MCan:setupRxMailbox(mbox, params)
    assert(U.isNonNegativeIntScalar(mbox))
    U.enforceParamContract(
      params,
      {
        canId = {U.isScalarInClosedInterval, 0, 0x1FFF0000},
        isExtId = U.isBoolean,
        width = U.isNonNegativeIntScalar,
        dlc = U.isNonNegativeIntScalar,
      })

    self.rxMailboxes[mbox] = {
      canId = params.canId,
      isExtId = params.isExtId,
      width = params.width,
      dlc = params.dlc
    }
  end

  function MCan:p_getDirectFeedthroughCode()
    U.error('Explicit use of CAN via target block not supported.')
  end

  function MCan:p_getNonDirectFeedthroughCode()
    U.error('Explicit use of CAN via target block not supported.')
  end

  function MCan:finalizeThis(c)
    self:logLine('CAN settings (nominal): %s' % {dump(self.nominalBitTiming)})
    if self.opt_dataBitTiming then
      self:logLine('CAN settings (data): %s' % {dump(self.opt_dataBitTiming)})
    end

    c.PreInitCode:append([[
      // Configure MCAN %(unit)s at %(nominalBaudRate)d Bit/s, with sampling at %(nominalSamplePt).1f%%
      // %(optionalFDcomment)s
      {
        PLX_MCAN_Params_t params = {0};
        params.tseg1 = %(nominalTseg1)d;
        params.tseg2 = %(nominalTseg2)d;
        params.sjw = %(nominalSjw)d;
        params.brp = %(nominalBrp)d; ]]
      % {
        unit = self.mcanUnitAsChar,
        nominalBaudRate = self.nominalBitTiming.baud,
        nominalSamplePt = 100 * self.nominalBitTiming.samplePoint,
        optionalFDcomment = 
           self.opt_dataBitTiming 
           and 'FD rate set to %d Bit/s' % {self.opt_dataBitTiming.baud}
           or '',
        nominalTseg1 = self.nominalBitTiming.tseg1,
        nominalTseg2 = self.nominalBitTiming.tseg2,
        nominalSjw = self.nominalBitTiming.sjw,
        nominalBrp = self.nominalBitTiming.brp,
      })

    if self.opt_dataBitTiming then
      c.PreInitCode:append([[
        params.enableRateSwitching = true;
        params.tseg1_data = %(tseg1)d;
        params.tseg2_data = %(tseg2)d;
        params.sjw_data = %(sjw)d;
        params.brp_data = %(brp)d;]] 
        % self.opt_dataBitTiming)
    else
      c.PreInitCode:append([[
        params.enableRateSwitching = false;]])
    end

    if self.opt_ssp then
      c.PreInitCode:append([[
        params.enableSecondarySamplePoint = true;
        params.tdcf = %(tdcf)d;
        params.tdco = %(tdco)d;
        ]] % self.opt_ssp)
    else
      c.PreInitCode:append([[
        params.enableSecondarySamplePoint = false;]])
    end

    if self.numRxMailboxes > 0 then
      c.PreInitCode:append([[
        params.numRxMailboxes = %(numRx)d;
        static PLX_MCAN_RxMailbox_t rxMailboxes[%(numRx)d];
        params.rxMailboxes = &rxMailboxes[0];]] % {numRx = self.numRxMailboxes})
    else
      c.PreInitCode:append([[
        params.numRxMailboxes = 0;]])
    end
    
    if self.numTxMailboxes > 0 then
      c.PreInitCode:append([[
        params.numTxMailboxes = %(numTx)d;
        static PLX_MCAN_TxMailbox_t txMailboxes[%(numTx)d];
        params.txMailboxes = &txMailboxes[0];]] % {numTx = self.numTxMailboxes})
    else
      c.PreInitCode:append([[
        params.numTxMailboxes = 0;]])
      
    end
    c.PreInitCode:append([[
      PLX_MCAN_configure(MCanHandles[%(instance)d], PLX_MCAN_MCAN_%(unit)s, &params);]]
      % {
        instance = self.instance,
        unit = self.mcanUnitAsChar,
      })

    for mbox, params in U.pairsSorted(self.rxMailboxes) do
      U.enforceParamContract(
        params,
        {
          canId = {U.isScalarInClosedInterval, 0, 0x1FFF0000},
          isExtId = U.isBoolean,
          width = U.isNonNegativeIntScalar,
          dlc = U.isNonNegativeIntScalar,
        })
      c.PreInitCode:append([[
        (void)PLX_MCAN_setupRxMailbox(MCanHandles[%d], %d, %d, %d, %d);]]
        % {self.instance, mbox, params.canId, params.isExtId, params.dlc})
    end

    for mbox, params in U.pairsSorted(self.txMailboxes) do
      U.enforceParamContract(
        params,
        {
          canId = {U.isScalarInClosedInterval, 0, 0x1FFF0000},
          isExtId = U.isBoolean,
          width = U.isNonNegativeIntScalar,
          dlc = U.isNonNegativeIntScalar,
          brsEnabled = U.isBoolean,
        })
      c.PreInitCode:append([[
        (void)PLX_MCAN_setupTxMailbox(MCanHandles[%d], %d, %d, %d, %d, %s);]]
        % {self.instance, mbox, params.canId, params.isExtId, params.dlc, tostring(params.brsEnabled)})
    end
    c.PreInitCode:append([[
      }
      ]])
  end

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

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

    c.Declarations:append([[
      PLX_MCAN_Handle_t MCanHandles[%(numInstances)d];
      PLX_MCAN_Obj_t MCanObj[%(numInstances)d];

      bool PLXHAL_MCAN_getMessage(uint16_t aChannel, uint16_t aMailBox, unsigned char data[], unsigned char lenMax, uint16_t *aFlags){
          return PLX_MCAN_getMessage(MCanHandles[aChannel], aMailBox, data, lenMax, aFlags);
      }

      void PLXHAL_MCAN_putMessage(uint16_t aChannel, uint16_t aMailBox, unsigned char data[], unsigned char len){
          (void)PLX_MCAN_putMessage(MCanHandles[aChannel], aMailBox, data, len);
      }

      void PLXHAL_MCAN_setBusOn(uint16_t aChannel, bool aBusOn){
          PLX_MCAN_setBusOn(MCanHandles[aChannel], aBusOn);
      }

      bool PLXHAL_MCAN_getIsBusOn(uint16_t aChannel){
          return PLX_MCAN_isBusOn(MCanHandles[aChannel]);
      }

      bool PLXHAL_MCAN_getIsErrorActive(uint16_t aChannel){
          return PLX_MCAN_isErrorActive(MCanHandles[aChannel]);
      }
      ]] % {numInstances = static[self.cpu].numInstances})

    c.PreInitCode:append([[
        PLX_MCAN_sinit();
        for (int i = 0; i < %(numInstances)d; i++) {
          MCanHandles[i] = PLX_MCAN_init(&MCanObj[i], sizeof(MCanObj[i]));
        }
      ]] % {numInstances = static[self.cpu].numInstances})

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

    static[self.cpu].finalized = true
  end

  return MCan
end

return Module
