--------------------------------------------------------------------------------
--- Globals
--------------------------------------------------------------------------------
Global("onReaction", { })
Global("onEvent", { })

--------------------------------------------------------------------------------
--- Common Functions
--------------------------------------------------------------------------------
function RegisterReactionHandlers(reactions)
  for k, v in reactions do
  common.RegisterReactionHandler( v, k ) 
  end
end

function RegisterEventHandlers(events)
  for k, v in events do
    common.RegisterEventHandler(v, k)
  end
end

function EqualPlacementPlain(placementPlain1, placementPlain2)
  if not (placementPlain1 or placementPlain2) then
    return false
  end
  if (placementPlain1.alignX ~= placementPlain2.alignX) or
     (placementPlain1.alignY ~= placementPlain2.alignY) or
     (placementPlain1.posX ~= placementPlain2.posX) or
     (placementPlain1.posY ~= placementPlain2.posY) or
     (placementPlain1.highPosX ~= placementPlain2.highPosX) or
     (placementPlain1.highPosY ~= placementPlain2.highPosY) or
     (placementPlain1.sizeX ~= placementPlain2.sizeX) or
     (placementPlain1.sizeY ~= placementPlain2.sizeY) then
    return false
  end
  return true
end

function GetTableSize( t )
    if not t then return 0 end
    return t[0] and table.getn(t)+1 or table.getn(t)
end

function ShowTable(t)
  for k, v in t do
    LogInfo("t[",k,"] = ",v)
  end
end

function CallWhenCharacterAvailable( func )
  if avatar and avatar.IsExist() then
    func()
  else
    common.RegisterEventHandler(func, "EVENT_AVATAR_CREATED")
  end
end

function AsArray(...)
  return arg
end

function ConcatTables(...)
  local result = { }
  for _, t in arg do
    if type(_) == "number" then
      for k, v in t do
        if type(k) == "number" then
          table.insert(result, v)
        end
      end
    end
  end
  return result
end

--- Displays all fields and values ​​as well as the function of the object
function researchObj (obj, tab)
    tab = tab or ""
    tab = tab .. "  "

    --- Restrict recursion
    if string.len (tab)> 50 then
        LogInfo ("recursion is limited!")
        return
    end

    local metaTable = getmetatable (obj)
    if metaTable then
        ---- Show the function of the object
        for k, v in pairs (metaTable) do
            LogInfo (tab, k ,":=", v)
        end
    end
    if type (obj) == "table" then
        ---- Show the fields (variables) of the table
        local containsElements = false
        for k, v in pairs (obj) do
            LogInfo (tab, k ,":=", v, "{", type (v), "}")
            if type (v) == "table" and k ~= "__index" and k ~= "_G" then
                --- "__index" - He was exactly the same table is a nested loop that leads to
                researchObj (v, tab)
            end
            containsElements = true
        end
        if not containsElements then
            LogInfo (tab, "{}")
            return
        end
    else
        LogInfo (tab, "_:=", obj, "{", type (obj), "}")
    end
end 

function PlacementPlainFromRealRect(realRect)
  return { 
           alignX = WIDGET_ALIGN_LOW, 
           alignY = WIDGET_ALIGN_LOW, 
           posX = realRect.x1, 
           posY = realRect.y1,
           highPosX = 0,
           highPosY = 0,
           sizeX = realRect.x2 - realRect.x1,
           sizeY = realRect.y2 - realRect.y1,
           sizingX = 0,
           sizingY = 0
        }
end

function CopyTable(t)
  local result = { }
  for k, v in t do
    result[k] = v
  end
  return result
end

function string.split(str, delim, maxNb)
    -- Eliminate bad cases...
    if string.find(str, delim) == nil then
        return { self }
    end
    if maxNb == nil or maxNb < 1 then
        maxNb = 0    -- No limit
    end
    local result = {}
    local pat = "(.-)" .. delim .. "()"
    local nb = 0
    local lastPos
    for part, pos in string.gfind(str, pat) do
        nb = nb + 1
        result[nb] = part
        lastPos = pos
        if nb == maxNb then break end
    end
    -- Handle the last field
    if nb ~= maxNb then
        result[nb + 1] = string.sub(str, lastPos)
    end
    return result
end
--------------------------------------------------------------------------------
--- WString Functions
--------------------------------------------------------------------------------
function EqualsWString(first, second)
  return common.CompareWString(first, second) == 0
end

-- ChatInput/ChatInput/Field/Input
  local copyEditLine = stateMainForm:CreateWidgetByDesc(
        stateMainForm:GetChildChecked("ChatInput", false)
        :GetChildChecked("ChatInput", false)
        :GetChildChecked("Field", false)
        :GetChildChecked("Input", false)
        :GetWidgetDesc())
  copyEditLine:SetName("EditLine")
  copyEditLine:Show(false)

