--    ======================
--    Description
--    ======================
--
--    Logic operates on values from input.values list and returns one result depends of setting.option
--
--    ======================
--    Parameters
--    ======================
--
--    SubLogic supports following variables:
--
--    input.values                             - input list of all values
--
--    output.value                             - calculated output
--
--    setting.option                           - determinate how output is calculated
--                                               0 - min value from input list
--                                               1 - max value from input list
--                                               2 - if any value from list is different from 0 then output = 1. If all values == 0 then output = 0
--                                               3 - if all values from list is different from 0 then output = 1. If any from values == 0 then output = 0
--                                               4 - if any value from list is different from 0 then output = 0. If all values == 0 then output = 1
--                                               5 - if any value from list is changed since setting.timer.time then output = 1.
--                                                   If all values are not changed i this time then output = 0
--                                               6 - if any value from list is not changed since setting.timer.time then output = 1.
--                                                   If all values are changed i this time then output = 0
--                                               7 - if there is more than (setting.value) quantity more or equal values from list different from 0 then output = 1. 
--                                                   For example if more or equal than 4 values form list are different from 0 then output = 1 else output = 0.
--                                               8 - result is addition of all values from list
--
--    setting.option                           - determinate how output is calculated
--
--    setting.value                            - value for calculating output for options
--                                               7 - it is quantity of values higher than 0 for output=1
--
--    setting.timer.time                       - timer value
--
--    counter.timer.downcounter                - current value of timer
--
--    counter.value                            - auxilary counter depends of options
--                                               7 - current quantity of values from list higher than 0
--
--    Logic uses lua language to implement own behaviour
--
--    ======================
--    mandatory variables
--    ======================
--
--    Logic expects following mandatory variables:
--
--    reload.trigger                          - causes reloading lua script
--
--    memcnt                                  - current amount of memory used by lua in bytes
--
--    Logic expects following kv settings:
--
--    LuaScriptPath                           - path to the lua script - must be absolute
--
--    ======================
--    ChangeLog
--    ======================
--
--    2022-10-18 ver 0.3.8.0
--
--    + fix dla setting.option == 3
--
--    2018-09-26 ver 0.3.7.0
--
--    + fix dla setting.option == 4
--
--    2018-05-03 ver 0.3.6.0
--
--    + setting.option == 8
--
--    2017-12-12 ver 0.2.5.0
--
--    + setting.option == 7
--
--    2017-12-02 ver 0.1.4.0
--
--    + setting.option == 5
--    + setting.option == 6
--
--    2017-07-25 ver 0.0.3.0
--
--    + setting.option == 4
--
--    2017-06-07 ver 0.0.2.825
--
--    # automatic build support
--
--    2017-04-13 ver 0.0.1
--
--    + setting.option == 3
--
--    2017-03-24 ver 0.0.0
--
--    + First release
--
-- 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";

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

g_counterTimerDowncounter = nil;
g_oldValuesList = {};
g_changedValuesList = {};
g_firstrun = true;

SUPPORTED_SUBLOGIC_TYPE = "ListUtils";
SUPPORTED_SUBLOGIC_VERSION = "0.3.8";
g_versionChecked = false;

-- 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 inputValueList = getVarList("input.values");
  local settingOption = getLogicValue("setting.option");
  local settingValue = getLogicValue("setting.value");
  local settingTimerTime = getLogicValue("setting.timer.time");
  local output = 0;
  local counterTimerDowncounter = 0;
  local counterValue = 0;
  local allChanged = true;

  -- timers

  if g_counterTimerDowncounter == nil or firstCall then
    g_counterTimerDowncounter = DownCounter.create();
  end
  g_counterTimerDowncounter:updateParams(settingTimerTime * 1000);

--  if g_counterTimerDowncounter:elapsed() then
--    g_counterTimerDowncounter:reset();
--  end

  counterTimerDowncounter = g_counterTimerDowncounter:timeTo0() / 1000;
  counterTimerDowncounter = (counterTimerDowncounter < 0) and 0 or counterTimerDowncounter;

  -- przy pierwszym uruchomieniu wypełnij tablice starych wartości
  if g_firstrun == true then
    if inputValueList ~= nil then
      for var, value in pairs(inputValueList) do
        g_oldValuesList[var] = value;
        g_changedValuesList[var] = false
      end
    end
  end

  -- właściwe działanie
  if inputValueList ~= nil then

    -- domyślny stan outputa
    if (settingOption == 3) then
        output = 1;
    end

    for var, value in pairs(inputValueList) do

      if (settingOption == 0) then

        if (g_firstrun == true) then
          output = value;
          g_firstrun = false;
        end

        if value < output then
          output = value;
        end

      elseif (settingOption == 1) then

        if (g_firstrun == true) then
          output = value;
          g_firstrun = false;
        end

        if value > output then
          output = value;
        end

      elseif (settingOption == 2) then

        if (g_firstrun == true) then
          output = 0;
          g_firstrun = false;
        end

        if value ~= 0 then
          output = 1;
        end

      elseif (settingOption == 3) then

        if (g_firstrun == true) then
          output = 1;
          g_firstrun = false;
        end

        if value == 0 then
          output = 0;
        end

      elseif (settingOption == 4) then

        if (g_firstrun == true) then
          output = 0;
          g_firstrun = false;
        end
        
        if value ~= 0 then
          output = 1;
        end

      elseif (settingOption == 5) then

        if (g_firstrun == true) then
          output = 0;
          g_firstrun = false;
        end
        
        if g_oldValuesList['' .. var] ~= value then
          g_counterTimerDowncounter:reset();
        end

      elseif (settingOption == 6) then

        if (g_firstrun == true) then
          output = 0;
          g_firstrun = false;
        end

        if g_oldValuesList['' .. var] ~= value then
          g_changedValuesList['' .. var] = true;
        end
        

      elseif (settingOption == 7) then

        if (g_firstrun == true) then
          output = 0;
          g_firstrun = false;
        end

        if value > 0 then
          counterValue = counterValue + 1;
        end

      elseif (settingOption == 8) then

        if (g_firstrun == true) then
          output = 0;
          g_firstrun = false;
        end

        output = output + value;

      end
    end
  end
 
  -- wartość wyjścia
  if (settingOption == 4) then
    if (output == 0) then
      output = 1;
    else
      output = 0;
    end
  elseif (settingOption == 5) then
    if g_counterTimerDowncounter:elapsed() then
      output = 1;
    else
      output = 0;
    end
  elseif (settingOption == 6) then
    for var, value in pairs(inputValueList) do
      if g_changedValuesList[var] == false then
        allChanged = false
      end
    end
    
    if allChanged == true then
      g_counterTimerDowncounter:reset();

      if inputValueList ~= nil then
        for var, value in pairs(inputValueList) do
          g_changedValuesList[var] = false
        end
      end
    end

    if g_counterTimerDowncounter:elapsed() then
      output = 1;
    else
      output = 0;
    end
  elseif (settingOption == 7) then
    if counterValue >= settingValue then
      output = 1;
    else
      output = 0;
    end
  end;

  -- wypełnij tablice starych wartości
  if inputValueList ~= nil then
    for var, value in pairs(inputValueList) do
      g_oldValuesList[var] = value;
    end
  end

  setLogicValue("output.value", output);
  setLogicValue("counter.value", counterValue);
  setLogicValue("counter.timer.downcounter", counterTimerDowncounter);

end
