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

local static = {
  numInstances = 0,
  trip_zones_configured = {}, -- key is zone index, value is {gpio = gpio} or nil
  trip_signal_groups_configured = {}, -- group key is A,B,C value is true or nil
  finalized = false,
}

local Module = {}

function Module.getBlock(globals, cpu)
  local TripZones = require('common.block').getBlock(globals, cpu)
  assert((cpu == 0), 'TripZones module should always be associated with CPU 0.')

  TripZones['instance'] = static.numInstances
  static.numInstances = static.numInstances + 1

  if static.numInstances > 1 then
    error(
      'There should be only one (implicit) instance of the TripZones block.')
  end

  function TripZones:createImplicit(req)
    -- process digital trip inputs
    local z = 1
    while Target.Variables['Tz%dEnable' % {z}] ~= nil do
      if Target.Variables['Tz%dEnable' % {z}] == 1 then
        local gpio = U.enforceCO(U.isNonNegativeIntScalar, 'Tz%dGpio' % {z})
        globals.target.allocateGpio(gpio, {}, req, 'TZ %d GPIO' % {z}) -- FIX ME: Would be better if this was a link in the resource conflict error
        static.trip_zones_configured[z] = {
          gpio = gpio
        }
      end
      z = z + 1
    end

    -- process analog trip inputs
    local comps_used = {}
    local i = 1
    while (Target.Variables['AnTrip%dEnable' % {i}] ~= nil) do
      if U.checkBoxSelected('AnTrip%dEnable' % {i}) then
        local label = 'AnTrip%dSignal' % {i}

        local pin
        local errorMessageHint
        if (Target.Variables['AnTrip%dInputType' % {i}] == nil) -- no InputType means ADC
        or (Target.Variables['AnTrip%dInputType' % {i}] == 1) then -- InputType 1 = ADC, 2 = PGA
          pin = '%s%d' % {
            string.char(64 + Target.Variables['AnTrip%dAdcUnit' % {i}]), -- drop down will be valid Int
            U.enforceCO(U.isIntScalarInClosedInterval, 'AnTrip%dAdcChannel' % {i}, 0, globals.target.getTargetParameters().adcs.num_channels - 1)
          }
          errorMessageHint = 'Adjust selection of @param:AnTrip%dAdcUnit:2: and @param:AnTrip%dAdcChannel:2:.' % {i, i}
          if not globals.target.getComparatorsForPin(pin) then
            U.error('No comparator found for pin %s for Analog Trip %d. %s' % {pin, i, errorMessageHint})
          end
        else
          pin = 'PGA%d' % {U.enforceCO(U.isNonNegativeIntScalar, 'AnTrip%dPgaUnit' % {i})}
          errorMessageHint = 'Adjust selection of @param:AnTrip%dPgaUnit:2:.' % {i}
          if not globals.target.getComparatorsForPin(pin) then
            U.error('No comparator found for pin %s for Analog Trip %d. %s' % {pin, i, errorMessageHint})
          end
        end

        local threshold_low = U.enforceCO(
          U.isScalarInClosedInterval, 'AnTrip%dThresholdLow' % {i}, 0, 3.3, 'V')
        local threshold_high = U.enforceCO(
          U.isScalarInClosedInterval, 'AnTrip%dThresholdHigh' % {i}, 0, 3.3, 'V')

        if threshold_low >= threshold_high then
          U.error(
            'For the analog trip protection, @param:AnTrip%dThresholdLow:2: must be set below @param:AnTrip%dThresholdHigh:2:.' % {i, i})
        end

        -- determine comparators available at pin (error checked above)
        local comparators = globals.target.getComparatorsForPin(pin)
        local compLow = comparators.low
        local compHigh = comparators.high


        -- establish TRIPIN signal number
        -- 'Emit trip signal' aka group A, B, C
        local group = string.char(64 + Target.Variables['AnTrip%dSignal' % {i}])
        self:registerTripSignalGroup(group)
        local tripInSignal = globals.target.getTargetParameters().trip_groups[group]

        if (threshold_low > 0) and (threshold_high < 3.3) then
          -- window comparator
          if (compLow == nil) or (compHigh == nil) then
            U.error('Pin %s is not suitable for setting up a window comparator for Analog Trip %d. %s' % {pin, i, errorMessageHint})
          end
          local compHighParams = {
            opt_pinMux = compHigh.mux,
            threshold = threshold_high,
            invertCompare = false,
          }
          local compLowParams = {
            opt_pinMux = compLow.mux,
            threshold = threshold_low,
            invertCompare = true,
          }
          if compHigh.unit == compLow.unit then
            -- window can be setup with a single CMPSS
            local comp_obj = self:makeBlock('cmpss')
            comp_obj:createImplicit(
              compHigh.unit,
              {
                analogPin = pin,
                tripSignal = tripInSignal,
                opt_compHigh = compHighParams,
                opt_compLow = compLowParams,
              }, req, label)
          else
            -- must use two different CMPSS
            local comp_high = self:makeBlock('cmpss')
            comp_high:createImplicit(
              compHigh.unit,
              {
                analogPin = pin,
                tripSignal = tripInSignal,
                opt_compHigh = compHighParams,
              }, req, label)

            local comp_low = self:makeBlock('cmpss')
            comp_low:createImplicit(
              compLow.unit,
              {
                analogPin = pin,
                tripSignal = tripInSignal,
                opt_compLow = compLowParams,
              }, req, label)
          end
        else
          -- single-ended
          local threshold, invert
          if (threshold_low > 0) then
            invert = true
            threshold = threshold_low
          else -- threshold_high < 3.3
            invert = false
            threshold = threshold_high
          end

          local unit, opt_high, opt_low
          if compHigh then
            unit =  compHigh.unit
            opt_high = {
              opt_pinMux = compHigh.mux,
              threshold = threshold,
              invertCompare = invert,
            }
          else
            unit = compLow.unit
            opt_low = {
              opt_pinMux = compLow.mux,
              threshold = threshold,
              invertCompare = invert,
            }
          end

          local compInstance = self:makeBlock('cmpss')
          compInstance:createImplicit(
            unit,
            {
              analogPin = pin,
              tripSignal = tripInSignal,
              opt_compHigh = opt_high,
              opt_compLow = opt_low,
            },
            req, label
          )
        end
      end
      i = i + 1
    end

    if self:targetUsesDriverLib() then
      for z, par in pairs(static.trip_zones_configured) do
        req:add('XBAR_INPUT', z, 'Trip zones')
        globals.syscfg:addEntry('input_xbar', {
          gpio = par.gpio,
          input = z,
          core = self.cpu + 1
        })
        globals.syscfg:addEntry('gpio', {
          unit = par.gpio,
          cbx_direction = U.newComboBox('INPUT',
                                        U.ENUM.TI.DIO_TYPE),
          cbx_pullType = U.newComboBox('TRISTATE',
                                       U.ENUM.PULL_TYPES),
          core = self.cpu + 1
        })
      end
    end
  end

  function TripZones:p_getDirectFeedthroughCode()
    U.error('Explicit use of TripZones via target block not supported.')
  end

  function TripZones:isTripZoneConfigured(zone)
    return (static.trip_zones_configured[zone] ~= nil)
  end

  function TripZones:isTripSignalGroupConfigured(group)
    assert(U.isChar(group))
    return (static.trip_signal_groups_configured[group] ~= nil)
  end

  function TripZones:registerTripSignalGroup(group)
    assert(U.isChar(group))
    static.trip_signal_groups_configured[group] = true 
  end

  function TripZones:isAnyTripZoneOrGroupConfigured()
    return (next(static.trip_zones_configured) ~= nil)
       or (next(static.trip_signal_groups_configured) ~= nil)
  end

  function TripZones:finalize(c)
    if static.finalized then
      return
    end

    if self:targetUsesPinsets() then
      for z, par in pairs(static.trip_zones_configured) do
        c.PreInitCode:append('PLX_PWR_configureTZGpio(%d, %d);' %
          {z, par.gpio})
      end
    end

    static.finalized = true
  end

  return TripZones
end

return Module
