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

  function Spi:checkMaskParameters()
  end

  function Spi:createImplicit(spi, params, req)
    self.spiUnitAsChar = spi
    static[self.cpu].instances[self.spiUnitAsChar] = self.bid

    req:add('SPI %s' % {self.spiUnitAsChar})

    self:logLine('SPI %s implicitly created.' % {self.spiUnitAsChar})

    self.charlen = params.charlen
    self.pol = params.pol
    self.phase = params.phase
    self.baudrate = params.baudrate
    self.controllerPeripheral = (params.baudrate > 0)

    local picogpio, pocigpio, clkgpio, opt_csgpio
    if self:targetUsesPinsets() then
      -- older targets require hard-coded pin-sets

      for _, p in ipairs(params.gpio) do
        req:add('GPIO', p)
        if self.pinset_string == nil then
          self.pinset_string = 'GPIO%i' % {p}
        else
          self.pinset_string = '%s_GPIO%i' % {self.pinset_string, p}
        end
      end

      local pinsetWithModule = '%s_%s' % {self.spiUnitAsChar, self.pinset_string}
      self.pinset =
        globals.target.getTargetParameters()['spis']['pin_sets'][pinsetWithModule]
      if self.pinset == nil then
        U.error('Pinset %s not supported for SPI %s.' %
                 {self.pinset_string, self.spiUnitAsChar} )
      end
    else
      -- newer targets have driverlib
      if self:targetMatches({'2837x'}) then
        picogpio = 'GPIO_%i_SPISIMO%s' % {params.gpio[1], self.spiUnitAsChar}
        pocigpio = 'GPIO_%i_SPISOMI%s' % {params.gpio[2], self.spiUnitAsChar}
        clkgpio  = 'GPIO_%i_SPICLK%s' % {params.gpio[3], self.spiUnitAsChar}
        if params.gpio[4] ~= nil then
          opt_csgpio = 'GPIO_%i_SPISTE%s' % {params.gpio[4], self.spiUnitAsChar}
        end
      elseif self:targetMatches({'28P55x', '28P65x', '29H85x'}) then
        picogpio = 'GPIO_%i_SPI%s_PICO' % {params.gpio[1], self.spiUnitAsChar}
        pocigpio = 'GPIO_%i_SPI%s_POCI' % {params.gpio[2], self.spiUnitAsChar}
        clkgpio  = 'GPIO_%i_SPI%s_CLK' % {params.gpio[3], self.spiUnitAsChar}
        if params.gpio[4] ~= nil then
          opt_csgpio = 'GPIO_%i_SPI%s_PTE' % {params.gpio[4], self.spiUnitAsChar}
        end
      elseif self:targetMatches({'28003x', '28004x', '280013x', '2838x', '28P55x', '28P65x'}) then
        picogpio = 'GPIO_%i_SPI%s_SIMO' % {params.gpio[1], self.spiUnitAsChar}
        pocigpio = 'GPIO_%i_SPI%s_SOMI' % {params.gpio[2], self.spiUnitAsChar}
        clkgpio  = 'GPIO_%i_SPI%s_CLK' % {params.gpio[3], self.spiUnitAsChar}
        if params.gpio[4] ~= nil then
          if self:targetMatches({'28003x', '28004x', '280013x'}) then
            opt_csgpio= 'GPIO_%i_SPI%s_STE' % {params.gpio[4], self.spiUnitAsChar}
          elseif self:targetMatches({'2838x', '28P55x', '28P65x'}) then
            opt_csgpio= 'GPIO_%i_SPI%s_STEN' % {params.gpio[4], self.spiUnitAsChar}
          else
            U.throwUnhandledTargetError()
          end
        end
      else
        U.throwUnhandledTargetError()
      end

      -- Check Alternate function for all GPIO at once:
      local gpioAfs = {picogpio, pocigpio, clkgpio, opt_csgpio}
      local errDetected = false
      local badAfs = {}
      for _, af in ipairs(gpioAfs) do
        if not globals.target.validateAlternateFunction(af) then
          errorDetected = true
          table.insert(badAfs, af)
        end
      end

      if errorDetected then
        local isPlural = #badAfs > 1
        table.insert(
          badAfs, 1, -- prepend header to the error message
          'Invalid @param:Gpios: configured for SPI block. %s not configurable for this function:'
          % {isPlural and 'These pins are' or 'This pin is'}) 
        U.error(table.concat(badAfs, '\n'))
      end

      globals.syscfg:addEntry('spi', {
        unitAsChar = self.spiUnitAsChar,
        pins = params.gpio,
        pinconf = gpioAfs,
        core = self.cpu + 1
      })

    end
  end

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

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

  function Spi:finalizeThis(c)
    local configCodeComment = U.CodeLines:new()
    local portSetupCode = U.CodeLines:new()

    if self.pinset ~= nil then
      configCodeComment:append('// configure SPI-%s for pinset %s' %
                              {self.spiUnitAsChar, self.pinset_string})
      portSetupCode:append('PLX_SPI_setupPortViaPinSet(SpiHandles[%d], %d, &params);' %
                          {self.instance, self.pinset})
    else
      configCodeComment:append('// configure SPI-%s' %
                              {self.spiUnitAsChar})
      portSetupCode:append('PLX_SPI_setupPort(SpiHandles[%d], &params);' %
                          {self.instance})
    end

    local m_clkSrc
    -- 29H85x does not have a low-speed clock!
    if self:targetMatches({'29H85x'}) then
      m_clkSrc = 'SYSCLK_HZ'
    elseif self:targetMatches({
        '2806x', '2833x',
        '2837x', '2838x',
        '28003x', '28004x',
        '280013x',
        '28P55x', '28P65x',
      }) then
      m_clkSrc = 'LSPCLK_HZ'
    else
      U.throwUnhandledTargetError()
    end

    c.PreInitCode:append([[
      %(configCodeComment)s
      {
        PLX_SPI_Params_t params;
        params.SPICHAR = %(charLen)d;
        params.CLKPOLARITY = %(polarity)d;
        params.CLKPHASE = %(phase)d;
        params.BAUDRATE = %(baudrate)d;
        params.CONTROLLERPERIPHERAL = %(controllerPeripheral)d;
        PLX_SPI_configure(SpiHandles[%(instance)d], PLX_SPI_SPI_%(spiUnitAsChar)s, %(m_clkSrc)s);
        %(portSetupCode)s
      }
    ]] % {
      configCodeComment = configCodeComment:concat(),
      charLen = self.charlen,
      polarity = self.pol,
      phase = self.phase,
      baudrate = self.baudrate,
      controllerPeripheral = self.controllerPeripheral,
      instance = self.instance,
      spiUnitAsChar = self.spiUnitAsChar,
      m_clkSrc = m_clkSrc,
      portSetupCode = portSetupCode:concat(),
    })
  end

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

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

    c.Declarations:append([[
      PLX_SPI_Handle_t SpiHandles[%(numInstances)d];
      PLX_SPI_Obj_t SpiObj[%(numInstances)d];

      uint16_t PLXHAL_SPI_getRxFifoLevel(int16_t aChannel){
        return PLX_SPI_getRxFifoLevel(SpiHandles[aChannel]);
      }
      
      bool PLXHAL_SPI_putWords(int16_t aChannel, uint16_t *aData, uint16_t aLen){
        return PLX_SPI_putWords(SpiHandles[aChannel], aData, aLen);
      }

      bool PLXHAL_SPI_getWords(int16_t aChannel, uint16_t *aData, uint16_t aLen){
        return PLX_SPI_getWords(SpiHandles[aChannel], aData, aLen);
      }
      
      bool PLXHAL_SPI_getAndResetRxOverrunFlag(int16_t aChannel){
        return PLX_SPI_getAndResetRxOverrunFlag(SpiHandles[aChannel]);
      }
    ]] % {
      numInstances = static[self.cpu].numInstances,
    })

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

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

    static[self.cpu].finalized = true
  end

  return Spi
end

return Module
