--    ======================
--    Description
--    ======================
--
--    Logika umożliwia serwisowe blokowanie wyjścia po wielokrotnym wystąpieniu alertu.
--
--    Obsługuje listę alertów.
--
--    ======================
--    Parameters
--    ======================
--
--    input.source                            - wejście źródła
--
--    output                                  - wyjście
--
--    input.alert.signal                      - lista - [0..1] sygnał alertu
--
--    setting.alert.enabled                   - lista - [0..1] flaga czy alert jest aktywny
--
--    setting.alert.delay                     - lista - [s] czas przerwy w pracy po wystąpieniu alertu
--
--    setting.alert.reset.delay               - lista - [s] jeżeli po tym czasie nie wystąpił alert to następuje reset counter.alert.count
--
--    setting.service.block.count             - lista - [1..] po tej ilości wystąpień alertu zostanie włączona blokada serwisowa
--
--    counter.alert.delay                     - lista - [s] licznik przerwy w pracy po wystąpieniu alertu
--
--    counter.alert.reset.delay               - lista - [s] licznik powiązany z setting.alert.reset.delay 
--
--    counter.alert.count                     - lista -  [0..] ilości wystąpień alertu (po upłynięciu licznika counter.alert.delay)
--
--    setting.alert.reset                     - wpisanie 1 powoduje resetowanie blokady serwisowej
--
--    setting.output.alert.state              - stan wyjścia podczas blokady serwisowej lub alertu
--
--    counter.alert.state                     - [0..1] flaga: czy jest aktywna blokada serwisowa
--
-- user can use some functions provided by ibmanager.

-- ibmanager provides following functions to use:
--
-- function returns value of required ibmanager variable
-- @param fullName - string - variable name - name of variable of which value must be returned, for example "rs.0.id.255.input.t.0.value"
-- @return         - string or integer - variable value or "nil" if variable not exist or is not readable
--
-- getValue(fullName)


-- function set value of given ibmanager variable
-- @param fullName - string - variable name - name of variable of which value we want to set, for example "rs.0.id.255.input.t.0.value"
-- @param value    - string, int or boolean - value to set
-- @return         - nothing
--
-- setValue(fullName, value)


-- function returns value of required ibmanager variable
-- @param key      - placed in xml logic configuration file:
--                   * as attribute: Name, in Var, RemoteVar and ImportVar elements in the case of stand alone variables
--                   * as concatenation of two attributes: ListName.Postfix in VarListItem, RemoteVarListItem and ImportVarListItem elements
--                     in the case of variables that are placed in lists
-- @return         - string or integer - variable value or "nil" if variable not exist or is not readable
--
-- getLogicValue(key)


-- function set value of given ibmanager variable
-- @param key      - placed in xml logic configuration file:
--                   * as attribute: Name, in Var, RemoteVar and ImportVar elements in the case of stand alone variables
--                   * as concatenation of two attributes: ListName.Postfix in VarListItem, RemoteVarListItem and ImportVarListItem elements
--                     in the case of variables that are placed in lists
-- @param value    - string, int or boolean - value to set
-- @return         - nothing
--
-- setLogicValue(key, value)

-- function returns two sections of kvsettings from xml configuration file
-- returned value is two element table, each of these elements is table too.
-- indices of returned table are strings and equal "instance" and "global"
-- values of returned tables are tables and contain KVsettings for applicable section.
-- nested tables have form key = value, where key is index in nested table and value is value.
-- example: {"instance" = {"ikey1" = "ivalue1", "ikey2" = "gvalue2"}, "global" = {"gkey1" = "gvalue1", "gkey2" = "gvalue2", "gkey3" = "gvalue3"}}
-- @return          - two dimensional array - kvsettings for global and instance sections
-- getKvSettings()


-- function schedules alert to send.
-- rules are defined in separated alert configuration files and are described in ibmanager instruction manual
-- @param id - alert identifier - must be defined in current logic configuration file in section: <Alert Id="any_identifier" ...
-- scheduleAlert(id)

-- function cancels alert sending, if was previously scheduled. if not then only wakes up alerts handling thread, so if there is no need to call this function, then do not call it.
-- rules are defined in separated alert configuration files and are described in ibmanager instruction manual
-- @param id - alert identifier - must be defined in current logic configuration file in section: <Alert Id="any_identifier" ...
-- cancelAlert(name)

-- function returns table, containing Variables that belongs to required list.
-- @param listName - Name attribute of VarList, RemoteVarList or ImportVarList elemetnst in configurationfile
-- @return array of key-value pairs. Key - variable postfix, Value - Variable value
-- getVarList(listName)

