--[[
  Copyright (c) 2021 - 2026 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.
--]]

-- This function is a hack to redirect the target include
-- for deprecated targets, thus it is listed before the requires.
local function targetDotLuaFilename(targetName)
  -- override deprecated targets here
  return (Target.Name == 'TI2837xS') and 'TI2837x' or targetName
end

local CLA = require('coderGenClaCode')
local debug = require('debug')
local HAL = require('common.coderGenHalCode')
local T = require('targets.'..targetDotLuaFilename(Target.Name))
local U = require('common.utils')
local VersionNumber = require('common.version')
local ExtTools = require('common.external_tools')

local Coder = require('common.coder_common')
local Registry = Coder.Registry

-- Now reading these version numbers out of Common.yanl via versions.lua
local versions = require('versions')
local MIN_CGT_VER = versions.MIN_CGT_VER
local MIN_CGT_VER_29 = versions.MIN_CGT_VER_29
local MAX_SUPPORTED_CGT_VER = versions.MAX_SUPPORTED_CGT_VER
local MAX_SUPPORTED_CGT_VER_C29 = versions.MAX_SUPPORTED_CGT_VER_C29
local MIN_UNIFLASH_VER = versions.MIN_UNIFLASH_VER
local minimumUniflashVersion =
   VersionNumber.new(MIN_UNIFLASH_VER)
   or error('Error, invalid MIN_UNIFLASH_VER')



local BUILD_TYPE_GEN_CCS = 1
local BUILD_TYPE_BUILD_FLASH = 2
local BUILD_TYPE_BUILD_ONLY = 3

local MINIMUM_SAMPLE_TIME = 5e-6
local EXTMODE_SAMPLE_TIME_WARNING = 1e-5

local FactoryBlock = Coder.CreateFactoryBlock(T)

function Coder.getTargetFamilyForOldCreateTargetBlockCheck()
  return 'TI C2000'
end

function Coder.GetInstallDir()
  if Target.Variables.genOnly == BUILD_TYPE_GEN_CCS then
    return U.enforceValidCoderOptionsDirectory(
      'installDir',
      U.getDisallowedPathCharsForInstallDir()
    )
  else
    return U.normalizePath(Target.Variables.BUILD_ROOT)
  end
end

-- Map Target.Name to ccs/ directory
local targetDirDict = U.lockTable({
  TI28004x = '28004x',
  TI2806x = '2806x',
  TI2833x = '2833x',
  TI2837xS = '2837x', -- Need a table to map 7xS to 7x
  TI2837x = '2837x',
  TI2838x = '2838x',
  TI28003x = '28003x',
  TI280013x = '280013x',
  TI28P65x = '28p65x',
  TI28P55x = '28p55x',
  TI29H85x = '29h85x',
})

-- Example familySrcDir = ccs/2837x
function Coder.GetFamilySrcDir()
  local familySrcDir = 
     ExtTools.assertValidTARGET_ROOT()..'/ccs/'..targetDirDict[Target.Name]
  return familySrcDir
end

function Coder.p_Initialize()
  local ci_modelHooksRecipe = {
    -- This table is populated in Coder.Initialize and holds all values
    -- returned by this function.
    Resources = ResourceList:new(),
    Require = ResourceList:new(),
    HeaderTypedefs = U.CodeLines:new(),
    CommonTypedefs = U.CodeLines:new(),
    HeaderDeclarations = U.CodeLines:new(),
    CommonDeclarations = nil,  -- for now, consider this deprecated
    Declarations = U.CodeLines:new(),
  }

  if U.isEmptyTable(Model.TaskConfiguration) then
    -- tasking mode is single-tasking
    table.insert(Registry.UsedCpus, 0)
  end
  for _, task in ipairs(Model.TaskConfiguration) do
    if not U.sequenceContains(Registry.UsedCpus, task.Cpu) then
      -- insert all used CPUs in a list
      table.insert(Registry.UsedCpus, task.Cpu)
    end
  end

  -- detect if the model is configured for multi cpus
  local hasMultiCpuConfig = false
  if Target.Variables.NUM_CPUS > 1 then
    hasMultiCpuConfig = true
  end

  local typeDeclarations = ''

  if T.is28Target() then
    typeDeclarations = [[
      typedef int_fast8_t int8_t;
      typedef uint_fast8_t uint8_t;
    ]]
  end

  if hasMultiCpuConfig then
    ci_modelHooksRecipe.CommonTypedefs:append(typeDeclarations)
  else
    ci_modelHooksRecipe.HeaderTypedefs:append(typeDeclarations)
  end

  ci_modelHooksRecipe.HeaderDeclarations:append(
    'extern void %s_background(void);' % {Target.Variables.BASE_NAME})
    
  -- do this before using any T methods
  T.configure(ci_modelHooksRecipe.Resources)
  U.initTargetInfo(T)

  for _, cpu in ipairs(Registry.UsedCpus) do
    -- (implicit) clock module - MUST COME FIRST!
    local clockBlock = FactoryBlock:makeBlock('clock', cpu)
    clockBlock:createImplicitClock()

    -- (implicit) external mode module
    if type(Target.Variables.EXTERNAL_MODE) == 'number' then
      local extModeBlock = FactoryBlock:makeBlock('extmode', cpu)
      extModeBlock:createImplicitExtMode(ci_modelHooksRecipe.Require)
    end

    -- (implicit) CPU2 management
    if hasMultiCpuConfig == true then
      local cpu2 = FactoryBlock:makeBlock('cpu2', cpu)
      cpu2:createImplicitCpu2(ci_modelHooksRecipe.Require)
    end
  end

  -- (implicit) system configuration,
  if T.targetMatches({'2806x', '2833x', '2837x', '2838x', '28003x', '28004x', '280013x', '28P55x', '28P65x'}) then
      FactoryBlock:makeBlock('syscfg', 0)
  elseif T.targetMatches({'29H85x'}) then
    for _, cpu in ipairs(Registry.UsedCpus) do
      FactoryBlock:makeBlock('syscfg', cpu)
    end
  else
    U.throwUnhandledTargetError()
  end

  Coder.setupPILSimulation(ci_modelHooksRecipe)

  -- PGAs
  if T.getTargetParameters()['pgas'] ~= nil then
    local gains = {3, 6, 12, 24};
    local rfs = {0, 200, 160, 130, 100, 80, 50}
    for i = 1, T.getTargetParameters()['pgas'].num_units do
      if Target.Variables['pga%iEn' % {i}] == 1 then
        local pga = FactoryBlock:makeBlock('pga', 0)
        pga:createImplicitPga(i, {
          gain = gains[Target.Variables['pga%iGain' % {i}]],
          rf = rfs[Target.Variables['pga%iRf' % {i}]]
        })
      end
    end
  end

  -- (implicit) trip zone management module
  local tzs = FactoryBlock:makeBlock('tripzones', 0)
  tzs:createImplicitTripZones(ci_modelHooksRecipe.Require)

  -- (implicit) analog subsystem configurations
  local analog = FactoryBlock:makeBlock('analog', 0)
  analog:createImplicitAnalog(ci_modelHooksRecipe.Require)

  return ci_modelHooksRecipe
