local profile = require("core.profile")
local param_check = function(param, name)
  if param == nil then
    if name and type(name) == "string" then
      engine.Error("Required parameter '", name, "' is nil")
    else
      engine.Error("Required parameter is nil")
    end
  end
end
local function GetChildFunctionRecurvsive(obj, function_name)
  if obj == nil then
    return nil
  end
  if obj.LuaObjectScript == nil or obj.LuaObjectScript._G[function_name] == nil then
    local temp_obj = GetChildFunctionRecurvsive(obj.Child, function_name)
    if temp_obj ~= nil then
      return temp_obj
    end
  end
  if obj.LuaObjectScript and obj.LuaObjectScript._G[function_name] then
    return obj
  end
  return nil
end
local visit_callbacks = function(level, obj, eventAttr, debugEventName, visitorFn, ...)
  if eventAttr == nil or eventAttr == "" then
    return
  end
  for functionToTrigger in string.gmatch(eventAttr, "[^,]+") do
    functionToTrigger = string.gsub(functionToTrigger, "%s+", "")
    local bangIndex = string.find(functionToTrigger, "!")
    local colonIndex = string.find(functionToTrigger, ":")
    if bangIndex ~= nil then
      local wadName = string.sub(functionToTrigger, 1, bangIndex - 1)
      local objName, functionName
      if colonIndex then
        objName = string.sub(functionToTrigger, bangIndex + 1, colonIndex - 1)
        functionName = string.sub(functionToTrigger, colonIndex + 1)
      else
        functionName = string.sub(functionToTrigger, bangIndex + 1)
      end
      local mockFunc = function(t, ...)
        local target = game.FindLevel(wadName)
        if not target then
          error("Can't find target level '", wadName, "' for callback '", functionToTrigger, "') from script on object '", obj, "'")
        end
        if objName then
          target = target:FindSingleGameObject(objName)
        end
        if not target then
          error("Can't find target object '", objName, "' for callback '", functionToTrigger, "') from script on object '", obj, "'")
        end
        target:CallScript(functionName, ...)
      end
      visitorFn(level, obj, eventAttr, debugEventName, mockFunc, ...)
    elseif colonIndex ~= nil then
      local objName = string.sub(functionToTrigger, 1, colonIndex - 1)
      local functionName = string.sub(functionToTrigger, colonIndex + 1)
      if string.sub(objName, 1, 6) == "_Quest" then
        local questName = functionName
        local mockFunc
        if objName == "_QuestStart" then
          function mockFunc(t, ...)
            if game.QuestManager.GetQuestState(questName) == "Inactive" then
              game.QuestManager.StartQuest(questName)
            end
          end
        elseif objName == "_QuestComplete" then
          function mockFunc(t, ...)
            if game.QuestManager.GetQuestState(questName) == "Active" then
              game.QuestManager.ProposeQuestCompleted(questName)
            end
          end
        end
        visitorFn(level, obj, eventAttr, debugEventName, mockFunc, ...)
      else
        local objRef
        if objName == "_Parent" then
          if obj.Parent.IsRefNode then
            objRef = obj.Parent.Parent
          else
            objRef = obj.Parent
          end
        elseif objName == "_GrandParent" then
          if obj.Parent.Parent.Parent.IsRefNode then
            objRef = obj.Parent.Parent.Parent.Parent
          else
            objRef = obj.Parent.Parent.Parent
          end
        else
          objRef = level:FindSingleGameObject(objName)
        end
        local child_objRef = GetChildFunctionRecurvsive(objRef, functionName)
        if child_objRef ~= nil then
          objRef = child_objRef
        end
        if objRef == nil then
          error("Can't find target object '", objName, "' for callback '", functionToTrigger, "') from script on object '", obj, "'")
        end
        local funcRef = objRef.LuaObjectScript._G[functionName]
        if not funcRef then
          error("Can't find target function '", functionName, "' for callback '", functionToTrigger, "') from script on object '", obj, "'")
        end
        visitorFn(level, obj, eventAttr, debugEventName, funcRef, ...)
      end
    else
      local funcRef = _G[functionToTrigger]
      if not funcRef then
        error("Can't find callback function '", functionToTrigger, "' in level script (", level, ") from script on object '", obj, "'")
      end
      visitorFn(level, obj, eventAttr, debugEventName, funcRef, ...)
    end
  end
