--[[
  Copyright (c) 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 debug = require('debug')
local U = require('common.utils')

local Coder = {}
local Registry = require('common.registry')

Coder.Registry = Registry -- pass the global Registry to Coder
local FactoryBlock

function Coder.CreateFactoryBlock(T)
  FactoryBlock = require('common.block').getBlock({
    target = T,
    instances = Registry.BlockInstances,
    syscfg = Registry.syscfgInterface,
    pinmap = Registry.pinmapInterface,
  })('')

  return FactoryBlock
end

function Coder.p_CreateTargetBlock(name)

  if  Coder.getTargetFamilyForOldCreateTargetBlockCheck
  and name == Coder.getTargetFamilyForOldCreateTargetBlockCheck() then
    -- this catches orphaned blocks that still make 
    -- the old CreateTargetBlock(family, name) call
    -- the function getTargetFamilyForOldCreateTargetBlockCheck is not 
    -- implemented for new tsps, which were never released with the old interface.
    U.error(
      'This block is no longer supported. Please replace it with an up-to-date library component.')
  end

  local TargetBlock = FactoryBlock:makeBlock(name)

  TargetBlock:checkMaskParameters()
  TargetBlock:checkTspVersion()

  return TargetBlock
end

-- delegate error reporting to getDirectFeedthroughCode() call
-- this is done by returning a "block" with the getDirectFeedthroughCode() function
-- returning the error string.
local function DelegateErrorTo_getDirectFeedthroughCode(errorStr)
  if type(errorStr) ~= 'string' then
    error('This function requires a string argument with the error message.')
  end

  local dummyBlock = {}
  function dummyBlock:getDirectFeedthroughCode()
    return errorStr
  end

  return dummyBlock
end

-- string errors from this function should be returned via getDirectFeedthroughCode()
function Coder.CreateTargetBlock(name)
  local ok, res = xpcall(Coder.p_CreateTargetBlock, debug.traceback, name)

  if ok then
    U.dumpLog()
    return res  -- res will be the block
  else          -- there was an error, res will be an error
    return DelegateErrorTo_getDirectFeedthroughCode(U.stripTraceOrRethrowError(res))
  end
end

function Coder.RegisterExternalLibrary(name, params, opt_cpu)
  local cpu = opt_cpu or 0
  if Registry.ExternalLibraries[cpu] == nil then
    Registry.ExternalLibraries[cpu] = {}
  end
  if Registry.ExternalLibraries[cpu][name] == nil then
    Registry.ExternalLibraries[cpu][name] = params
  else
    U.error('This library has already been registered.')
  end
end

function Coder.SetLinkerFlags(flags)
  if #Registry.LinkerFlags ~= 0 then
    U.error('Linker flags can only be set once.')
  end
  for _, v in ipairs(flags) do
    table.insert(Registry.LinkerFlags, v)
  end
end

function Coder.SetCompilerFlags(flags)
  if #Registry.CompilerFlags ~= 0 then
    U.error('Compiler flags can only be set once.')
  end
  for _, v in ipairs(flags) do
    table.insert(Registry.CompilerFlags, v)
  end
end

function Coder.GetTargetBlock(bid)
  return Registry.BlockInstances[bid]
end

function Coder.PreFlight()
  local ok, res = xpcall(Coder.p_PreFlight, debug.traceback)

  if ok then
    U.dumpLog()
    return res
  else  -- there was an error, res will be an error including the stack trace
    return U.stripTraceOrRethrowError(res)
  end
end

--[[ 
    PIL Simulation needs to create an implicit 'pil' block and add its 
    declarations to the global list. This should be called from 
    Coder.Initialize() before any explicit blocks are configured but after 
    the implicit external mode block, which configures the PIL_Init().
--]]
function Coder.setupPILSimulation(c)

  if Target.Variables.GENERATE_PIL_PROBES == 1 then
    -- Create the implicit 'pil' block
    local pil_obj = FactoryBlock:makeBlock('pil')
    pil_obj:createImplicit(c)
  end
end

--[[
    Call this function if the target does not support double precision
    floating point.
--]]
function Coder.assertFLOAT_TYPEIsFloat()
  if Target.Variables.FLOAT_TYPE ~= 'float' then
    U.error([[
      Double precision floating point format not supported.
      Please change the @param:CodeGenFloatingPointFormat: to 'float'.]])
  end
end

function Coder.p_PreFlight() -- override if we implement more in the future.
  U.InitLogFile(Coder.GetInstallDir())
  U.initModelInfoFile(Coder.GetInstallDir())

  if Target.Variables.SKIP_COMPILE_ACTIVE then
    U.devWarning('No Make steps are configured in info.xml. This info.xml configuration should not be committed or released!')
  end

  return {}
end

function Coder.Initialize()
  local ok, res = xpcall(Coder.p_Initialize, debug.traceback)

  if ok then
    U.dumpLog()
    return res
  else  -- there was an error, res will be an error including the stack trace
    return U.stripTraceOrRethrowError(res)
  end
end

function Coder.Finalize()
  local ok, res = xpcall(Coder.p_Finalize, debug.traceback)

  if ok then
    U.addToModelInfo('ModelChecksum', Model.Checksum)
    U.addToModelInfo('TargetName', Target.Name)
    U.addToModelInfo('TargetFamily', Target.Family)
    U.addToModelInfo('NumCpus', Target.Variables.NUM_CPUS)
    U.addToModelInfo('BaseName', Target.Variables.BASE_NAME)
    U.addToModelInfo('BuildRoot', Target.Variables.BUILD_ROOT)
    U.addToModelInfo('TargetRoot', Target.Variables.TARGET_ROOT)
    U.addToModelInfo('FloatType', Target.Variables.FLOAT_TYPE)
    U.writeModelInfo()
    U.dumpLog()
    return res
  else  -- there was an error, res will be an error
    return U.stripTraceOrRethrowError(res)
  end
end

return Coder
