--[[
  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 debug = require('debug')  -- required for xpcall
local U = require('common.utils')
local VersionNumber = require('common.version')

local Module = {}

--[[
    globals: passed in as an argument to FactoryBlock:getBlock in Coder.lua
    and includes:
     {
        target = T, -- from target.lua
        instances = Registry.BlockInstances,
        syscfg = Registry.systemconfigInterface,
        pinmap = Registry.pinmapInterface,
    }
--]]
function Module.getBlock(globals, cpu)
  local TargetBlock = {
    bid = nil,  -- block id
    cpu = cpu,
    globals = globals
  }

  -- create convenient shortcut for new
  setmetatable(TargetBlock, {
    __call = function (cls, ...)
      return cls:new(...)
    end
  })

  function TargetBlock:new(blockType, cpu)
    local obj = {}
    table.insert(globals.instances, obj)
    local bid = #globals.instances
    self.__index = self  -- inherit from prototype
    self.__call = function (cls, ...)
      return cls:new(...)
    end  -- make () constructor available to copies (derived classes)
    self.bid = bid
    self.blockType = blockType

    self.cpu = cpu
    setmetatable(obj, self)
    return obj
  end

  --[[
      While a block is being processed data about it is provided by PLECS in
      the global variable `Block`, which represents the currently
      being processed instance. The data includes:

      Block = {
        Name = 'Control Task\nTrigger', -- Name of the Target Code Block
        Path = '4400/Control Task\nTrigger',  -- Full path in model
        Mask = {
          ParameterA, -- Names of all the widgets in the block mask dialog
          CustomVar,  -- Any variables defined in the block mask initialization
        },
        NumOutputSignals = { -- can be empty {} (or nil for Control Task Trigger)
          [1] = 1,    -- Number of elements at output port 1
          [2] = 2,
          [3] = 1,
        },
        InputSignal = { -- List of string lists containing C expressions of input ports
          [1] = {
            [1] = "pl_1400_UNCONNECTED", -- Unconnected
          },
          [2] = {
            [1] = "<undefined in output function>", -- Can only be read in NonDirectFeedthrough
          },
          [3] = {
            [1] = "0.1f",                           -- 'float'
          },
          [4] = {
            [1] = "false",                          -- 'bool'
          },
          [5] = {
            [1] = "{adctrig = {bid = 23}}",         -- 'event' (these can only be read in NonDirectFeedthrough)
          },
        },
        InputType = {
          [1] = {
            [1] = "uint16_t",
          },
        },
        OutputType = {
          [1] = {
            [1] = "float",
          },
          [2] = {
            [1] = "event", -- A trigger signal has type 'event'
          },
          [3] = {
            [1] = "uint32_t",
            [2] = "uint32_t",
          },
        },
        OutputSignal()  -- Callback function, disables expression folding
                        -- returns list of lists containing variable names
                        -- corresponding to the variable names of the 
                        -- Target Block output ports.
                        -- Note if the output value can be an expression
                        -- for example the return value of a function call
                        -- The expressions are returned as the OutputSignal table
                        -- from the direct feedthrough code function.
                        -- 
                        -- In the case where we can't return an expression, we
                        -- write the output value into the variable retrieved 
                        -- via the OutputSignal() callback.
          -- Example return values:
          { -- for SCI block Block:OutputSigna() returns this:
            [1] = {
              [1] = "pl_2837x_B.SCI_uint16_t",
            },
            [2] = {
              [1] = "pl_2837x_B.SCI_uint8_t",
            },
          }
          { -- For Capture block
            [1] = {
              [1] = "pl_1400_B.Capture[0]",
              [2] = "pl_1400_B.Capture[1]",
            },
            [2] = {
              [1] = "pl_1400_B.Capture[2]",
              [2] = "pl_1400_B.Capture[3]",
            },
          }
        or:
          { -- For ADC block
            [1] = {
              [1] = "pl_1400_B.ADC",
            },
            [2] = {
              [1] = "EVENT",  -- not sure why this is all caps now. :)
            },
          }
        
        },
        UniqueIdentifier = 'UniqueValidCIdentifierForThisBlock',
        Task = {
          SampleTime = {
            [1] = 0.0001, -- SampleTime, 0 would mean continuous (not discrete)
            [2] = 0.0,    -- Offset, 0 means USE_START_TIME_IN_INIT_FCN is zero
          },
          Core = 0,         -- Core this block is assigned to
          Cpu = 0,          -- CPU this block is assigned to
          Name = "[0, 0]",  -- This weird string if only one task, 
                            -- otherwise the name of the task.
        },
        UserData = {      -- Value returned by directFeedthrough function for
          bid = 5,        -- use in nonDirectFeedthrough function, by convention
        },                -- our blocks return {bid = self:getId()}
        OutputMetaData,   -- A custom table used by RTBox to pass arbitrary
                          -- information along with the output signals to the
                          -- next block.   
        
      }
  --]]
  function TargetBlock:makeBlock(name, cpu)
    if not cpu then
      if Block and Block.Task and Block.Task.Cpu then
        cpu = Block.Task.Cpu
      else
        cpu = 0
      end
    end

    -- To require the block we need the full path
    -- however, the name is also used as a block identifier ('timer', etc)
    -- so don't modify the 'name' used elsewhere
    local blockPath = name
    if not string.find(blockPath, '%.') then
      -- The full path to the block is not specified.
      -- Assume it is in the blocks directory.
      blockPath = 'blocks.%s' % {name}
    end

    local blockLoad = require(blockPath)
    if type(blockLoad) ~= 'table' then
      error('Problem loading the file '..blockPath)
    end
    local block = blockLoad.getBlock(globals, cpu)(name, cpu)

    block:setBlockMetaData()
    return block
  end

  --[[
      Each block in the model gets a unique block ID when created above.
      This value can be used to look up the block in the global registry via:

      ```
          function Coder.GetTargetBlock(bid)
            return Registry.BlockInstances[bid]
          end
      ```
      Blocks with trigger outputs pass their 'bid' along with the trigger
      information:

      ```
          {syncper = {bid = 33}}
      ```
      Also blocks use this for the non-directfeedthrough code, by storing the
      'bid' in 'UserData' when returning from the direct feedthrough code:

      ```
          UserData = {bid = self:getId()}
      ```

      Block.UserData is then used to call the non-direct feedthrough code:

      ```
          local block = Target.Coder.GetTargetBlock(Block.UserData.bid)
          return block:getNonDirectFeedthroughCode()
      ```
  --]]
  function TargetBlock:getId()
    return self.bid
  end
  --[[
      blockType is typically the name of the .lua file that was loaded.
  --]]
  function TargetBlock:getType()
    return self.blockType
  end

  function TargetBlock:blockMatches(luaFileName)
    return self.blockType == luaFileName
  end

  function TargetBlock:setBlockMetaData()
    -- Copy over some values for all blocks.
    if Block then
      self.blockName = Block.Name
      self.blockPath = Block.Path
      self.uniqueIdentifier = Block.UniqueIdentifier
    else
      self.blockName = 'UNDEFINED for implicit block'
      self.blockPath = 'UNDEFINED for implicit block'
      self.uniqueIdentifier = 'UNDEFINED for implicit block'
    end
  end

  -- Returns the name of the block in the model
  function TargetBlock:getName()
    return self.blockName
  end

  function TargetBlock:setName(name) -- implicit blocks can set a name this way
    self.blockName = name
  end

  -- Returns the path to the block in the model
  function TargetBlock:getPath()
    return self.blockPath
  end

  -- Unique ID for the block (used for PIL)
  function TargetBlock:getUniqueIdentifier()
    return self.uniqueIdentifier
  end

  function TargetBlock:getCpu()
    return self.cpu
  end

  --[[
      Each block module (can.lua etc), is responsible for tracking the number
      of instances of this block type and assigning each a unique 'instance'
      index, which by convention increase from zero.

      A reference to each instances 'bid' is stored in the 'instances' table,
      in both the globals.instances and the module.instances.

      This allows blocks to query the instance of another block. For example, for
      can_port, can_rx, can_tx.lua to reference a shared implicit can.lua object.

      And for one block to access and finalize() all instances of a specific block.
  --]]
  function TargetBlock:getObjIndex()
    if not self.instance then
      error(
        "Each peripheral.lua file is responsible for assigning the 'instance' variable.")
    end
    return self.instance
  end

  --[[
      checkMaskParameters() is run from Coder.lua
      immediately after the block is created with makeBlock()
      (Target.Coder.CreateTargetBlock('powerstage'), etc)
      and can be used to validate Block.Mask values.

      Note from Plecs perspective, this is run as part of
      the DirectFeedThrough() code.
  --]]
  function TargetBlock:checkMaskParameters()
  end

  local GPIO = require('utils.gpio') -- TSP specific port mask validation
  --[[
    This function configures a 'pad' based on the Port / Pin widget names
    It is a block function so it has access to the blockPath via self:getPath().
    Checks the validity of two widgets and configures the 'pad' link.
  --]]
  function TargetBlock:enforceMask_isPad(portMaskVarNameStr, pinMaskVarNameStr, opt_linkPreFix)
    -- returns a pad object
    return {
      port = GPIO.portMaskValidation(portMaskVarNameStr),
      pin = U.enforceMask(U.isNonNegativeIntScalar, pinMaskVarNameStr),
      opt_link = (opt_linkPreFix and opt_linkPreFix..' ' or '')
         ..'@param:'..portMaskVarNameStr..':%(id)d: /'
         ..' @param:'..pinMaskVarNameStr..':%(id)d:',
      opt_blockPath = self:getPath(),
    }
  end
  --[[
    This function configures a 'pad' array based on the Port / Pin widget names
    It is a block function so it has access to the blockPath via self:getPath().
    Checks the validity of two widgets and configures the 'pad' links.
  --]]
  function TargetBlock:enforceMask_isPadArray(portMaskVarNameStr, pinMaskVarNameStr)

    local port = GPIO.portMaskValidation(portMaskVarNameStr)
    local pins = U.enforceMask(U.isNonNegativeIntScalarOrVector, pinMaskVarNameStr)
    local pads = {}

    for i, pin in ipairs(pins) do
      local opt_errIdx = (#pins > 1) and (' index %d' % {i}) or ''
      pads[i] = {
        port = port,
        pin = pin,
        opt_link = '@param:'..portMaskVarNameStr..':%(id)d: /'
           ..' @param:'..pinMaskVarNameStr..':%(id)d:'..opt_errIdx,
        opt_blockPath = self:getPath(),
      }
    end
    return pads
  end


  --[[
      This check is a remnant from when we distributed the experimental 
      library separately from the TSP. To enable this check, thus restricting
      the block to specific versions of the TSP, add these variables to 
      the block MaskInit:

      TspMinVer = '1.5'
      TspMaxVer = '1.6'
  --]]
  function TargetBlock:checkTspVersion()

    if  Target.Version
    and Block.Mask
    and Block.Mask.TspMinVer
    and Block.Mask.TspMaxVer then
      U.warning([[
        Experimental block. Do not use for important work as
        it may no longer be supported in the future.]])

      -- Crop Target.Version to two significant digits because
      -- TspMinVer and TspMaxVer are only ever 2 'digits' and we want 
      -- Target.Version 1.9.1 to match TspMaxVer 1.9
      local shortTargetVersion = VersionNumber.stripVersion(Target.Version, 2)

      local thisVer = VersionNumber.new(shortTargetVersion)
      local minVer = VersionNumber.new(Block.Mask.TspMinVer)
      local maxVer = VersionNumber.new(Block.Mask.TspMaxVer)
      if  thisVer and minVer and maxVer  -- if any are nil, skip the check
      and (thisVer < minVer or maxVer < thisVer) then
        U.error([[
          This component is not supported by TSP version %s.
          ]] % {Target.Version})
      end
    end
  end

  --[[
      Check if the current target matches one in the argument,
      where the arg 'targets' is optionally a string for one target
      or a list of strings for one or more targets.
  --]]
  function TargetBlock:targetMatches(targets, should_be_nil)
    if should_be_nil ~= nil then
      error('Multiple targets must be listed in an array for targetMatches.')
    end
    return U.targetMatches(targets, globals.target)
  end

  -- Pinset / Driverlib helper functions are for the TI TSP.
  function TargetBlock:targetUsesPinsets()
    -- For older targets, which do not have a driverlib,
    -- configuration is done in the initialization functions
    return TargetBlock:targetMatches({'2806x', '2833x'})
  end

  function TargetBlock:targetUsesDriverLib()
    -- If the target has a driverlib, pins are configured in sysconfig
    return not TargetBlock:targetUsesPinsets()
  end

  --[[
      The resource list is populated provided in each targets .configure()
      function in the respective target.lua file.

        Resources exist in two forms:

        1) A simple string, for example `resources:add("MCAN A")`
           Is represented in the output like this:

          [344] = {
              [1] = "MCAN A",
              [2] = "-1",
           },

        2) Ranged resources, for example: `resources:add("QEP", 1, 6)`
           Are represented in the output like this:
             [368] = {
                [1] = "QEP",
                [2] = "1",
              },
              [369] = {
                [1] = "QEP",
                [2] = "2",
              },
              -- etc

      At the end of the build PLECS checks that no resource was claimed twice
      and highlights both blocks if a clash is detected. However, if you would
      like to detect an error earlier for a missing resouce, this function
      allows you to check if a resource exists to be claimed on this target.

      The options are to provide a string name and optionally a number.

      self:checkResourceExists('STRING NAME')
      self:checkResourceExists('STRING NAME', number)

      Returns true if a matching resource is found, false otherwise.
  --]]
  function TargetBlock:checkResourceExists(resourceStr, opt_number)
    assert(U.isString(resourceStr))

    -- get a local copy of the resources for this target
    local resources = ResourceList:new()
    globals.target.configure(resources)

    for _, r in ipairs(resources) do
      if r[1] == resourceStr then
        if not opt_number
        or r[2] == opt_number then
          return true
        end
      end
    end
    return false
  end

  function TargetBlock:getParameter(p)
    if not U.isString(p) then
      error('getParameter arg must be a string')
    end

    if p:find('^opt_') or (self[p] ~= nil) then
      return self[p]
    else
      error('Cannot access nil data via getParameter(), probably you have a typo in the parameter name: "'..tostring(p)
        ..'". If you want to access data that can be nil, use getOptParameter().')
    end

  end

  -- returns nil if no pilReadProbes for this block.
  function TargetBlock:getReadProbeCode(pil_obj)
    local code
    -- Set Read Probes on all non 'event' block inputs.
    for i = 1, #Block.InputSignal do
      if  Block.InputType
      and Block.InputType[i]
      and Block.InputType[i][1] ~= 'event' then
        for j = 1, #Block.InputSignal[i] do
          local name = '%(prefix)s_r%(i)d_%(j)d' % {
            prefix = Block.UniqueIdentifier,
            i = i,
            j = j
          }
          pil_obj:registerReadProbe(name, {type = Block.InputType[i][j]})

          if not code then
            code = U.CodeLines:new()
          end
          code:append(
            '%(baseName)s_probes.%(name)s = %(inputSigName)s;' % {
              baseName = Target.Variables.BASE_NAME,
              name = name,
              inputSigName = Block.InputSignal[i][j],
            })
        end
      end
    end
    return code
  end

  --[[
      PIL wrapper for directFeedthroughCode that reads inputs and overrides
      outputs as needed.
  --]]
  function TargetBlock:p_getDirectFeedthroughCodePILWrapper(...)

    -- Call the protected getDirectFeedthroughCode function
    local retTable = self:p_getDirectFeedthroughCode(...)

    local pilEnabled = Target.Variables.GENERATE_PIL_PROBES == 1 -- checkbox
    if pilEnabled and Block.UniqueIdentifier then 
      -- InstructionOverrides do not have UniqueIdentifier and don't need probe hooks

      local pil_obj = self:getBlockInstance('pil')
      -- Read Probes: When nonDirectFeedthrough inputs are present, 
      -- all PIL read probes are generated in the nonDirectFeedthrough 
      -- code function.
      if not Block.HasNonDftInputs then
        -- Set Read Probes in direct feedthrough code

        local readCode = self:getReadProbeCode(pil_obj)
        if readCode then
          if not retTable.OutputCode then
            retTable.OutputCode = U.CodeLines:new()
          end
          retTable.OutputCode:append(readCode)
        end
      end

      -- Override Outputs set via OutputSignal List
      if retTable.OutputSignal then
        for i = 1, #retTable.OutputSignal do
          if  Block.OutputType
          and Block.OutputType[i]
          and Block.OutputType[i][1] ~= 'event' then
            for j = 1, #retTable.OutputSignal[i] do
              local name = '%(prefix)s_o%(i)d_%(j)d' % {
                prefix = Block.UniqueIdentifier,
                i = i,
                j = j
              }
              pil_obj:registerOverrideProbe(name,
                                            {type = Block.OutputType[i][j]})

              if not retTable.OutputCode then
                retTable.OutputCode = U.CodeLines:new()
              end
              if not retTable.OutputSignal[i][j] then
                U.error(
                  'The OutputSignal table for this block is not formatted correctly for PIL.')
              end
              -- Write code for override probe using the OutputSignal from the block
              retTable.OutputCode:append(
                'SET_OPROBE(%(baseName)s_probes.%(name)s, %(genericOutput)s);' %
                {
                  baseName = Target.Variables.BASE_NAME,
                  name = name,
                  genericOutput = retTable.OutputSignal[i][j],
                })
              -- Override the OutputSignal the block specified with the PIL value
              retTable.OutputSignal[i][j] =
                 '%(baseName)s_probes.%(name)s' % {
                   baseName = Target.Variables.BASE_NAME,
                   name = name
                 }
            end
          end
        end
      else
        -- There is no OutputSignal Table, did this block call Block:OutputSignal()?
        local outSignals = Block:OutputSignal()

        if outSignals then
          for i = 1, #outSignals do
            if  Block.OutputType
            and Block.OutputType[i]
            and Block.OutputType[i][1] ~= 'event' then
              for j = 1, #outSignals[i] do
                local name = '%(prefix)s_o%(i)d_%(j)d' % {
                  prefix = Block.UniqueIdentifier,
                  i = i,
                  j = j
                }
                pil_obj:registerOverrideProbe(name,
                                              {type = Block.OutputType[i][j]})

                if not retTable.OutputCode then
                  retTable.OutputCode = U.CodeLines:new()
                end
                -- Write code for override probe using the OutputSignal from the function call
                retTable.OutputCode:append([[
                    SET_OPROBE(%(baseName)s_probes.%(name)s, %(globalOutput)s);
                    %(globalOutput)s = %(baseName)s_probes.%(name)s;]] %
                  {
                    baseName = Target.Variables.BASE_NAME,
                    name = name,
                    globalOutput = outSignals[i][j],
                  })
              end
            end
          end
        end
      end
    end

    return retTable
  end

  --[[
      getDirectFeedthroughCode() is run after the model initialization,
      but before all blocks have been initialized/configured. Most TSP code
      can be generated at this step.

      However, anything that receives input from another TSP block,
      for example the Trigger chain, must be configured in the Non Direct Feedthrough stage.

      This function uses a protected call (xpcall) to p_getDirectFeedthroughCode,
      which catches and cleans up errors for user feedback.

      Returns a table with these optional entries:
        Include, Require, Declarations, InitCode, UpdateCode, TerminateCode
      These entries are direct feedthrough specific:
        OutputSignal, OutputCode, UserData
  --]]
  function TargetBlock:getDirectFeedthroughCode(...)
    local ok, res = xpcall(self.p_getDirectFeedthroughCodePILWrapper, 
                           debug.traceback, self, ...)

    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 wrapper for nonDirectFeedthroughCode that reads inputs if they
      were not read at the directFeedthrough Stage.
  --]]
  function TargetBlock:p_getNonDirectFeedthroughCodePILWrapper(...)

    -- Call the protected getNonDirectFeedthroughCode function
    local retTable = self:p_getNonDirectFeedthroughCode(...)

    -- Read Probes:
    local pilEnabled = Target.Variables.GENERATE_PIL_PROBES == 1 -- checkbox
    if pilEnabled and Block.UniqueIdentifier and Block.HasNonDftInputs then
      -- InstructionOverrides do not have UniqueIdentifier and don't need probe hooks

      local pil_obj = self:getBlockInstance('pil')
      -- Set Read Probes in nonDirectFeedthrough code
      local readCode = self:getReadProbeCode(pil_obj)
      if readCode then
        if not retTable.UpdateCode then
          retTable.UpdateCode = U.CodeLines:new()
        end
        retTable.UpdateCode:append(readCode)
      end
    end

    return retTable
  end
  --[[
      getNonDirectFeedthroughCode() is run after all blocks have been through the
      direct feedthrough stage. Any configuration that uses the output of another TSP
      block as input must wait till this stage to be configured.

      Returns a table with these optional entries:
        Include, Require, Declarations, InitCode, UpdateCode, TerminateCode
  --]]
  function TargetBlock:getNonDirectFeedthroughCode(...)
    local ok, res = xpcall(self.p_getNonDirectFeedthroughCodePILWrapper, 
                           debug.traceback, self, ...)

    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

  --[[
      getNonDirectFeedthroughCode is run after
      getDirectFeedthroughCode has run for all blocks
      in the model. Coder or input checks that depend on outputs
      of other TSP blocks must wait till here, when the
      full global block registry has been populated and
      trigger input sources are known, etc.

      This stub function is only overwritten in cases where
      the non direct feedthrough stage is necessary.
  --]]
  function TargetBlock:p_getNonDirectFeedthroughCode()
    return {}
  end

  function TargetBlock:requestImplicitTrigger(ts)
  end

  function TargetBlock:setImplicitTriggerSource(bid)
  end

  -- informs the source of a trigger about its sink
  -- called multiple times if a source has multiple sinks
  function TargetBlock:setSinkForTriggerSource()
  end

  --[[
      Implemented by all trigger sources, will propogate sample time information
      down to all registered trigger sinks.
  --]]
  function TargetBlock:propagateTriggerSampleTime()
  end

  -- XMC-specific trigger chain functions start here ---------------------------

  --[[
    *******************  TRIGGER CHAIN OVERVIEW **************************

    ** Control Task Trigger **
    There is only one Control Task Trigger in a model, which triggers the base
    task. This may be an explicit block (which requires an explicit source), 
    or be created implicitly.

    The "task trigger origin block" is the block at the top of the trigger 
    chain which triggers the Control Task Trigger. If this trigger chain is not
    defined explicitly in the model, we look for a block in the model which
    provides a trigger sample time (Ts) that corresponds to the discretization
    step size specified in the Coder Options. (Coder.lua loops on all blocks
    to do this). If none is found, we create a Timer to serve as the task 
    trigger origin block.
    
    ** Trigger sources **
    Timer, PWM, ADC, and CCU8 Slice (Low Level) blocks can be trigger sources. 
    
    Trigger sources maintain a registry of their sinks and can propagate their 
    sample time to all sinks registered. All sources provide their trigger
    sample time (for blocks looking for an implicit trigger) and identify
    whether or not they are possible task trigger origin blocks (the ADC is not,
    and neither are PWM blocks which are configured for variable frequency, etc.).

    ** Trigger sinks **
    Control Task Trigger blocks and triggered ADC blocks are trigger sinks and 
    require a trigger source.
    
    If no trigger source block is explicitly connected in the model, these
    blocks are implicitly triggered at the base task rate by the task trigger
    origin block or, if possible, by an ADC block triggered by the task trigger
    origin block (exception: the ADC block in the XMC TSP cannot be triggered
    implicitly - instead of an implicit trigger source, it uses its internal
    SCAN trigger source). This is done so the ADC conversion happens immediately
    before the base task is triggered and the base task can compute with current
    ADC values.

    ** Building the trigger chain **
    Trigger sinks are responsible for registering themselves with their trigger
    source. These blocks are able to read their explicit trigger port inputs in 
    the nonDirectFeedthrough function (with the exception of the task trigger).
    Since all blocks are in the global block registry after the directFeedthrough 
    stage, sinks register themselves with their explicit triggers in the
    nonDirectFeedthrough code.

    Trigger sinks without an explicitly defined trigger source are assigned an
    implicit trigger source in the Coder.Finalize() function. They register
    themselves with their trigger source as soon as they are offered a trigger
    source.

    Every time a trigger sink is added to a trigger source block, the trigger
    sampling time Ts is propagated down the trigger chain towards the sinks.
  --]]

  --[[
    Timer, PWM, and CCU8 Slice (Low Level) blocks can be task trigger origin 
    blocks. If their configuration allows them to function this way, these
    trigger source blocks identify themselves by returning 'true' from this
    function.
  --]]
  function TargetBlock:canBeImplicitTaskTriggerOriginBlock()
  end

  --[[
    Implemented by all blocks that can provide an implicit trigger.

    returns the implicit trigger sample time ts the block provides (exception:
    if this function is called on an adc_trig block before ts has been
    propagated to the adc_trig block from it trigger source).
  --]]
  function TargetBlock:getImplicitTriggerTs()
  end

  --[[
    This function is only implemented for trigger sinks to allow them to accept
    an offered implicit trigger source.

    If the block is already explicitly connected to a trigger source, nothing
    is done. Otherwise, if no implicit trigger has been found, the offered
    trigger source candidate becomes the implicit trigger source for this block.
    
    This function is called in Coder.lua (Finalize), the argument is always 
    the task trigger origin block, or an ADC in the same trigger chain.

    Without an explicit trigger, blocks are triggered at the base task rate.
  --]]
  function TargetBlock:offerImplicitTriggerSource(triggerSourceCandidate)
  end

  --[[
    This function adds a sink to the sink registry of a trigger source block.
    The sink registry is a list of all the trigger sinks that are attached to
    a trigger source block. After adding the sink to the sink registry, this
    function calls propagateTriggerSampleTime() to propagate the trigger sample
    time down the trigger chain to all registered sinks.

    This function is called from the trigger sink block when a trigger source
    block is defined for the trigger sink block.
  --]]
  function TargetBlock:registerTriggerSink(sink)
  end

  -- XMC-specific trigger chain functions end here -----------------------------

  -- for configuration purposes before finalize is called
  function TargetBlock:preFinalize()
  end

  --[[
      finalize() will be called on each block in the global registry,
      from Coder.lua.

      Note, for most (all?) peripherals we finalize all instances together.
      Using the total number of instances to allocate memory. For this reason,
      the first instance for which finalize() is called, will loop over all
      instances of this block type calling finalizeThis() on each.
  --]]
  function TargetBlock:finalize()
  end

  function TargetBlock:logLine(line)
    U.log('- %02i (%s): %s\n' % {self.bid, self.blockType, line})
  end

  -- returns the first block of the specified blockType on the same CPU as the caller block
  function TargetBlock:getBlockInstance(blockType)
    return TargetBlock:getBlockInstanceWithMatchingParameter(blockType)
  end

  -- returns the first block of the specified blockType and matching parameter on the same CPU as the caller block
  function TargetBlock:getBlockInstanceWithMatchingParameter(blockType, opt_param)
    for _, b in ipairs(globals.instances) do
      if  b:blockMatches(blockType)
      and b:getCpu() == self.cpu then
        if not opt_param then
          return b
        else
          if b:getParameter(opt_param) == self[opt_param] then
            return b
          end
        end
      end
    end
  end

  -- returns the first block of the specified blockType on any CPU
  function TargetBlock:getGlobalBlockInstance(blockType)
    return TargetBlock:getGlobalBlockInstanceWithMatchingParameter(blockType)
  end

  -- returns the first block of the specified blockType and matching parameter on any CPU
  function TargetBlock:getGlobalBlockInstanceWithMatchingParameter(blockType,
                                                                   opt_param)
    for _, b in ipairs(globals.instances) do
      if b:blockMatches(blockType) then
        if not opt_param then
          return b
        else
          if b:getParameter(opt_param) == self[opt_param] then
            return b
          end
        end
      end
    end
  end

  return TargetBlock
end

return Module
