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

  function SpiPeripheral:checkMaskParameters()

    self.spiUnitAsChar = U.comboAsChar(Block.Mask.SpiModule)

    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.dimension = U.enforceMask(U.isIntScalarInClosedInterval, 'WordsPerTransmission', 1, spi_fifo_depth)
    
    self.gpios = U.enforceMask(U.isNonNegativeIntScalarOrVector, 'Gpios')
    if #self.gpios ~= 4 then
      U.error('Please supply a vector of 4 GPIOS for @param:Gpios:.')
    end
  end

  function SpiPeripheral: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 = 0, -- peripheral
      gpio = Block.Mask.Gpios
    }, Require)

    self.spi_instance = self.spi_obj:getObjIndex()

    local txBufferAssignmentCode = U.CodeLines:new()
    for i = 1, self.dimension do
      txBufferAssignmentCode:append([[
          SpiPeripheral%(spiInstance)dTxBuffer[%(index)d] = %(inputSignal)s;
      ]] % {
        spiInstance = self.spi_instance,
        index = i - 1,
        inputSignal = Block.InputSignal[1][i],
      })
    end

    OutputCode:append([[
      // set up I/O buffers
      static uint16_t %(spiPeriphStr)sRxBuffer[%(dim)d] = {0};
      static uint16_t %(spiPeriphStr)sTxBuffer[%(dim)d];
      static bool %(spiPeriphStr)sDataReady = false;
      static bool %(spiPeriphStr)sRxOverrun = false;

      {
        // service task
        %(txBufferAssignmentCode)s
        %(spiPeriphStr)sRxOverrun = 
          PLXHAL_SPI_getAndResetRxOverrunFlag(%(spiInstance)d) ||
          (PLXHAL_SPI_getRxFifoLevel(%(spiInstance)d) > %(dim)d);
        %(spiPeriphStr)sDataReady = 
          PLXHAL_SPI_getWords(%(spiInstance)d, &%(spiPeriphStr)sRxBuffer[0], %(dim)d);
        if (%(spiPeriphStr)sDataReady){
          PLXHAL_SPI_putWords(%(spiInstance)d, &%(spiPeriphStr)sTxBuffer[0], %(dim)d); 
        }
      }
    ]] % {
      spiPeriphStr = 'SpiPeripheral%d' % {self.spi_instance},
      spiInstance  = self.spi_instance,
      dim = self.dimension,
      txBufferAssignmentCode = txBufferAssignmentCode:concat(),
    })

    -- output signals
    OutputSignal[1] = {}
    for i = 1, self.dimension do
      OutputSignal[1][i] = 'SpiPeripheral%iRxBuffer[%i]' % {self.spi_instance, i - 1}
    end
    OutputSignal[2] = {}
    OutputSignal[2][1] = 'SpiPeripheral%iDataReady' % {self.spi_instance}
    OutputSignal[3] = {}
    OutputSignal[3][1] = 'SpiPeripheral%iRxOverrun' % {self.spi_instance}

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

  function SpiPeripheral:p_getNonDirectFeedthroughCode()
    return {}
  end

  function SpiPeripheral:finalizeThis(c)
  end

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

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

    static[self.cpu].finalized = true
  end

  return SpiPeripheral
end

return Module

