--[[
  Copyright (c) 2021, 2024 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 U = require('common.utils')
local VREF = require('blocks.vref_config')

local Module = {}

local static = {}

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


  function Dac:checkMaskParameters()

    self.dacChar = U.comboAsChar(Block.Mask.DacUnit)

    -- checks in block mask
    self.scale = Block.Mask.Scale
    self.offset = Block.Mask.Offset

    -- update min and max based on target configuration and optional mask parameters
    local dacParams = globals.target.getTargetParameters().dacs
  
    if not dacParams
    or not dacParams.minOutputVoltage
    or not dacParams.maxOutputVoltage then
      U.error('Buffered DAC peripheral is not available for this target device.')
    end

    self.min = U.enforceMask(U.isScalarInClosedInterval,
                             'MinOutput',
                             dacParams.minOutputVoltage,
                             dacParams.maxOutputVoltage, 'V')
    self.max = U.enforceMask(U.isScalarInClosedInterval,
                             'MaxOutput',
                             dacParams.minOutputVoltage,
                             dacParams.maxOutputVoltage, 'V')
  
    if self.min > self.max then
      U.error(
        'Invalid configuration: %(min)s must not be greater than %(max)s.'
        % {
          min = U.paramLink('MinOutput', true),
          max = U.paramLink('MaxOutput', true),
        })
    end

    local dim = #Block.InputSignal[1]
    if dim ~= 1 then
      U.error('Expecting scalar input.')  -- this check triggers (not redundant)
    end
    self.inputSignal = Block.InputSignal[1][1]
  end

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

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

    -- Check for this DAC before requiring, because the voltage ref code will 
    -- error before the resource manager if it doesn't exist on this target.
    local resourceName = 'DAC %s' % {self.dacChar}
    if not U.resourceExists(globals.target, resourceName) then
      U.error('This target does not provide @param:DacUnit: "%s".' % {resourceName})
    end
    Require:add(resourceName)

    OutputCode:append([[
      PLXHAL_DAC_set(%(instance)d, %(inputSignal)s);
      ]]
      % {
        instance = self.instance,
        inputSignal = self.inputSignal,
      })

    globals.syscfg:addEntry('dac', {
      unitAsChar = self.dacChar,
      core = self.cpu + 1
    })

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

  function Dac:finalizeThis(c)
    -- the DAC Voltage Reference
    local vrefConfig = VREF.getDacVref(globals, self.dacChar)

    c.PreInitCode:append([[
      // configure DAC%(dacChar)s
        PLX_DAC_configure(DacHandles[%(instance)d], PLX_DAC_%(dacChar)s, %(useExtVref)d, %(vref).9ef);
        PLX_DAC_configureScaling(DacHandles[%(instance)d], %(scale).9ef, %(offset).9ef, %(min).9ef, %(max).9ef);
      ]]
      % {
        dacChar = self.dacChar,
        instance = self.instance,
        useExtVref = vrefConfig.useExternalVref,
        vref = vrefConfig.vref,
        scale = self.scale,
        offset = self.offset,
        min = self.min,
        max = self.max,
      })
  end

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

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

    c.Declarations:append([[
      PLX_DAC_Handle_t DacHandles[%(numInstances)d];
      PLX_DAC_Obj_t DacObj[%(numInstances)d];

      void PLXHAL_DAC_set(uint16_t aHandle, float aValue){
        PLX_DAC_setValF(DacHandles[aHandle], aValue);
      } ]]
      % {numInstances = static[self.cpu].numInstances})

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

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

    static[self.cpu].finalized = true
  end

  return Dac
end

return Module