-- function returns monotonic system clock value, that elapsed since specific epoch
-- returned time is expressed in milliseconds.
-- getClock()

-- function logs message to file, if defined in configuration file log level is less than passed to this function
-- @param logLevel - one of:
--        LogLevel.TraceLo
--        LogLevel.Trace
--        LogLevel.TraceHi
--        LogLevel.DebugLo
--        LogLevel.Debug
--        LogLevel.DebugHi
--        LogLevel.Info
--        LogLevel.Notice
--        LogLevel.Warning
--        LogLevel.Error
--        LogLevel.Critical
-- @param logMessage - string to log
-- log(logLevel, logMessage)


-- ibmanager provides following global variables:

-- logic type, (in this case it will always be "Lua") - the same as in logic configuration file in section: <Logic Type="Lua" ...
-- LOGIC_TYPE

-- logic version, the same as in logic configuration file in section: <Logic ... Version="x.y.z" ...
-- LOGIC_VERSION

-- logic sub-type, the same as in logic configuration file in section: <Logic ... SubType="Hysteresis" ...
-- LOGIC_SUBTYPE

-- logic sub-version, the same as in logic configuration file in section: <Logic ... SubVersion="x.y.z" ...
-- LOGIC_SUBVERSION

-- logic instance name - the same as in logic configuration file, in section: <Instance Name="0">
-- LOGIC_INSTANCE_NAME

-- add script directory to package path
package.path = package.path .. ";./logic/scripts/utils/?.lua;../../scripts/utils/?.lua";

-- use script - without .lua extension - delta class
require("DownCounter");

SUPPORTED_SUBLOGIC_TYPE = "OutputServiceBlock";
SUPPORTED_SUBLOGIC_VERSION = "1.0.0";
g_versionChecked = false;


g_tableCount = 0;
g_checkList = 0;

g_tablePostfix = {};

g_input_alert_signal = {};
g_setting_alert_enabled = {};
g_setting_alert_delay = {};
g_setting_alert_reset_delay = {};
g_setting_service_block_count = {};
g_counter_alert_delay = {};
g_counter_alert_reset_delay = {};
g_counter_alert_count = {};

g_counter_alert_delayDownCounter = {};
g_counter_alert_reset_delayDownCounter = {};


-- zwraca 1 jeżeli listy są OK

function checkLists()
  local fail = false

  if (g_tableCount == 0) then
    log(LogLevel.Error, "VarList input.alert.signal not initialized in instance section of configuration.")
    fail = true
  else
    for i = 0, g_tableCount-1, 1 do

      -- Check for fail

      if g_setting_alert_enabled[g_tablePostfix[i]] then
      else
        log(LogLevel.Error, "VarList element setting.alert.enabled." .. g_tablePostfix[i] .. " not initialized in instance section of configuration.")
        fail = true
      end

      if g_setting_alert_delay[g_tablePostfix[i]] then
      else
        log(LogLevel.Error, "VarList element setting.alert.delay." .. g_tablePostfix[i] .. " not initialized in instance section of configuration.")
        fail = true
      end

      if g_setting_alert_reset_delay[g_tablePostfix[i]] then
      else
        log(LogLevel.Error, "VarList element setting.alert.reset.delay." .. g_tablePostfix[i] .. " not initialized in instance section of configuration.")
        fail = true
      end

      if g_setting_service_block_count[g_tablePostfix[i]] then
      else
        log(LogLevel.Error, "VarList element setting.service.block.count." .. g_tablePostfix[i] .. " not initialized in instance section of configuration.")
        fail = true
      end

      if g_counter_alert_delay[g_tablePostfix[i]] then
      else
        log(LogLevel.Error, "VarList element counter.alert.delay." .. g_tablePostfix[i] .. " not initialized in instance section of configuration.")
        fail = true
      end

      if g_counter_alert_reset_delay[g_tablePostfix[i]] then
      else
        log(LogLevel.Error, "VarList element counter.alert.reset.delay." .. g_tablePostfix[i] .. " not initialized in instance section of configuration.")
        fail = true
      end

      if g_counter_alert_count[g_tablePostfix[i]] then
      else
        log(LogLevel.Error, "VarList element counter.alert.count." .. g_tablePostfix[i] .. " not initialized in instance section of configuration.")
        fail = true
      end

    end
  end

  if fail then
    return 0
  else
    return 1
  end

end


