local classlib = require("core.class")
local tablex = require("core.tablex")
local consts = require("ui.consts")
local hooks = require("ui.hooks")
local priorityList = require("ui.priorityList")
local util = require("ui.util")
local UI = game.UI
local SequenceStage = classlib.Class("SequenceStage")
local g_uniqueID = 1
local GetNextUniqueID = function()
  local newID = g_uniqueID
  g_uniqueID = (g_uniqueID + 1) % 8192
  return newID
end
function SequenceStage:init(sequence, fsmState, parentGO, args)
  self._sequence = sequence
  self._fsmState = fsmState
  self._parentGO = parentGO
  self._name = args.StageName
  assert(self._name ~= nil, "Stage name missing in stage args")
  self._timerID = self._name .. GetNextUniqueID()
  self._unlockTimerID = self._timerID .. "lock"
  self._timer = args.Timer
  self._unlockTimer = args.UnlockTimer
  self._anims = args.Anims
  self._onStartCallback = args.StartCallback
  self._onStartCallbackArgs = args.StartCallbackArgs
  self._onTimeoutCallback = args.TimeoutCallback
  self._onTimeoutCallbackArgs = args.TimeoutCallbackArgs
end
local GetChildGOFromPath = function(goPath, goParent, showGOs)
  local childGO
  if goPath ~= nil then
    local goNames = {}
    local nameCount = 1
    local strStart = 1
    local splitCharByte = string.byte("|")
    for i = 1, #goPath do
      if goPath:byte(i) == splitCharByte then
        goNames[nameCount] = string.sub(goPath, strStart, i - 1)
        strStart = i + 1
        nameCount = nameCount + 1
      end
    end
    goNames[nameCount] = string.sub(goPath, strStart, #goPath)
    local goItr = goParent
    for _, goName in ipairs(goNames) do
      childGO = goItr:FindSingleGOByName(goName)
      assert(childGO ~= nil, "Unable to find Game-object:" .. goName .. " From path:" .. goPath)
      if showGOs == true then
        childGO:Show()
      end
      goItr = childGO
    end
  end
  return childGO
end
local ProcessAnim = function(parentGO, animTable)
  local goPath = animTable.GOPath
  assert(goPath ~= nil, "Game-object animation missing path")
  local callShowGO = true
  local childGO = GetChildGOFromPath(goPath, parentGO, callShowGO)
  assert(childGO ~= nil, "Unable to find Game-object with path:" .. goPath)
  local doFade = util.GetValueWithDefault(animTable.DoFade, true)
  local fadeValue = util.GetValueWithDefault(animTable.FadeValue, 1)
  local fadeTime = util.GetValueWithDefault(animTable.FadeTime, 0)
  local doAnimate = util.GetValueWithDefault(animTable.DoAnimate, true)
  local animStyle = util.GetValueWithDefault(animTable.AnimStyle, consts.AS_Forward)
  local animName = util.GetValueWithDefault(animTable.AnimName, "")
  local animRate = util.GetValueWithDefault(animTable.AnimRate, 1)
  local animStart = util.GetValueWithDefault(animTable.AnimStart, 0)
  local animEnd = util.GetValueWithDefault(animTable.AnimEnd, -1)
  local animSound = util.GetValueWithDefault(animTable.Sound, nil)
  if doFade then
    game.UI.AlphaFade(childGO, fadeValue, fadeTime)
  end
  if doAnimate then
    game.UI.Anim(childGO, animStyle, animName, animRate, animStart, animEnd)
  end
  if animSound ~= nil then
    game.Audio.PlaySound(animSound)
  end
  if animTable.ProcessCallback ~= nil then
    animTable.ProcessCallback(childGO, animTable)
  end
end
function SequenceStage:Start()
  local userCallbackResult = true
  local waitForTimer = false
  if self._onStartCallback ~= nil and self._onStartCallback(self, self._onStartCallbackArgs) == false then
    userCallbackResult = false
  end
  if userCallbackResult == true then
    if self._anims ~= nil then
      for _, anim in ipairs(self._anims) do
        ProcessAnim(self._parentGO, anim)
      end
    end
    if self._timer ~= nil and self._timer > 0 then
      self._fsmState:StartTimer(self._timerID, self._timer, function()
        self:Handle_TimerFinished()
      end)
      waitForTimer = true
    end
    if self._unlockTimer ~= nil then
      if 0 < self._unlockTimer then
        self._fsmState:StartTimer(self._unlockTimerID, self._unlockTimer, function()
          self._sequence._othersCanPlay = true
          if self._unlockTimer ~= nil and self._unlockTimer > 0 then
            self._fsmState:DeleteTimer(self._unlockTimerID)
            self._unlockTimer = nil
          end
        end)
      elseif self._unlockTimer == 0 then
        self._sequence._othersCanPlay = true
      end
    end
  end
  return self._name, waitForTimer
end
function SequenceStage:Handle_TimerFinished()
  if self._onTimeoutCallback ~= nil and self._onTimeoutCallback(self, self._onTimeoutCallbackArgs) == false then
    return
  end
  if self._timer ~= nil and self._timer > 0 then
    self._fsmState:DeleteTimer(self._timerID)
    self._timer = nil
  end
  self._sequence:Handle_StageFinished(self)
end
local Sequence = classlib.Class("Sequence")
function Sequence:init(name, manager, fsmState, args)
  self._name = name
  self._manager = manager
  self._fsmState = fsmState
  self._initStages = args.InitStages
  local parentGOName = args.ParentGOName
  self._parentGO = util.GetUiObjByName(parentGOName)
  assert(self._parentGO ~= nil, "Sequence parent-game-object is nil")
  self._parentGO:Hide()
  self._onActivateCallback = args.ActivateCallback
  self._onActivateCallbackArgs = args.ActivateCallbackArgs
  self._onTerminateCallback = args.TerminateCallback
  self._onTerminateCallbackArgs = args.TerminateCallbackArgs
  self._onAddStageCallback = args.AddStageCallback
  self._onAddStageCallbackArgs = args.AddStageCallbackArgs
  self._onPausedChanged = args.PausedChanged
  self._onCinematicBegan = args.CinematicBegan
  self._stageDataQueue = {}
  self._activeStage = nil
  self._activeStageName = nil
  self._waitingOnStage = true
  self._currentPriority = nil
end
function Sequence:Start(newStages, currentPriority)
  if self._onActivateCallback ~= nil and self._onActivateCallback(self, self._onActivateCallbackArgs, newStages) == false then
    return
  end
  self._parentGO:Show()
  game.UI.AlphaFade(self._parentGO, 1, 0)
  self._waitingOnStage = true
  self._currentPriority = currentPriority
  self._othersCanPlay = false
  tablex.Clear(self._stageDataQueue)
  for _, initStage in ipairs(self._initStages) do
    tablex.FastInsert(self._stageDataQueue, initStage, #self._stageDataQueue + 1)
  end
  self:AddStages(newStages)
  self:StartNextStage()
end
function Sequence:AddStages(newStages)
  if self._onAddStageCallback ~= nil and self._onAddStageCallback(self, self._onAddStageCallbackArgs, newStages) == false then
    return
  end
  for _, newStage in ipairs(newStages) do
    tablex.FastInsert(self._stageDataQueue, newStage, #self._stageDataQueue + 1)
  end
  if not self._waitingOnStage then
    self:StartNextStage()
  end
end
function Sequence:Terminate()
  if self._onTerminateCallback ~= nil and self._onTerminateCallback(self, self._onTerminateArgs) == false then
    return
  end
  self._parentGO:Hide()
  self._currentPriority = nil
  self._manager:Handle_SequenceFinished(self)
end
function Sequence:PausedChanged(gamePaused, uiPaused)
  if self._onPausedChanged ~= nil then
    self._onPausedChanged(self, gamePaused, uiPaused)
  end
end
function Sequence:StartNextStage()
  while #self._stageDataQueue > 0 do
    local stageArgs = table.remove(self._stageDataQueue, 1)
    local newStage = SequenceStage.New(self, self._fsmState, self._parentGO, stageArgs)
    local stageName, waitForTimer = newStage:Start()
    self._activeStage = newStage
    self._activeStageName = stageName
    self._waitingOnStage = waitForTimer
    if waitForTimer == true then
      break
    end
  end
  if #self._stageDataQueue == 0 and not self._waitingOnStage then
    self:Terminate()
  end
end
function Sequence:Handle_StageFinished(stage)
  self._activeStage = nil
  self._activeStageName = nil
  if #self._stageDataQueue > 0 then
    self._waitingOnStage = false
    self:StartNextStage()
  else
    self:Terminate()
  end
end
function Sequence:UninstallHookTokens()
  if self._hookTokens ~= nil then
    for index, token in pairs(self._hookTokens) do
      hooks.UninstallWithMachineIndex(index, self._fsmState:GetMachineIndex(), token)
    end
    tablex.Clear(self._hookTokens)
    UI.ClearUpHandlerShowing()
  end
end
local LogManager = classlib.Class("LogManager")
function LogManager:init(fsmState)
  self._fsmState = fsmState
  self._sequences = {}
  self._multiDispatch = {}
  self._tracks = {}
  self._queuedMessages = {}
  self._isPaused = false
  self._wasPaused = false
  self._pausedQueue = {}
  self._uiPause = false
  self._disableInputPriorityList = priorityList.PriorityList.New(false)
  self._disableInputPriorityList:AddItem("In Shell", function()
    if UI.GetInScreen(consts.SHELL) then
      return true
    end
    return nil
  end)
  self._disableInputPriorityList:AddItem("In Loading Screen", function()
    if UI.GetInScreen(consts.LOADINGSCR) then
      return true
    end
    return nil
  end)
  self._disableInputPriorityList:AddItem("In Dead Screen", function()
    if UI.GetInScreen(consts.DEADSCR) then
      return true
    end
    return nil
  end)
  self._disableInputPriorityList:AddItem("DesignerMessages")
  self._disableInputPriorityList:AddItem("DeadMenu")
  self._disableInputPriorityList:AddItem("GlobalMenu")
  self._activeSequenceNames = {}
  UI.ClearUpHandled()
  UI.ClearUpHandlerShowing()
end
function LogManager:Register(name, sequenceArgs, allowMultiDispatch, track)
  assert(self._sequences[name] == nil, "Attempting to register Log with duplicated name/ID")
  self._sequences[name] = Sequence.New(name, self, self._fsmState, sequenceArgs)
  self._multiDispatch[name] = allowMultiDispatch
  self._tracks[name] = track
end
function LogManager:HandleTimers(timer, timerID)
  if timer ~= nil and 0 < timer then
    if not self._wasPaused then
      self._fsmState:PauseTimer(timerID)
    else
      self._fsmState:UnpauseTimer(timerID)
    end
  end
end
function LogManager:Update()
  self._isPaused = game.IsPaused() or self._uiPaused
  if self._isPaused ~= self._wasPaused then
    for i = 1, #self._activeSequenceNames do
      local activeSequenceName = self._activeSequenceNames[i]
      local sequence = self._sequences[activeSequenceName]
      assert(sequence ~= nil, "Unable to retrieve active sequence %s", activeSequenceName)
      sequence:PausedChanged(game.IsPaused(), self._uiPaused)
      local activeStage = sequence._activeStage
      if activeStage ~= nil then
        self:HandleTimers(activeStage._timer, activeStage._timerID)
        self:HandleTimers(activeStage._unlockTimer, activeStage._unlockTimerID)
      end
    end
    if self._wasPaused then
      while #self._pausedQueue > 0 do
        local firstMessage = table.remove(self._pausedQueue, 1)
        self:QueueMessage(firstMessage.Name, firstMessage.Args, firstMessage.Priority)
      end
    end
  end
  self._wasPaused = self._isPaused
  if not self._isPaused then
    self:DispatchNextMessage()
  end
end
function LogManager:QueueMessage(logName, message, priority)
  assert(self._sequences[logName] ~= nil, "Attempting to send a message to an unregistered sequence")
  local logMessage = {}
  logMessage.Name = logName
  logMessage.Args = message
  logMessage.Priority = priority
  if self._isPaused then
    tablex.FastInsert(self._pausedQueue, logMessage, #self._pausedQueue + 1)
    return
  else
    local index = 1
    while index <= #self._queuedMessages do
      local messageItr = self._queuedMessages[index]
      if priority < messageItr.Priority then
        break
      end
      index = index + 1
    end
    table.insert(self._queuedMessages, index, logMessage)
  end
end
function LogManager:ShouldRunSequence(sequenceName, sequencePriority)
  local track = self._tracks[sequenceName]
  for i = 1, #self._activeSequenceNames do
    local name = self._activeSequenceNames[i]
    if name == sequenceName then
      return false, self._multiDispatch[name]
    end
    if self._tracks[name] == track or self._sequences[name]._currentPriority == nil or sequencePriority >= self._sequences[name]._currentPriority and self._sequences[name]._othersCanPlay == false then
      return false, false
    end
  end
  return true
end
function LogManager:DispatchNextMessage()
  while #self._queuedMessages > 0 do
    local firstMessage = self._queuedMessages[1]
    local name = firstMessage.Name
    local args = firstMessage.Args
    local priority = firstMessage.Priority
    local shouldRun, allowMultiDispatch = self:ShouldRunSequence(name, priority)
    if shouldRun then
      table.insert(self._activeSequenceNames, name)
      table.remove(self._queuedMessages, 1)
      self._sequences[name]:Start(args, priority)
    elseif allowMultiDispatch then
      table.remove(self._queuedMessages, 1)
      self._sequences[name]:AddStages(args, priority)
    else
      break
    end
  end
end
function LogManager:Handle_SequenceFinished(sequence)
  local idx = tablex.Index(self._activeSequenceNames, sequence._name)
  if idx ~= nil then
    table.remove(self._activeSequenceNames, idx)
  end
end
function LogManager:SetDisableInputItemByName(name, newState)
  return self._disableInputPriorityList:SetItemByName(name, newState)
end
function LogManager:ClearDisableInputItems()
  self._disableInputPriorityList:Clear()
end
function LogManager:IsInputDisabled()
  return self._uiPaused or self._disableInputPriorityList:GetAvailable() == true
end
function LogManager:BroadcastCinematicBegan()
  for i = 1, #self._activeSequenceNames do
    local activeSequenceName = self._activeSequenceNames[i]
    local currSequence = self._sequences[activeSequenceName]
    if currSequence._onCinematicBegan ~= nil then
      currSequence:_onCinematicBegan()
    end
  end
end
function LogManager:Clear()
  for i = 1, #self._activeSequenceNames do
    local activeSequenceName = self._activeSequenceNames[i]
    local currSequence = self._sequences[activeSequenceName]
    currSequence._parentGO:Hide()
    currSequence:UninstallHookTokens()
  end
  tablex.Clear(self._activeSequenceNames)
  tablex.Clear(self._pausedQueue)
  tablex.Clear(self._queuedMessages)
end
return {LogManager = LogManager}
