--[[
  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 SpiController = require('common.block').getBlock(globals, cpu)
  if not static[cpu] then
    static[cpu] = {
      numInstances = 0,
      instances = {},
      finalized = false,
    }
  end
  SpiController.instance = static[cpu].numInstances
  static[cpu].numInstances = static[cpu].numInstances + 1

  function SpiController:checkMaskParameters()

    self.spiUnitAsChar = U.comboAsChar(Block.Mask.SpiModule)
    
    self.clkRate = U.enforceMask(U.isPositiveIntScalar, 'ClkRate')

    globals.target.checkSpiClockIsAchievable(self.clkRate)

    self.wordLength = U.enforceMask(
      U.isIntScalarInClosedInterval, 'WordLength', 1, 16)

    if Block.Mask.Mode == nil then
        self.mode = ({2,3,0,1})[Block.Mask.mode] --deprecated parameter re-map
        U.warning('Block uses a deprecated SPI mode parameter. To remove this warning set the Mode parameter to \'hide\' and then reselect the desired option.')
    else
        local cbx_spiMode = U.enforceMask_newComboBox('Mode', {'0', '1', '2', '3'})
        self.mode = cbx_spiMode.asCx() - 1
    end

    local spi_fifo_depth =
        globals.target.getTargetParameters()['spis']['fifo_depth']

    self.gpios = U.enforceMask(U.is3Pins, 'Gpios')
    self.csGpio = U.enforceMask(U.isNonNegativeIntScalarOrVector, 'CsGpio')
    self.wordsPerTransmission = U.enforceMask(
      U.isIntScalarOrVectorInClosedInterval, 'WordsPerTransmission', 1, spi_fifo_depth)

    if #self.csGpio ~= #self.wordsPerTransmission then
      U.error('Dimensions of peripheral @param:CsGpio: and @param:WordsPerTransmission: must match.')
    end
  end

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

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

    self.spi_obj = self:makeBlock('spi', self.cpu)
    self.spi_obj:createImplicitSpi(self.spiUnitAsChar, {
      charlen = self.wordLength,
      pol = (self.mode >= 2),
      phase = (self.mode == 0) or (self.mode == 2),
      baudrate = self.clkRate,
      gpio = self.gpios
    }, Require)

    self.spiInstance = self.spi_obj:getObjIndex()



    -- set up chip selects
    self.csDoutHandles = {}
    for _, pinNumber in ipairs(self.csGpio) do
      local dio_obj = self:makeBlock('dout', self.cpu)
      local cs = dio_obj:createImplicitDout(pinNumber, {}, Require)
      table.insert(self.csDoutHandles, cs)
    end

    local rxDataSwapCode = U.CodeLines:new()
    local txDataSwapCode = U.CodeLines:new()
    for i = 1, #Block.InputSignal[1] do
      rxDataSwapCode:append([[
        SpiController%(inst)dRxData[%(index)d] = SpiController%(inst)dRxDataBuffer[%(index)d]; 
      ]] % {
        inst = self.spiInstance,
        index = i - 1,
      })

      txDataSwapCode:append([[
        SpiController%(inst)dTxData[%(index)i] = %(inputStr)s;
      ]] % {
        inst = self.spiInstance,
        index = i - 1,
        inputStr = Block.InputSignal[1][i],
      })
    end

    OutputCode:append([[
      static uint16_t SpiController%(inst)dPeripheralCsHandles[] = %(csArrayStr)s;
      static uint16_t SpiController%(inst)dPeripheralWordsPerTransmission[] = %(dimArrayStr)s;
      static uint16_t SpiController%(inst)dRxData[%(numInputs)d] = {0};
      static uint16_t SpiController%(inst)dTxData[%(numInputs)d];
      static uint16_t SpiController%(inst)dRxDataBuffer[%(numInputs)d];
      static uint16_t SpiController%(inst)dPeripheralIndex = 0;
      static uint16_t SpiController%(inst)dPeripheralDataIndex = 0;
      static bool SpiController%(inst)dPeripheralTxActive = false;
      static bool SpiController%(inst)dReady = false; 
      static bool SpiController%(inst)dTxOverrun = false;
      SpiController%(inst)dReady = false;
      if(SpiController%(inst)dPeripheralTxActive){
        // de-assert last CS
        PLXHAL_DIO_set(SpiController%(inst)dPeripheralCsHandles[SpiController%(inst)dPeripheralIndex], true);

        SpiController%(inst)dTxOverrun = (PLXHAL_SPI_getRxFifoLevel(%(inst)d) != SpiController%(inst)dPeripheralWordsPerTransmission[SpiController%(inst)dPeripheralIndex]);
        if(SpiController%(inst)dTxOverrun){
          // overrun occurred
          SpiController%(inst)dPeripheralIndex = 0;
          SpiController%(inst)dPeripheralTxActive = false;
        } else {
          // read data
          PLXHAL_SPI_getWords(%(inst)d, &SpiController%(inst)dRxDataBuffer[SpiController%(inst)dPeripheralDataIndex], SpiController%(inst)dPeripheralWordsPerTransmission[SpiController%(inst)dPeripheralIndex]); // FIXME: incorrect index

          // next peripheral
          SpiController%(inst)dPeripheralDataIndex += SpiController%(inst)dPeripheralWordsPerTransmission[SpiController%(inst)dPeripheralIndex];
          SpiController%(inst)dPeripheralIndex++;
          if(SpiController%(inst)dPeripheralIndex == %(numPeripherals)s){
            // all peripherals have been serviced
            %(rxDataSwapCode)s
            SpiController%(inst)dReady = true;

            SpiController%(inst)dPeripheralIndex = 0;
            SpiController%(inst)dPeripheralTxActive = false;
          }
        }
      }

      // prime next transmission
      if(SpiController%(inst)dPeripheralIndex == 0){
        %(txDataSwapCode)s
        SpiController%(inst)dPeripheralDataIndex = 0;
        SpiController%(inst)dPeripheralTxActive = true;
      }

      if(SpiController%(inst)dPeripheralTxActive){
        PLXHAL_DIO_set(SpiController%(inst)dPeripheralCsHandles[SpiController%(inst)dPeripheralIndex], false);
        PLXHAL_SPI_putWords(
          %(inst)d, &SpiController%(inst)dTxData[SpiController%(inst)dPeripheralDataIndex], 
          SpiController%(inst)dPeripheralWordsPerTransmission[SpiController%(inst)dPeripheralIndex]);
      }
    ]] % {
      csArrayStr = U.toIntegerInitializationList(self.csDoutHandles),
      dimArrayStr = U.toIntegerInitializationList(self.wordsPerTransmission),
      numInputs = #Block.InputSignal[1],
      numPeripherals = #self.csGpio,
      inst = self.spiInstance,
      rxDataSwapCode = rxDataSwapCode:concat(),
      txDataSwapCode = txDataSwapCode:concat(),
    })

    OutputSignal[1] = {}
    for i = 1, #Block.InputSignal[1] do
      OutputSignal[1][i] = 'SpiController%dRxData[%i]' % {self.spiInstance, i - 1}
    end
    OutputSignal[2] = {}
    OutputSignal[2][1] = 'SpiController%dReady' % {self.spiInstance}
    OutputSignal[3] = {}
    OutputSignal[3][1] = 'SpiController%dTxOverrun' % {self.spiInstance}

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

  function SpiController:p_getNonDirectFeedthroughCode()
    return {}
  end

  function SpiController:finalizeThis(c)
  end

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

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

    static[self.cpu].finalized = true
  end

  return SpiController
end

return Module

