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

local static = {}
local Module = {}

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

  function Can:checkMaskParameters()
  end

  function Can:createImplicitCan(canUnitAsChar)
    self.canUnitAsChar = canUnitAsChar
    
    static[self.cpu].instances[self.canUnitAsChar] = self.bid


    self.mailboxes = {}
    self.numMailboxes = 0
    self.isConfigured = false

    self:logLine('CAN %s implicitly created.' % {self.canUnitAsChar})
  end

  function Can:configureCAN(params, req)
    U.enforceParamContract(
      params,
      {
        gpio = U.isPinPair,
        autoBusOn = U.isBoolean,
        baudRate = U.isPositiveIntScalar,
        samplePoint = U.isNonNegativeScalar,
        opt_bitLengthTq = U.isNonNegativeIntScalar,
        opt_sjwTq = U.isNonNegativeIntScalar,
      })

      
    self.gpio = params.gpio
    self.autoBusOn = params.autoBusOn

    if self:targetUsesPinsets() then
      -- older targets require hard-coded pin-sets
      local canPins = '%s_GPIO%d_GPIO%d' % {
        self.canUnitAsChar, self.gpio[1], self.gpio[2]
      }
      self.pinset =
         globals.target.getTargetParameters().cans.pin_sets[canPins]

      if self.pinset == nil then
        U.error('Pins [%d, %d] not supported for CAN %s.' % {
          self.gpio[1], self.gpio[2], self.canUnitAsChar
        })
      end
    else
      -- newer targets have driverlib
      local rxGpio, txGpio
      if self:targetMatches({'2837x'}) then
        rxGpio = 'GPIO_%d_CANRX%s' % {self.gpio[1], self.canUnitAsChar}
        txGpio = 'GPIO_%d_CANTX%s' % {self.gpio[2], self.canUnitAsChar}
      elseif self:targetMatches({'2838x', '28003x', '28004x', '280013x', '28P55x', '28P65x'}) then
        rxGpio = 'GPIO_%d_CAN%s_RX' % {self.gpio[1], self.canUnitAsChar}
        txGpio = 'GPIO_%d_CAN%s_TX' % {self.gpio[2], self.canUnitAsChar}
      else
        U.throwUnhandledTargetError()
      end
      if (not globals.target.validateAlternateFunction(rxGpio)) 
      or (not globals.target.validateAlternateFunction(txGpio)) then
        U.error('Invalid @param:Gpio: configured for CAN communication.')
      end
      globals.syscfg:addPeripheralEntry('can', {
        unitAsChar = self.canUnitAsChar,
        pins = self.gpio,
        pinconf = {rxGpio, txGpio},
        core = globals.target.getTiCpuNumber(self.cpu)
      })
    end

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

    -- determine bit timing
    local clk, brpMax = globals.target.getCanClkAndMaxBrp()

    self.bt = CAN_CONFIG.determineCanBitTiming({
      clk = clk,
      baud = params.baudRate,
      baudParamName = 'BaudRate',
      brpMax = brpMax,
      tseg1Range = {2, 16},
      tseg2Range = {1, 8},
      sjwRange = {1, 4},
      samplePoint = params.samplePoint,
      -- advanced configuration
      opt_bitLengthTq = params.opt_bitLengthTq,
      opt_sjw = params.opt_sjwTq,
    })
    self.isConfigured = true
  end

  function Can:canPortConfigured()
    return self.isConfigured
  end

  function Can:getMailbox()
    if self.numMailboxes == 32 then
      U.error('This device supports a maximum of 32 mailboxes for CAN Rx and Tx.')
    end
    local mbox = self.numMailboxes
    self.numMailboxes = self.numMailboxes + 1
    return mbox
  end

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

    self.mailboxes[mbox] = {
      isTx = true,
      canId = params.canId,
      isExtId = params.isExtId,
      width = params.width,
    }
  end

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

    self.mailboxes[mbox] = {
      isTx = false,
      canId = params.canId,
      isExtId = params.isExtId,
      width = params.width
    }
  end

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

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

  function Can:finalizeThis(c)
    self:logLine('CAN settings: %s' % {dump(self.bt)})

    c.PreInitCode:append([[
      // Configure CAN %(unit)s at %(baudRate)d Bit/s, with sampling at %(samplePoint).1f%%
      {
        PLX_CANBUS_Params_t params;
        params.tseg1 = %(tseg1)d;
        params.tseg2 = %(tseg2)d;
        params.sjw = %(sjw)d;
        params.sam = %(sam)d;
        params.brp = %(brp)d;
        params.autoBusOn = %(cb_autoBusOn)d; ]]
      % {
        unit = self.canUnitAsChar,
        baudRate = self.bt.baud,
        samplePoint = 100 * self.bt.samplePoint,
        tseg1 = self.bt.tseg1,
        tseg2 = self.bt.tseg2,
        sjw = self.bt.sjw,
        sam = 0,  -- 1x ((3x sampling not supported on newer MCUs)
        brp = self.bt.brp,
        cb_autoBusOn = self.autoBusOn and 1 or 0,
      })
    if self.pinset then
      c.PreInitCode:append([[
        PLX_CANBUS_configureViaPinSet(CanHandles[%d], PLX_CANBUS_CAN_%s, %d, &params);]]
        % {self.instance, self.canUnitAsChar, self.pinset})
    else
      c.PreInitCode:append([[
        PLX_CANBUS_configure(CanHandles[%(instance)d], PLX_CANBUS_CAN_%(unit)s, &params);]]
        % {instance = self.instance, unit = self.canUnitAsChar})

    end
    for mbox, params in pairs(self.mailboxes) do
      U.enforceParamContract(
        params,
        {
          canId = {U.isScalarInClosedInterval, 0, 0x1FFF0000},
          isExtId = U.isBoolean,
          width = U.isNonNegativeIntScalar,
          isTx = U.isBoolean, -- plecs converts this to 0 or 1 in string sub routine
        })
      c.PreInitCode:append([[
        (void)PLX_CANBUS_setupMailbox(CanHandles[%(canUnit)d], %(mbox)d, %(isTx)d, %(id)d, %(isExt)d, %(w)d);]]
        % {
          canUnit = self.instance, 
          mbox = mbox, 
          isTx = params.isTx, 
          id = params.canId, 
          isExt = params.isExtId, 
          w = params.width})
    end
    c.PreInitCode:append([[
      }]])
  end

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

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

    c.Declarations:append([[
      PLX_CANBUS_Handle_t CanHandles[%(numInstances)d];
      PLX_CANBUS_Obj_t CanObj[%(numInstances)d];

      bool PLXHAL_CAN_getMessage(uint16_t aChannel, uint16_t aMailBox, unsigned char data[], unsigned char lenMax){
          return PLX_CANBUS_getMessage(CanHandles[aChannel], aMailBox, data, lenMax);
      }

      void PLXHAL_CAN_putMessage(uint16_t aChannel, uint16_t aMailBox, unsigned char data[], unsigned char len){
          (void)PLX_CANBUS_putMessage(CanHandles[aChannel], aMailBox, data, len);
      }

      void PLXHAL_CAN_setBusOn(uint16_t aChannel, bool aBusOn){
          PLX_CANBUS_setBusOn(CanHandles[aChannel], aBusOn);
      }

      bool PLXHAL_CAN_getIsBusOn(uint16_t aChannel){
          return PLX_CANBUS_isBusOn(CanHandles[aChannel]);
      }

      bool PLXHAL_CAN_getIsErrorActive(uint16_t aChannel){
          return PLX_CANBUS_isErrorActive(CanHandles[aChannel]);
      }
      ]] % {numInstances = static[self.cpu].numInstances})

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

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

    static[self.cpu].finalized = true
  end

  return Can
end

return Module
