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

  function Qep:checkMaskParameters()

    self.gpios = U.enforceMask(U.is3Pins, 'Gpios')
  end

  function Qep:p_getDirectFeedthroughCode()
    local Require = ResourceList:new()
    local InitCode = U.CodeLines:new()

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

    self.qep = Block.Mask.QepModule[1]
    self.reset_on_index = false
    if Block.Mask.CounterResetMode == 2 then
      self.reset_on_index = true
    end

    Require:add('QEP', self.qep)
    for p in ipairs(Block.Mask.Gpios) do
      globals.target.allocateGpio(Block.Mask.Gpios[p], {}, Require)
    end

    self.pin_set_string = '[A=GPIO%i, B=GPIO%i, I=GPIO%i]' %
        { Block.Mask.Gpios[1], Block.Mask.Gpios[2], Block.Mask.Gpios[3] }

    if self:targetUsesPinsets() then
      -- older targets require hard-coded pin-sets
      -- check the user config is valid for this target

      local possiblePins = globals.target.getTargetParameters()['qeps']['pin_set_groups']

      if possiblePins == nil then
        U.error('Qep support not configured.')
      end

      local unit_pins = possiblePins['_%i' % { self.qep }]

      -- return error if desired a, b and i are not valid options
      if not (U.sequenceContains(unit_pins.a, Block.Mask.Gpios[1]) and
            U.sequenceContains(unit_pins.b, Block.Mask.Gpios[2]) and
            U.sequenceContains(unit_pins.i, Block.Mask.Gpios[3])) then
        U.error('Pinset %s not supported for QEP%d.' %
          { self.pin_set_string, self.qep })
      end

      self.configureWithPinsArgs = {
        instance = self.instance,
        unit = self.qep,
        pin_a = Block.Mask.Gpios[1],
        pin_b = Block.Mask.Gpios[2],
        pin_i = Block.Mask.Gpios[3],

      }
    else
      -- newer targets have driverlib
      self.abi = Block.Mask.Gpios
      if self:targetMatches({'2806x', '2833x', '2837x', '2838x', '28003x', '28004x', '280013x', '28P55x', '28P65x'}) then
        local chagpio, chbgpio, chigpio
        if self:targetMatches({'2837x'}) then
          chagpio = 'GPIO_%i_EQEP%iA' % { self.abi[1], self.qep }
          chbgpio = 'GPIO_%i_EQEP%iB' % { self.abi[2], self.qep }
          chigpio = 'GPIO_%i_EQEP%iI' % { self.abi[3], self.qep }
        else
          chagpio = 'GPIO_%i_EQEP%i_A' % { self.abi[1], self.qep }
          chbgpio = 'GPIO_%i_EQEP%i_B' % { self.abi[2], self.qep }
          chigpio = 'GPIO_%i_EQEP%i_INDEX' % { self.abi[3], self.qep }
        end
        if (not globals.target.validateAlternateFunction(chagpio)) or
            (not globals.target.validateAlternateFunction(chbgpio)) or
            (not globals.target.validateAlternateFunction(chigpio)) then
          U.error('Invalid GPIO configured for QEP block.')
        end
        globals.syscfg:addEntry('qep', {
          unit = self.qep,
          pins = self.abi,
          pinconf = { chagpio, chbgpio, chigpio },
          core = self.cpu + 1
        })
      elseif self:targetMatches({'29H85x'}) then
        globals.syscfg:addEntry('input_xbar', {
          gpio = self.abi[1],
          input = 33+4*(self.qep-1),
          core = self.cpu + 1
        })
        globals.syscfg:addEntry('input_xbar', {
          gpio = self.abi[2],
          input = 34+4*(self.qep-1),
          core = self.cpu + 1
        })
        globals.syscfg:addEntry('input_xbar', {
          gpio = self.abi[3],
          input = 35+4*(self.qep-1),
          core = self.cpu + 1
        })
      else
        U.throwUnhandledTargetError()
      end
    end

    self.prd = Block.Mask.CounterMax

    local OutputSignal1 = StringList:new()
    OutputSignal1:append('PLXHAL_QEP_getCounter(%i)' % { self.instance })
    local OutputSignal2 = StringList:new()
    OutputSignal2:append('PLXHAL_QEP_getIndexLatchCounter(%i)' % { self.instance })
    local OutputSignal3 = StringList:new()
    OutputSignal3:append('PLXHAL_QEP_getAndCearIndexFlag(%i)' % { self.instance })

    return {
      InitCode = InitCode,
      OutputSignal = { OutputSignal1, OutputSignal2, OutputSignal3 },
      Require = Require,
      UserData = { bid = Qep:getId() }
    }
  end

  function Qep:finalizeThis(c)
    if self.pin_set_string ~= nil then
      c.PreInitCode:append(' // configure QEP%i for pinset %s' %
        { self.qep, self.pin_set_string })
    else
      c.PreInitCode:append(' // configure QEP%i' %
        { self.qep })
    end
    c.PreInitCode:append('{')
    c.PreInitCode:append('  PLX_QEP_Params_t params;')
    c.PreInitCode:append('  PLX_QEP_setDefaultParams(&params);')
    c.PreInitCode:append('  params.QPOSMAX = %i;' % { self.prd })
    if self.reset_on_index then
      c.PreInitCode:append(
        '  params.QEPCTL.bit.PCRM = 0; // operate QEP with reset on index event')
    else
      c.PreInitCode:append(
        '  params.QEPCTL.bit.PCRM = 1; // operate QEP in reset on max counter mode')
    end
    if self:targetUsesDriverLib() then
      -- for driver lib, pins are configured in gpio.c
      c.PreInitCode:append('PLX_QEP_configure(QepHandles[%(instance)i], %(unit)i, &params);' % {
        instance = self.instance,
        unit = self.qep,
      })
    else -- pinsets
      c.PreInitCode:append(
        'PLX_QEP_configureWithPins(QepHandles[%(instance)i], %(unit)i, %(pin_a)i, %(pin_b)i, %(pin_i)i, &params);' %
        self.configureWithPinsArgs)
    end
    c.PreInitCode:append('}\n')
  end

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

    c.Include:append('plx_qep.h')
    c.Declarations:append('PLX_QEP_Handle_t QepHandles[%i];' %
      { static[self.cpu].numInstances })
    c.Declarations:append('PLX_QEP_Obj_t QepObj[%i];' % { static[self.cpu].numInstances })

    c.Declarations:append('uint32_t PLXHAL_QEP_getCounter(uint16_t aChannel){')
    c.Declarations:append('  return PLX_QEP_getPosCnt(QepHandles[aChannel]);')
    c.Declarations:append('}')

    c.Declarations:append(
      'bool PLXHAL_QEP_getAndCearIndexFlag(uint16_t aChannel){')
    c.Declarations:append(
      '  return PLX_QEP_getAndClearIndexFlag(QepHandles[aChannel]);')
    c.Declarations:append('}')

    c.Declarations:append(
      'uint32_t PLXHAL_QEP_getIndexLatchCounter(uint16_t aChannel){')
    c.Declarations:append(
      '  return PLX_QEP_getPosILatchCnt(QepHandles[aChannel]);')
    c.Declarations:append('}')

    local code = [[
      PLX_QEP_sinit();
      for (int i = 0; i < %d; i++) {
        QepHandles[i] = PLX_QEP_init(&QepObj[i], sizeof(QepObj[i]));
      }
    ]]
    c.PreInitCode:append(code % { static[self.cpu].numInstances })

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

    static[self.cpu].finalized = true
  end

  return Qep
end

return Module