--- s: WString - The text that will be used for substring
--- index: number - The start index of the text
--- length: number - The maximum length of the result
--- example: Substring(userMods.ToWString("Hello World"), 6, 5)
--- This returns a WString like userMods.ToWString("World")
--- Substring(userMods.ToWString("Hello World"), 0, 5)
--- This returns a WString like userMods.ToWString("Hello")
function Substring(s, index, length)
    -- check input parameters
    if not s and not command.IsWString(s) then
        return nil
    end
    if index < 0 then
        index = 0
    end
    if length < 1 then
        return common.GetEmptyWString()
    end
    -- set the text to the copied editline
    copyEditLine:SetText(s)
    -- get the length of the given text
    local slength = copyEditLine:GetPosObjectCount()
    -- calculate resulting string size
    local resultLength = slength - index
    if resultLength > length then
        resultLength = length
    end
    -- if resultLength smaller than 1, give empty wstring
    if resultLength < 1 then
        return common.GetEmptyWString()
    end
    -- set position of copiesEditLine to begin
    copyEditLine:SetCursorPos(0)
    -- delete chars
    for i = 0, index-1 do
        copyEditLine:DeleteCharAtCursorPos()
    end
    -- calculate amount of chars to be deleted at the end of the text
    local charsToDelete = slength - index - resultLength
    -- set position to the length, then we can delete all chars at the end
    copyEditLine:SetCursorPos(resultLength)
    for i = 0, charsToDelete-1 do
        copyEditLine:DeleteCharAtCursorPos()
    end

    return copyEditLine:GetText()
end

--- merging an array of wstring to one wstring
--- example: Concat(userMods.ToWString("A"), userMods.ToWString("b"), userMods.ToWString("C"))
--- This returns a WString like userMods.ToWString("AbC").
function Concat(...)
    copyEditLine:SetText(common.GetEmptyWString())

    for k, v in arg do
        if v and common.IsWString(v) then
            copyEditLine:InsertTextAtCursorPos(v)
        end
    end

    return copyEditLine:GetText()
end 

--- merging an table of wstring to one wstring
--- example: Concat({ userMods.ToWString("A"), userMods.ToWString("b"), userMods.ToWString("C") })
--- This returns a WString like userMods.ToWString("AbC").
function ConcatFromTable(t)
    copyEditLine:SetText(common.GetEmptyWString())

    for k, v in t do
        if v and common.IsWString(v) then
            copyEditLine:InsertTextAtCursorPos(v)
        end
    end

    return copyEditLine:GetText()
end 

function CreateWStringStream(editLine)
  local result = 
  {
    EditLine = editLine,
    LastRemovedChar = common.GetEmptyWString(),
    LastRemovedCharIsEscaped = false,
    ReadChar = function(self)
      local eL = self.EditLine
      local text = eL:GetText()
      eL:SetMaxSize(1)
      local char = eL:GetText()
      self.LastRemovedChar = char
      eL:SetMaxSize(1000000)
      eL:SetText(text)
      eL:SetCursorPos(0)
      eL:DeleteCharAtCursorPos()
      return char
    end,
    ReadUntil = function(self, untilPredicate, escapeCharacter)
      local text = { }

      local emptyWString = common.GetEmptyWString()
      local char
      while self:CanReadChar() do
        char = self:ReadChar()
        self.LastRemovedCharIsEscaped = false
        if escapeCharacter and EqualsWString(char, escapeCharacter) then
          if not self:CanReadChar() then
            return nil
          end
          char = self:ReadChar()
          self.LastRemovedCharIsEscaped = true
        elseif untilPredicate and untilPredicate(char) then
          break		  
        end
        table.insert(text, char)
      end

      return ConcatFromTable(text)
    end,
    CanReadChar = function(self)
      return self.EditLine:GetPosObjectCount() > 0
    end
  }

  return result
end
--------------------------------------------------------------------------------
--- Timer
--------------------------------------------------------------------------------
function StartTimer( wtTimerTemplate, interval, tickFunction)
  local wtTimer = mainForm:CreateWidgetByDesc(wtTimerTemplate:GetWidgetDesc())
  common.RegisterEventHandler(function(params)
    if params.effectType == ET_FADE and params.wtOwner:IsEqual( wtTimer ) then 
      wtTimer:PlayFadeEffect(0.0, 1.0, interval, EA_MONOTONOUS_INCREASE)
      tickFunction()
    end
  end, "EVENT_EFFECT_FINISHED")
  wtTimer:PlayFadeEffect(0.0, 1.0, interval, EA_MONOTONOUS_INCREASE)
end

--------------------------------------------------------------------------------
--- Animation
--------------------------------------------------------------------------------
-- Plays a list of fade effects in a chain on the widget
-- Parameters:
--   widget: The widget on that the fade effect chain will played
--   values: table of table
--     from: the start value of the effect [0..1]
--     to: the end value of the effect [0..1]
--     timeMs: the duration of the effect (milliseconds)
--     
function PlayFadeEffect(widget, values)
  
end