end
local visit_and_execute_callback = function(level, obj, eventAttr, debugEventName, funcRef, ...)
  funcRef(...)
end
local visit_and_store_callback = function(level, obj, eventAttr, debugEventName, funcRef, store_table)
  store_table[#store_table + 1] = funcRef
end
local ExtractAndExecuteCallbacksForEvent = function(level, obj, eventAttr, debugEventName, ...)
  if eventAttr == nil or eventAttr == "" then
    return
  end
  visit_callbacks(level, obj, eventAttr, debugEventName, visit_and_execute_callback, ...)
end
local ExtractCallbacksForEvent = function(level, obj, eventAttr)
  if eventAttr == nil or eventAttr == "" then
    return
  end
  local functionsToTrigger = {}
  visit_callbacks(level, obj, eventAttr, "?", visit_and_store_callback, functionsToTrigger)
  return functionsToTrigger
end
local PostProcessAttributeCallbacks = function(attributeList, attributes, store, execute, ...)
  for attribute, mode_ in pairs(attributeList) do
    if mode_ == "callback" then
      local callbackList = attributes[attribute]
      if callbackList then
        for key, target in pairs(callbackList) do
          local mode = target.mode
          if mode == 1 then
            local go = target.go
            local function_name = target.function_name
            if execute then
              go:CallScript(function_name, ...)
            end
            if store then
              callbackList[key] = function(t, ...)
                go:CallScript(function_name, ...)
              end
            end
          elseif mode == 2 then
            local level = target.level
            local function_name = target.function_name
            if execute then
              level:CallScript(function_name, ...)
            end
            if store then
              callbackList[key] = function(t, ...)
                level:CallScript(function_name, ...)
              end
            end
          elseif mode == 3 then
            local quest_name = target.quest_name
            if execute and game.QuestManager.GetQuestStateInt(quest_name) == 0 then
              game.QuestManager.StartQuest(quest_name)
            end
            if store then
              callbackList[key] = function(t, ...)
                if game.QuestManager.GetQuestStateInt(quest_name) == 0 then
                  game.QuestManager.StartQuest(quest_name)
                end
              end
            end
          elseif mode == 4 then
            local quest_name = target.quest_name
            if execute and game.QuestManager.GetQuestStateInt(quest_name) == 1 then
              game.QuestManager.ProposeQuestCompleted(quest_name)
            end
            if store then
              callbackList[key] = function(t, ...)
                if game.QuestManager.GetQuestStateInt(quest_name) == 1 then
                  game.QuestManager.ProposeQuestCompleted(quest_name)
                end
              end
            end
          end
        end
      end
    end
  end
end
local ExecuteCallbacksForEvent = function(level, obj, eventCallbacks, debugEventName, ...)
  param_check(level, "Level")
  param_check(obj, "Obj")
  if eventCallbacks ~= nil then
    for _, functionToTrigger in ipairs(eventCallbacks) do
      functionToTrigger(...)
    end
  end
end
local ConvertStringListToTable = function(inputString)
  local stringTable = {}
  for value in string.gmatch(inputString, "[^,]+") do
    value = string.gsub(value, "%s+", "")
    if value ~= "" then
      table.insert(stringTable, value)
    end
  end
  return stringTable
end
local ConvertTableToStringList = function(inputTable)
  local result = ""
  for _, v in ipairs(inputTable) do
    result = result .. tostring(v) .. ","
  end
  result = string.sub(result, 1, string.len(result) - 1)
  return result
end
local FindAnimFrameOrPercentInfoFromLuaTableAttr = function(go, goAttr)
  local animPositionString = go:GetLuaTableAttribute(goAttr)
  if animPositionString ~= nil then
    local animPositionTable = ConvertStringListToTable(animPositionString)
    local valueType = animPositionTable[1]
    local value = animPositionTable[2]
    if tonumber(animPositionTable[2]) ~= nil then
      value = tonumber(animPositionTable[2])
    end
    if animPositionTable[1] ~= "Frame" and animPositionTable[1] ~= "Percent" then
      error("Can't tell whether the value on lua table attribute '" .. tostring(goAttr) .. "' is a frame number or percent. (Level: '" .. ActiveLevel.Name .. ", GameObject: '" .. go:GetName() .. "', attribute name: '" .. tostring(goAttr) .. "')")
    end
    return valueType, value
  else
    return nil
  end
end
local GetAnimFrameOrPercentInfoFromLuaTableAttr = function(go, goAttr)
  local animPositionString = go:GetLuaTableAttribute(goAttr)
  local animPositionTable = ConvertStringListToTable(animPositionString)
  local valueType = animPositionTable[1]
  local value = animPositionTable[2]
  if valueType ~= "Frame" and valueType ~= "Percent" then
    error("Can't tell whether the value on lua table attribute '" .. tostring(goAttr) .. "' is a frame number or percent. (Level: '" .. ActiveLevel.Name .. ", GameObject: '" .. go:GetName() .. "', attribute name: '" .. tostring(goAttr) .. "')")
  end
  return valueType, value
end
local ExecuteSingleCallbackAndReturn = function(level, obj, functionToTrigger, ...)
  if functionToTrigger == nil or functionToTrigger == "" then
    return
  end
  local colonIndex = string.find(functionToTrigger, ":")
  if colonIndex ~= nil then
    local objName = string.sub(functionToTrigger, 1, colonIndex - 1)
    local functionName = string.sub(functionToTrigger, colonIndex + 1)
    local objRef
    if objName == "_Parent" then
      if obj.Parent.IsRefNode then
        objRef = obj.Parent.Parent
      else
        objRef = obj.Parent
      end
    elseif objName == "_GrandParent" then
      if obj.Parent.Parent.Parent.IsRefNode then
        objRef = obj.Parent.Parent.Parent.Parent
      else
        objRef = obj.Parent.Parent.Parent
      end
    else
      objRef = level:FindSingleGameObject(objName)
    end
    local child_objRef = GetChildFunctionRecurvsive(objRef, functionName)
    if child_objRef ~= nil then
      objRef = child_objRef
    end
    if objRef == nil then
      error("Can't find target object '", objName, "' for callback '", functionToTrigger, "') from script on object '", obj, "'")
    end
    local funcRef = objRef.LuaObjectScript._G[functionName]
    if not funcRef then
      error("Can't find target function '", functionName, "' for callback '", functionToTrigger, "') from script on object '", obj, "'")
    end
    return funcRef(...)
  else
    local funcRef = _G[functionToTrigger]
    if not funcRef then
      error("Can't find callback function '", functionToTrigger, "' in level script (", level, ") from script on object '", obj, "'")
    end
    return funcRef(...)
  end
end
return profile.WrapLibrary({
  ExtractAndExecuteCallbacksForEvent = ExtractAndExecuteCallbacksForEvent,
  ExtractCallbacksForEvent = ExtractCallbacksForEvent,
  ExecuteCallbacksForEvent = ExecuteCallbacksForEvent,
  ConvertStringListToTable = ConvertStringListToTable,
  ConvertTableToStringList = ConvertTableToStringList,
  GetAnimFrameOrPercentInfoFromLuaTableAttr = GetAnimFrameOrPercentInfoFromLuaTableAttr,
  FindAnimFrameOrPercentInfoFromLuaTableAttr = FindAnimFrameOrPercentInfoFromLuaTableAttr,
  ExecuteSingleCallbackAndReturn = ExecuteSingleCallbackAndReturn,
  PostProcessAttributeCallbacks = PostProcessAttributeCallbacks
})