end

function Coder.p_Finalize()
  -- This table is returned by this function and used by plecs to generate hooks into
  -- the HAL code from <model_name>.c file. Note code added here must be terminated by newlines
  -- These entries can contain a dictionary that allows to specify the specific code for each CPU
  local cf_modelHooksRecipe = {
    HeaderDeclarations = {},
    Include = {},
    Require = ResourceList:new(),  -- only one require list per target
    Declarations = {},
    PreInitCode = {},
    PostInitCode = {},
    TerminateCode = {},
  }

  local blockInstancesByType = {}

  -- detect if the model is configured for multi cpus
  local hasMultiCpuConfig = false
  if Target.Variables.NUM_CPUS > 1 then
    hasMultiCpuConfig = true
  end

  -- determine final destination for generated files
  local installDir = Coder.GetInstallDir()

  -- parse user-supplied serial number for target device, if there is one
  local serialNumSelectXML = ''
  if Target.Variables.SerialNumberSpecified == 1 then
    -- this chunk of XML can be inserted into the target ccxml file if we need to
    -- program a device based on a specific serial number
    serialNumSelectXML = [[
    <property Type="choicelist" Value="1" id="Debug Probe Selection">
      <choice Name="Select by serial number" value="0">
        <property Type="stringfield" Value="|>SERIAL_NUM<|" id="-- Enter the serial number"/>
      </choice>
    </property>]]

    serialNumSelectXML = serialNumSelectXML:gsub('|>SERIAL_NUM<|',
                                                 Target.Variables.SerialNumber)
  end

  -- generate ipc transfer code
  if hasMultiCpuConfig then
    local tasks = Model.Tasks
    for _, transfer in ipairs(Model.IpcTransfers) do
      local srcCpu = Model.Tasks[transfer.SrcTaskId + 1].Cpu
      local dstCpu = Model.Tasks[transfer.DstTaskId + 1].Cpu
      local dataType = transfer.DataType

      local ipc_tx = FactoryBlock:makeBlock('ipc_transmit', srcCpu)
      ipc_tx:createImplicitIpcTx({
        datatype = dataType,
        index = transfer.SrcIndex,
      })

      local ipc_rx = FactoryBlock:makeBlock('ipc_receive', dstCpu)
      ipc_rx:createImplicitIpcRx({
        datatype = dataType,
        index = transfer.DstIndex,
      })
    end
  end

  -- iterate over all configured cpus
  for _, cpu in ipairs(Registry.UsedCpus) do
    -- create dictionary for return values
    cf_modelHooksRecipe.HeaderDeclarations[tostring(cpu)] = U.CodeLines:new()
    cf_modelHooksRecipe.Include[tostring(cpu)] = StringList:new()
    cf_modelHooksRecipe.Declarations[tostring(cpu)] = U.CodeLines:new()
    cf_modelHooksRecipe.PreInitCode[tostring(cpu)] = U.CodeLines:new()
    cf_modelHooksRecipe.PostInitCode[tostring(cpu)] = U.CodeLines:new()
    cf_modelHooksRecipe.TerminateCode[tostring(cpu)] = U.CodeLines:new()

    U.log('Triggering algorithm for CPU %d\n' % {cpu})
    -- categorize cpu local block instances by type
    blockInstancesByType[cpu] = {}

    for _, b in ipairs(Registry.BlockInstances) do
      if b:getCpu() == cpu then
        if blockInstancesByType[cpu][b:getType()] == nil then
          blockInstancesByType[cpu][b:getType()] = {}
        end
        table.insert(blockInstancesByType[cpu][b:getType()], b)
      end
    end
    -- first make sure that an adequate base-task trigger timer is present
    -- take sample rate of each base task (CpuTaskId is 0)
    -- additionally, verify requested sample times are within hard limits
    local cpuSampleTime

    for _, tsk in ipairs(Model.Tasks) do
      if tsk.Cpu == cpu then
        if tsk.CpuTaskId == 0 then
          cpuSampleTime = tsk.SampleTime[1]
        end
      end
      -- Even simplest of models overwhelmingly likely to not work
      -- C2000 fastest sample time possible: 5e-6
      -- Fastest sample time with minimum clock (50 MHz): 9e-6
      -- Equation calculates assuming a linear relationship
      if tsk.SampleTime[1] < (5e-6 + ((200e6 - T.getCleanSysClkHz()) * 8e-14 / 3)) then
        U.error('Sample time for task %d on CPU %d too small - raise sample time or system clock rate.' % {tsk.CpuTaskId, cpu})
      end
      -- External mode may experience issues
      if tsk.SampleTime[1] < EXTMODE_SAMPLE_TIME_WARNING then
        U.warning('Running at exceedingly fast task rates may affect external mode operation.')
      end
    end

    local triggerOriginBlock, triggerOriginAbsFreqError
    for _, b in ipairs(Registry.BlockInstances) do
      -- only consider blocks on the local CPU as valid implicit trigger sources
      if b:getCpu() == cpu then
        local ts = b:requestImplicitTrigger(cpuSampleTime)
        if (ts ~= nil) and (ts > 0) then
          local freqError = math.abs(1 / ts - 1 / cpuSampleTime)
          if (triggerOriginBlock == nil) or (freqError < triggerOriginAbsFreqError) then
            -- (new) best fit
            triggerOriginBlock = b
            triggerOriginAbsFreqError = freqError
            U.log(
              'New best fit for default triggering block: bid=%d, Ts=%e, Ferror=%e\n' %
              {b:getId(), ts, freqError})
          end
        end
      end
    end
    -- Only create implicit CPUTIMER if no block on the local CPU can provide a task trigger
    -- that is inside the defined step size tolerance band.
    local relTol = 1e-6
    if Target.Variables.taskFreqTol == 2 then
      relTol = Target.Variables.SampleTimeRelTol / 100
    end
    local absTsError
    local absTsTol = relTol * cpuSampleTime
    if triggerOriginBlock ~= nil then
      local ts = triggerOriginBlock:requestImplicitTrigger(cpuSampleTime)
      absTsError = math.abs(ts - cpuSampleTime)
    end

    if (triggerOriginBlock == nil) or (absTsError > absTsTol) then
      -- No implicit trigger source found on the local cpu. The actual trigger may still be on the remote cpu.
      U.log(
        '- No (perfectly) suitable triggering block found. Attempt creating implicit timer.\n')
      local timerCandidate = FactoryBlock:makeBlock('timer', cpu)

      local createdImplicitTimer = false
      if (blockInstancesByType[cpu]['tasktrigger']) then
        -- if there is a control task trigger block, errors in createImplicit
        -- imply the trigger chain is not properly configured. Suppress
        -- errors here and defer to the more detailed error message below
        createdImplicitTimer, _ = pcall(function ()
          timerCandidate
             :createImplicitTimer({f = 1 / cpuSampleTime})
        end)
      else
        -- if there is no control task trigger block, allow createImplicit
        -- to throw an error, since all timers are used for other functionality.
        timerCandidate:createImplicitTimer({f = 1 / cpuSampleTime})
        createdImplicitTimer = true
      end

      if createdImplicitTimer then
        local ts = timerCandidate:requestImplicitTrigger(cpuSampleTime)
        local freqError = math.abs(1 / ts - 1 / cpuSampleTime)
        if (triggerOriginBlock == nil) or (freqError < triggerOriginAbsFreqError) then
          -- new timer is better fit
          U.log('- Retaining new implicit timer (Ferror=%e).\n' % {freqError})
          if blockInstancesByType[cpu][timerCandidate:getType()] == nil then
            blockInstancesByType[cpu][timerCandidate:getType()] = {}
          end
          table.insert(blockInstancesByType[cpu][timerCandidate:getType()],
                       timerCandidate)
          triggerOriginBlock = timerCandidate
        end
      end
    end

    if triggerOriginBlock == nil then
      U.error('Unable to allocate implicit model trigger.')
    end

    -- provide implicit base trigger to blocks that might need it
    for _, b in ipairs(Registry.BlockInstances) do
      if b:getCpu() == cpu then
        b:setImplicitTriggerSource(triggerOriginBlock:getId())
      end
    end

    -- inform all trigger sources about the sinks attached to them
    for _, b in ipairs(Registry.BlockInstances) do
      if b:getCpu() == cpu then
        b:setSinkForTriggerSource()
      end
    end

    -- propagate all trigger connections
    for _, b in ipairs(Registry.BlockInstances) do
      b:propagateTriggerSampleTime()
    end

    -- make sure model has a task trigger, if not create implicit
    if blockInstancesByType[cpu]['tasktrigger'] == nil then
      -- create an implicit trigger
      local taskTrigger = FactoryBlock:makeBlock('tasktrigger', cpu)

      blockInstancesByType[cpu]['tasktrigger'] = {}
      table.insert(blockInstancesByType[cpu]['tasktrigger'], taskTrigger)

      -- connect to trigger block
      local blockForTaskTrigger
      -- connect to ADC, if possible
      if blockInstancesByType[cpu]['adc'] ~= nil then
        local candidates = {}
        -- find ADCs at correct sample time
        for _, adc in ipairs(blockInstancesByType[cpu]['adc']) do
          if adc:getTriggerSampleTime() == nil then
            U.error('ADC %i has undefined trigger time' % {adc:getId()})
          end
          U.log('- ADC %i detected at ts=%f.\n' %
            {adc:getId(), adc:getTriggerSampleTime()})
          local relTol = 1e-6
          if Target.Variables.taskFreqTol == 2 then
            relTol = Target.Variables.SampleTimeRelTol / 100
          end
          local absTsError
          local absTsTol = relTol * cpuSampleTime
          if adc ~= nil then
            local ts = adc:getTriggerSampleTime()
            absTsError = math.abs(ts - cpuSampleTime)
          end
          if (absTsError <= absTsTol) then
            table.insert(candidates, adc)
          end
        end
        if #candidates ~= 0 then
          -- find ADC that takes the longest to complete conversions
          local maxConversionTime = -1
          for _, adc in ipairs(candidates) do
            if maxConversionTime < adc:getTotalConversionTime() then
              maxConversionTime = adc:getTotalConversionTime()
              blockForTaskTrigger = adc
            end
          end
        end
      end

      if blockForTaskTrigger == nil then
        -- no suitable ADC found, use timer
        blockForTaskTrigger = triggerOriginBlock
      end

      taskTrigger:setImplicitTriggerSource(blockForTaskTrigger:getId())
      local sink = {
        type = 'modtrig',
        bid = taskTrigger:getId()
      }
      blockForTaskTrigger:setSinkForTriggerSource(sink)
      for _, b in ipairs(Registry.BlockInstances) do
        local f = b:propagateTriggerSampleTime()
      end
    end
    -- final task trigger check
    if blockInstancesByType[cpu]['tasktrigger'] == nil then
      U.error('Exception: Model does not have a task trigger.')
    else
      local ts = blockInstancesByType[cpu]['tasktrigger'][1]
         :getTriggerSampleTime('modtrig')
      if ts == nil then
        U.error('Exception: Task trigger does not have a defined sample time.')
      else
        local opt_stepSizeTolerance
        if U.comboEnabled(Target.Variables.taskFreqTol) then
          opt_stepSizeTolerance = {
            relTolPercent = U.enforceCO(U.isNonNegativeScalar, 'SampleTimeRelTol'),
          }
        end

        U.enforceStepSize({
          opt_stepSizeTolerance = opt_stepSizeTolerance,
          stepDesired = cpuSampleTime,
          stepAchievable = ts,
          opt_cpuNum = hasMultiCpuConfig and T.getTiCpuNumber(cpu) or nil,
          stepTolWidgetLink = '@param:taskFreqTol:2:',
          stepTolBandWidgetLink = '@param:SampleTimeRelTol:2:',
        })
      end
    end
  end

  -- This table will be populated with data/code to generate
  -- the <model_name>_hal.c (and cla) files. Note code added here is
  -- automatically terminated with newlines.
  local halCodeGenRecipe = {}
  for _, cpu in ipairs(Registry.UsedCpus) do
    halCodeGenRecipe[cpu] = {
      HeaderDeclarations = U.CodeLines:new(),
      Include = StringList:new(),
      Require = ResourceList:new(),
      Declarations = U.CodeLines:new(),
      PreInitCode = U.CodeLines:new(),
      PostInitCode = U.CodeLines:new(),
      TerminateCode = U.CodeLines:new(),

      InterruptEnableCode = U.CodeLines:new(),
      TimerSyncCode = U.CodeLines:new(),
      BackgroundTaskCodeBlocks = U.CodeLines:new(),
      PilHeaderDeclarations = U.CodeLines:new(),
      BootSecondaryCode = U.CodeLines:new(),

      -- CLA uses these tables
      ClaDeclarations = U.CodeLines:new(),
      ClaCode = U.CodeLines:new(),
    }
    halCodeGenRecipe[cpu].Include:append('plx_hal.h')
    halCodeGenRecipe[cpu].Include:append('plx_dispatcher.h')
  end

  -- prefinalize all blocks
  -- For blocks that must be configured before any code/data is populated
  -- in the halCodeGenRecipe table. preFinalize does not have access to
  -- the code table and is only for configuration purposes.
  for _, b in ipairs(Registry.BlockInstances) do
    b:preFinalize()
  end

  -- finalize all blocks
  for _, b in ipairs(Registry.BlockInstances) do
    b:finalize(halCodeGenRecipe[b:getCpu()])
  end

  -- generate pre-init code for external libraries
  for _, cpu in ipairs(Registry.UsedCpus) do
    if Registry.ExternalLibraries[cpu] == nil then
      Registry.ExternalLibraries[cpu] = {}
    end
    for name, par in pairs(Registry.ExternalLibraries[cpu]) do
      if par.PreInitCode ~= nil then
        halCodeGenRecipe[cpu].PreInitCode:append(
          '// initialization of %s' % {par.LibFileName})
        for _, v in ipairs(par.PreInitCode) do
          halCodeGenRecipe[cpu].PreInitCode:append(v)
        end
      end
    end
    -- create PIL structure
    for _, v in ipairs(halCodeGenRecipe[cpu].PilHeaderDeclarations) do
      cf_modelHooksRecipe.HeaderDeclarations[tostring(cpu)]:append(v..'\n')
    end
  end

  U.log('\nBlock coding complete.\n\n')
  U.log('Blocks in model: %s\n' % {dump(Registry.BlockInstances)})
  U.log('\n\n')
  U.log('Target settings: %s\n' % {dump(Target.Variables)})

  for _, cpu in ipairs(Registry.UsedCpus) do
    local multiCpuPostFix = ''
    if hasMultiCpuConfig then
      multiCpuPostFix = '_CPU%d' % {cpu}
    end
    cf_modelHooksRecipe.Include[tostring(cpu)]:append('plx_hal.h')

    -- It is completely valid for a user to leave some blocks outputs
    -- unpopulated. However, this can lead to helper variables from blocks
    -- which generate a compiler warning for an unused variable.
    -- These pragma silence those warnings.
    cf_modelHooksRecipe.HeaderDeclarations[tostring(cpu)]:append('#pragma diag_suppress 552')
    cf_modelHooksRecipe.TerminateCode[tostring(cpu)]:append('#pragma diag_default 552\n')
    cf_modelHooksRecipe.Declarations[tostring(cpu)]:append([[
      // tag step function to allow special linking
#pragma CODE_SECTION(%(base_name)s%(cpu_postfix)s_step, "step")
      extern void %(base_name)s%(cpu_postfix)s_initHal();
    ]] % {base_name = Target.Variables.BASE_NAME, cpu_postfix = multiCpuPostFix})

    cf_modelHooksRecipe.PreInitCode[tostring(cpu)]:append('%s%s_initHal();\n' %
      {Target.Variables.BASE_NAME, multiCpuPostFix})

    HAL.generateHalCode(
      '%s/%s%s_hal.c' % {
        Target.Variables.BUILD_ROOT,
        Target.Variables.BASE_NAME,
        multiCpuPostFix
      }, halCodeGenRecipe[cpu], {
        isMultiCpuProject = Target.Variables.NUM_CPUS > 1,
        cpu = cpu,
      })

    -- create CLA code file
    if T.getTargetParameters()['clas'] ~= nil then
      CLA.generateClaCode(
        '%s/%s%s_cla.cla' % {
          Target.Variables.BUILD_ROOT,
          Target.Variables.BASE_NAME,
          multiCpuPostFix
        }, halCodeGenRecipe[cpu])
    end

    -- forward required resources
    for _, v in ipairs(halCodeGenRecipe[cpu].Require) do
      cf_modelHooksRecipe.Require:add(v[1], tonumber(v[2]), v[3])
    end

    
    -- TSP version number represented in C as a hex number:
    -- For example: Version '1.8' >> #define TSP_VER 0x0108
    local tspVerDef = '#undef TSP_VER'
    local tspVersion = VersionNumber.new(Target.Version) -- from info.xml (after release only, might be better to read something always present?)
    if tspVersion then
      tspVerDef = '#define TSP_VER 0x%s' % {tspVersion:majorMinorAs4DigitString()}
    end

    -- process templates
    local templateDict = {}
    templateDict['|>TARGET_NAME<|'] = Target.Name
    templateDict['|>BASE_NAME<|'] = Target.Variables.BASE_NAME
    templateDict['|>BUILD_ROOT<|'] = U.normalizePath(Target.Variables.BUILD_ROOT)
    
    templateDict['|>TARGET_ROOT<|'] = Target.Variables.TARGET_ROOT
    templateDict['|>SHARED_MAKE_FUNCTIONS<|'] = ExtTools.SHARED_MAKE_FUNCTIONS
    templateDict['|>TSP_VER_DEF<|'] = tspVerDef
    templateDict['|>DATA_TYPE<|'] = Target.Variables.FLOAT_TYPE
    templateDict['|>TSP_VERSION<|'] = Target.Version
    templateDict['|>MIN_CGT_VER<|'] = MIN_CGT_VER
    templateDict['|>SERIALNUM_SELECT_XML<|'] = serialNumSelectXML
    templateDict['|>NUM_CPUS<|'] = math.tointeger(Target.Variables.NUM_CPUS)

    if Target.Variables.genOnly ~= BUILD_TYPE_GEN_CCS and Target.Variables.buildConfig ~= 2 then
      templateDict['|>FLASH_FLAG<|'] = '#define _FLASH'
    else
      templateDict['|>FLASH_FLAG<|'] = ''
    end

    if Registry.syscfgInterface:getPeripheralEntries('csv') then
      templateDict['|>STACK_SIZE<|'] = '0xA00'
    else
      templateDict['|>STACK_SIZE<|'] = '0x300'
    end

    local stackMonCode = ''
    local stackMonConfig = Registry.syscfgInterface:getPeripheralEntries('stackMon')
    if stackMonConfig then
      for _, instance in ipairs(stackMonConfig) do
        if instance.cpu == cpu then
          stackMonCode = 'PLXHAL_StackMon_paintStack();'
        end
      end
    end
    templateDict['|>STACK_MON<|'] = stackMonCode

    local familySrcDir = Coder.GetFamilySrcDir()
 
    templateDict['|>SRC_ROOT<|'] = familySrcDir

    local corePostfix =
    ''                       -- single core targets don't define targetCore and use no postfix
    local coreNum = ''
    local coreFlashOpt = ''  -- only second core uses --core option for flash
    if hasMultiCpuConfig or T.targetMatches({'2837x', '2838x', '28P65x', '29H85x'}) then
      local tiCpuNum = T.getTiCpuNumber(cpu)
      coreNum = '%d' % {tiCpuNum}
      corePostfix = '_cpu%d' % {tiCpuNum}
      if T.isSecondaryCpu(tiCpuNum) then
        coreFlashOpt = '--core=%d' % {tiCpuNum}
      end
    end
    templateDict['|>CORE_POST_FIX>|'] = corePostfix
    templateDict['|>CPU_NUM<|'] = coreNum
    templateDict['|>CORE_FLASH_OPT<|'] = coreFlashOpt
    if hasMultiCpuConfig then
      templateDict['|>CPU_POSTFIX<|'] = '_CPU%d' % {cpu}
    else
      templateDict['|>CPU_POSTFIX<|'] = ''
    end

    local coreTemplatesDir = familySrcDir..'/templates'
    local coreAppDir = familySrcDir..'/app'..corePostfix

    if Target.Variables.genOnly ~= BUILD_TYPE_GEN_CCS then
      --[[
          "One-click" build and flash
      --]]

      -- we only make this check for the one-click build to allow
      -- flexibility when building from CCS
      Coder.assertFLOAT_TYPEIsFloat()

      templateDict['|>INSTALL_DIR<|'] = './'
      templateDict['|>BIN_DIR<|'] = './output_%s%s' % {Target.Name, corePostfix}
      
      templateDict['|>CPU2_BIN_DIR<|'] = './output_%s%s' % {Target.Name, '_cpu2'}
      templateDict['|>CPU3_BIN_DIR<|'] = './output_%s%s' % {Target.Name, '_cpu3'}
      if T.is29Target() then
        -- PLECS Cockpit plx-binutil needed to generate code certificate
        local cockpitDir = ExtTools.PlxCockpit.assertPlxCockpitFound()
        templateDict['|>COCKPIT_PATH<|'] = cockpitDir

        -- C29 codegen tools
        local c29CodegenDir = ExtTools.enforceValidDirectoryExists({'C29CodegenDir', 'C29 Codegen tools'})  
        templateDict['|>C29_CG_PATH<|'] = c29CodegenDir

        ExtTools.CGT.assertCGTFound29(MIN_CGT_VER_29, MAX_SUPPORTED_CGT_VER_C29)

      elseif T.is28Target() then
        -- Code Generation Tools (CGT)
        local codegenDir = ExtTools.enforceValidDirectoryExists({'codegenDir', 'Codegen tools'})
        templateDict['|>CG_PATH<|'] = codegenDir

        ExtTools.CGT.assertCGTFound(MIN_CGT_VER, MAX_SUPPORTED_CGT_VER)
      else
        error('Only 28 and 29 targets are supported.')
      end

      -- UniFlash application
      local uniflashDir = ExtTools.enforceValidDirectoryExists({'uniflashDir', 'TI Uniflash'})

      local m_osDir, m_ext
      if Target.Variables.HOST_OS == 'win' then
        m_osDir = 'win'
        m_ext = '.exe'
      elseif Target.Variables.HOST_OS == 'mac' then
        m_osDir = 'osx'
        m_ext = ''
      elseif Target.Variables.HOST_OS == 'linux' then
        m_osDir = 'linux'
        m_ext = ''
      else
        error('Unknown OS for Uniflash, consider using top level dslite.sh.')
      end
      local uniflashExePath = uniflashDir..
         '/deskdb/content/TICloudAgent/%s/ccs_base/DebugServer/bin/DSLite%s' %
         {m_osDir, m_ext}

      U.assertValidPathStr(uniflashExePath, {
        opt_badCharString = U.getDisallowedPathChars(),
        opt_errorMsgPrefix = 'Uniflash install'
      })
      U.assertFileExists(uniflashExePath, {
        opt_descriptor = 'Uniflash executable',
        opt_instructions = ExtTools.getNotFoundInstructions(),
      })

      templateDict['|>FLASH_EXE_PATH<|'] = uniflashExePath

      -- UniFlash target file
      local uniflashFile  -- ccxml
      local board = T.getBoardNameFromComboIndex(Target.Variables.board)
      local ccxmlInstructions = 'Please specify a valid .ccxml file for custom @param:uniflashFile:2:.'
      -- We only care about uniflash file if we are flashing
      if Target.Variables.genOnly ~= BUILD_TYPE_BUILD_ONLY then
        if board ~= 'custom' then
          if T.getUniflashConfig(board) == '' then
            U.error('The selected board (%(board)s) is not yet supported for this device.' % {board = board})
          end
          -- if we have a user-supplied serial number, it needs to be subbed into ccxml file template
          local uniflashTemplatePath = Target.Variables.TARGET_ROOT..
             '/templates/uniflash/'..T.getUniflashConfig(board)
          local uniflashDestination = Target.Variables.BUILD_ROOT..
             '/'..T.getUniflashConfig(board)
          U.copyTemplateFile(uniflashTemplatePath, uniflashDestination, templateDict)

          uniflashFile = uniflashDestination
        elseif Target.Variables.uniflashFile:sub(- #'.ccxml') == '.ccxml' then
          uniflashFile = U.normalizePath(Target.Variables.uniflashFile)
          U.assertFileExists(uniflashFile, {
            opt_descriptor = 'The @param:uniflashFile:2:',
            opt_instructions = ccxmlInstructions})
        else
          U.error(ccxmlInstructions)
        end
      end

      if not uniflashFile 
      or Target.Variables.SKIP_FLASHING_ACTIVE then -- don't validate uniflash in CI
        templateDict['|>CCXML_FILE<|'] = ''

      else -- Uniflash file found:
        local ccxmlSource = (board == 'custom') and 'The @param:uniflashFile:2:' or 'Project'
        U.assertValidPathStr(uniflashFile, {
          opt_badCharString = U.getDisallowedPathChars(),
          opt_errorMsgPrefix = ccxmlSource
        })

        templateDict['|>CCXML_FILE<|'] = uniflashFile

        local uniflashVersionFound -- nil will indicate no version number found
        local uniflashVerPath = uniflashDir..'/uniflash/public/version.txt'
        if U.fileExists(uniflashVerPath) then
          local file = assert(io.open(uniflashVerPath, 'r'))
          local text = file:read('l')
          -- establish UniFlash version number ex: 8.5.0.4593
          uniflashVersionFound = VersionNumber.new(text)
        end

        if not uniflashVersionFound
        or not minimumUniflashVersion
        or uniflashVersionFound < minimumUniflashVersion then
            U.error([[
            UniFlash must be of version %(min_version)s or more recent.

            - Version detected: %(version)s
            - Installation directory: %(install_dir)s
            ]] % {
              min_version = tostring(minimumUniflashVersion),
              version = uniflashVersionFound and tostring(uniflashVersionFound) or 'None found.',
              install_dir = uniflashDir,
            })
        end
      end

      -- handle instaSPIN as a special case
      local mk_postfix = ''
      if blockInstancesByType[cpu]['est'] ~= nil then
        if Target.Variables.buildConfig == 2 then
          U.error(
            "The @param:buildConfig:2: 'Run from RAM' is not supported for models with an InstaSpin block.")
        end
        if Target.Variables.buildConfig ~= 3 then
          mk_postfix = '_instaspin'
        end
        templateDict['|>INSTASPIN<|'] = 'YES'
      else
        templateDict['|>INSTASPIN<|'] = 'NO'
      end

      -- 280039 does not have enough RAM to support 'Run from RAM'
      -- 29H85x experiences lockups using RAM config
      if Target.Variables.buildConfig == 2 then
        if T.targetMatches({'28003x', '29H85x'}) then
          U.error(
            "The @param:buildConfig:2: 'Run from RAM' is not supported for this target.")
        elseif T.targetMatches({'2806x', '2833x', '28004x', '2837x', '2838x', '28P55x', '28P65x'}) then
          -- Run from RAM is fine
        else
          U.throwUnhandledTargetError()
        end
      end

      -- establish linker command file
      if Target.Variables.buildConfig < 3 then
        if Target.Variables.buildConfig == 2 and hasMultiCpuConfig then
          U.error(
            "A model configured for multi CPU operation does not support the @param:buildConfig:2: 'Run from RAM'.")
        end

        -- insert mk_postfix before file extension
        local linkerCommandFile = '%s%s.cmd' % {
          string.sub(T.getLinkerFileName(cpu + 1, Target.Variables.buildConfig),
                     1, -(#'.cmd' + 1)),
          mk_postfix
        }

        templateDict['|>LINKER_CMD_FILE<|'] = '%s/%s' % {coreAppDir, linkerCommandFile}
        
      else
        if hasMultiCpuConfig then
          U.error(
            "A model configured for multi CPU operation does not support the Build configuration 'Custom linking'.")
        else
          if string.sub(Target.Variables.LinkerCommandFile, -4) ~= '.cmd' then
            U.error('Invalid @param:LinkerCommandFile:2:.')
          end
          U.assertFileExists(Target.Variables.LinkerCommandFile,
                             {opt_descriptor = 'The @param:LinkerCommandFile:2:'})
          templateDict['|>LINKER_CMD_FILE<|'] = Target.Variables.LinkerCommandFile
          
        end
      end

      -- configure user defined flags for compiler and linker
      local compilerFlags = '\\'
      local linkerFlags = ''

      for _, v in ipairs(Registry.CompilerFlags) do
        compilerFlags = compilerFlags..'\n'..v..'\\'
      end

      for _, v in ipairs(Registry.LinkerFlags) do
        linkerFlags = linkerFlags..'\n'..v
      end

      -- add external libraries
      for _, par in pairs(Registry.ExternalLibraries[cpu]) do
        if par.IncludePath ~= nil then
          compilerFlags = compilerFlags..
             '\n--include_path="%s" \\' % {par.IncludePath}
        end
        if par.LibFilePath ~= nil then
          linkerFlags = linkerFlags..'\n-i "%s"' % {par.LibFilePath}
        end
        if par.LibFileName ~= nil then
          linkerFlags = linkerFlags..'\n-l %s' % {par.LibFileName}
        end
      end

      if U.userIsDeveloper() then
        if T.is28Target() then
          compilerFlags = compilerFlags..'\n--emit_warnings_as_errors \\'
        elseif T.is29Target() then
          compilerFlags = compilerFlags..'\n-Werror \\'
        else
          U.throwUnhandledTargetError()
        end
      end

      templateDict['|>LFLAGS<|'] = string.gsub(linkerFlags, '^%s*', '')                                                -- remove leading newline characters with gsub
      templateDict['|>CFLAGS<|'] = string.gsub(compilerFlags, '^%s*', '')                                                -- remove leading newline characters with gsub

      -- generate make and linker files
      U.copyTemplateFile(coreTemplatesDir..'/link%s.lkf' % {mk_postfix},
                         Target.Variables.BUILD_ROOT..'/'..
                         Target.Variables.BASE_NAME..
                         multiCpuPostFix..'.lkf', templateDict)
      U.copyTemplateFile(coreTemplatesDir..'/main.mk', Target.Variables
                         .BUILD_ROOT..
                         '/'..
                         Target.Variables.BASE_NAME..multiCpuPostFix..
                         '.mk', templateDict)

      -- generate code entry
      U.copyTemplateFile(coreTemplatesDir..'/main.c',
                         Target.Variables.BUILD_ROOT..
                         '/'..
                         Target.Variables.BASE_NAME..
                         multiCpuPostFix..'_main.c',
                         templateDict)
    else
      --[[
          Generate files into CCS project and build/debug from there
      --]]

      local ccsDir
      if Target.Variables.NUM_CPUS == 0 or cpu == 0 then
        ccsDir = U.enforceValidCoderOptionsDirectory('installDir')
      else
        ccsDir = U.enforceValidCoderOptionsDirectory('installDirCpu%d' % {T.getTiCpuNumber(cpu)})
      end

      templateDict['|>INSTALL_DIR<|'] = ccsDir
      templateDict['|>RUN_PIL_PREP<|'] = 0  -- PIL Prep Tool will likely not be needed
      if T.getTargetParameters()['clas'] ~= nil then
        templateDict['|>HAS_CLA<|'] = 'TRUE'
      else
        templateDict['|>HAS_CLA<|'] = 'FALSE'
      end

      if T.is29Target() then      
        -- PLECS Cockpit plx-binutil needed to generate code certificate
        local cockpitDir = ExtTools.PlxCockpit.assertPlxCockpitFound()
        templateDict['|>COCKPIT_PATH<|'] = cockpitDir
      else
        templateDict['|>COCKPIT_PATH<|'] = ''
      end

      do
          local gdbServerDir = Target.FamilySettings.ExternalTools.GdbServerDir
          if gdbServerDir ~= "" then
            gdbServerDir = ExtTools.enforceValidDirectoryExists({'GdbServerDir', 'C2Prog GDB server'})
          end
          templateDict['|>GDB_SERVER_DIR<|'] = gdbServerDir
      end

      U.copyTemplateFile(Target.Variables.TARGET_ROOT..'/templates/install.mk',
                         Target.Variables.BUILD_ROOT..'/'..
                         Target.Variables.BASE_NAME..
                         multiCpuPostFix..'.mk', templateDict)

      U.copyTemplateFile(Target.Variables.TARGET_ROOT..
                         '/templates/cg.mk',
                         ccsDir..'/cg.mk', templateDict)

      U.copyTemplateFile(coreTemplatesDir..'/main.c', ccsDir..
                         '/'..
                         Target.Variables.BASE_NAME..
                         multiCpuPostFix..'_main.c', templateDict)
    end
  end
  return cf_modelHooksRecipe
end

return Coder