-- entry point to the logic
-- @param firstCall - tells if logic is called first time
-- @return        - nothing
function onLogicCall(firstCall)

  if not g_versionChecked then
    -- checking sublogic type and sublogic version
    if LOGIC_SUBTYPE ~= SUPPORTED_SUBLOGIC_TYPE then
      error("Wrong logic sub-type. expected " .. SUPPORTED_SUBLOGIC_TYPE .. " but used " .. LOGIC_SUBTYPE);
    end
    local versionWithoutBuild = string.match(LOGIC_SUBVERSION, "[0-9]+%.[0-9]+%.[0-9]+");
    if versionWithoutBuild ~= SUPPORTED_SUBLOGIC_VERSION then
      error("Wrong logic sub-version. expected " .. SUPPORTED_SUBLOGIC_VERSION .. " but used " .. LOGIC_SUBVERSION);
    end
    g_versionChecked = true;
  end

  local input_source                              = getLogicValue("input.source");
  local setting_output_alert_state                = getLogicValue("setting.output.alert.state");
  local setting_alert_reset                       = getLogicValue("setting.alert.reset");

  local input_alert_signal                        = getVarList("input.alert.signal");
  local setting_alert_enabled                     = getVarList("setting.alert.enabled");
  local setting_alert_delay                       = getVarList("setting.alert.delay");
  local setting_alert_reset_delay                 = getVarList("setting.alert.reset.delay");
  local setting_service_block_count               = getVarList("setting.service.block.count");
  local counter_alert_delay                       = getVarList("counter.alert.delay");
  local counter_alert_reset_delay                 = getVarList("counter.alert.reset.delay");
  local counter_alert_count                       = getVarList("counter.alert.count");

  local output = input_source;
  local counter_alert_state = 0
  g_tableCount = 0;

  -- tablica 
  if input_alert_signal ~= nil then
    for postfix, value in pairs(input_alert_signal) do
      g_input_alert_signal['' .. postfix] = value;

      g_tablePostfix[g_tableCount] = postfix
      g_tableCount = g_tableCount + 1;
    end
  end

  -- tablica
  if setting_alert_enabled ~= nil then
    for postfix, value in pairs(setting_alert_enabled) do
      g_setting_alert_enabled['' .. postfix] = value;
    end
  end

  -- tablica
  if setting_alert_delay ~= nil then
    for postfix, value in pairs(setting_alert_delay) do
      g_setting_alert_delay['' .. postfix] = value;
    end
  end

  -- tablica
  if setting_alert_reset_delay ~= nil then
    for postfix, value in pairs(setting_alert_reset_delay) do
      g_setting_alert_reset_delay['' .. postfix] = value;
    end
  end

  -- tablica
  if setting_service_block_count ~= nil then
    for postfix, value in pairs(setting_service_block_count) do
      g_setting_service_block_count['' .. postfix] = value;
    end
  end

  -- tablica
  if counter_alert_delay ~= nil then
    for postfix, value in pairs(counter_alert_delay) do
      g_counter_alert_delay['' .. postfix] = value;
    end
  end

  -- tablica
  if counter_alert_reset_delay ~= nil then
    for postfix, value in pairs(counter_alert_reset_delay) do
      g_counter_alert_reset_delay['' .. postfix] = value;
    end
  end

  -- tablica
  if counter_alert_count ~= nil then
    for postfix, value in pairs(counter_alert_count) do
      g_counter_alert_count['' .. postfix] = value;
    end
  end

  if (firstCall) then
    g_checkList = checkLists()
  end

  if g_checkList == 1 then

    for i = 0, g_tableCount-1, 1 do
      
      if setting_alert_reset == 1 then
        g_counter_alert_count[g_tablePostfix[i]] = 0
      end

      if g_counter_alert_delayDownCounter[g_tablePostfix[i]] == nil or firstCall then
        g_counter_alert_delayDownCounter[g_tablePostfix[i]] = DownCounter.create();
        g_counter_alert_delayDownCounter[g_tablePostfix[i]]:freeze()
      else
        g_counter_alert_delayDownCounter[g_tablePostfix[i]]:updateParams(g_setting_alert_delay[g_tablePostfix[i]] * 1000);
      end


      g_counter_alert_delay[g_tablePostfix[i]] = g_counter_alert_delayDownCounter[g_tablePostfix[i]]:timeTo0() / 1000;
      g_counter_alert_delay[g_tablePostfix[i]] = (g_counter_alert_delay[g_tablePostfix[i]] < 0) and 0 or g_counter_alert_delay[g_tablePostfix[i]];


      if g_counter_alert_reset_delayDownCounter[g_tablePostfix[i]] == nil or firstCall then
        g_counter_alert_reset_delayDownCounter[g_tablePostfix[i]] = DownCounter.create();
        g_counter_alert_reset_delayDownCounter[g_tablePostfix[i]]:freeze()
      else
        g_counter_alert_reset_delayDownCounter[g_tablePostfix[i]]:updateParams(g_setting_alert_reset_delay[g_tablePostfix[i]] * 1000);
      end

      g_counter_alert_reset_delay[g_tablePostfix[i]] = g_counter_alert_reset_delayDownCounter[g_tablePostfix[i]]:timeTo0() / 1000;
      g_counter_alert_reset_delay[g_tablePostfix[i]] = (g_counter_alert_reset_delay[g_tablePostfix[i]] < 0) and 0 or g_counter_alert_reset_delay[g_tablePostfix[i]];


      if g_setting_alert_enabled[g_tablePostfix[i]] == 1 then
        if g_counter_alert_count[g_tablePostfix[i]] >= g_setting_service_block_count[g_tablePostfix[i]] then

          g_counter_alert_reset_delayDownCounter[g_tablePostfix[i]]:updateParams(0);
          g_counter_alert_reset_delayDownCounter[g_tablePostfix[i]]:reset();
          g_counter_alert_reset_delayDownCounter[g_tablePostfix[i]]:freeze()

          g_counter_alert_delayDownCounter[g_tablePostfix[i]]:updateParams(0);
          g_counter_alert_delayDownCounter[g_tablePostfix[i]]:reset();
          g_counter_alert_delayDownCounter[g_tablePostfix[i]]:freeze()

          counter_alert_state = 1;
          output = setting_output_alert_state;

        else
          if g_input_alert_signal[g_tablePostfix[i]] == 1 then
            g_counter_alert_reset_delayDownCounter[g_tablePostfix[i]]:reset();
            g_counter_alert_reset_delayDownCounter[g_tablePostfix[i]]:unfreeze()

            g_counter_alert_delayDownCounter[g_tablePostfix[i]]:reset();
            g_counter_alert_delayDownCounter[g_tablePostfix[i]]:unfreeze()
          end
        end
      else
        g_counter_alert_count[g_tablePostfix[i]] = 0

        g_counter_alert_delayDownCounter[g_tablePostfix[i]]:updateParams(0);
        g_counter_alert_delayDownCounter[g_tablePostfix[i]]:reset();
        g_counter_alert_delayDownCounter[g_tablePostfix[i]]:freeze()

        g_counter_alert_reset_delayDownCounter[g_tablePostfix[i]]:updateParams(0);
        g_counter_alert_reset_delayDownCounter[g_tablePostfix[i]]:reset();
        g_counter_alert_reset_delayDownCounter[g_tablePostfix[i]]:freeze()
      end

      if math.floor(g_counter_alert_reset_delay[g_tablePostfix[i]]) == 1 then
        g_counter_alert_count[g_tablePostfix[i]] = 0

        g_counter_alert_reset_delayDownCounter[g_tablePostfix[i]]:updateParams(0);
        g_counter_alert_reset_delayDownCounter[g_tablePostfix[i]]:reset();
        g_counter_alert_reset_delayDownCounter[g_tablePostfix[i]]:freeze()
      end

      if g_counter_alert_delay[g_tablePostfix[i]] ~= 0 then
        output = setting_output_alert_state;
      end

      if math.floor(g_counter_alert_delay[g_tablePostfix[i]]) == 1 then
        g_counter_alert_count[g_tablePostfix[i]] = g_counter_alert_count[g_tablePostfix[i]] + 1

        g_counter_alert_delayDownCounter[g_tablePostfix[i]]:updateParams(0);
        g_counter_alert_delayDownCounter[g_tablePostfix[i]]:reset();
        g_counter_alert_delayDownCounter[g_tablePostfix[i]]:freeze()        
      end

      --g_counter_alert_reset_delayDownCounter[g_tablePostfix[i]]:reset();

      --print (g_tablePostfix[i], g_counter_alert_delay[g_tablePostfix[i]])
      --print ("counter.alert.delay." .. g_tablePostfix[i])

      setLogicValue("counter.alert.delay." .. g_tablePostfix[i], g_counter_alert_delay[g_tablePostfix[i]]);
      setLogicValue("counter.alert.reset.delay." .. g_tablePostfix[i], g_counter_alert_reset_delay[g_tablePostfix[i]]);
      setLogicValue("counter.alert.count." .. g_tablePostfix[i], g_counter_alert_count[g_tablePostfix[i]]);

    end

    --print('ok')
  end

  setLogicValue("setting.alert.reset", 0);
  setLogicValue("counter.alert.state", counter_alert_state);
  setLogicValue("output", output);

end
