local tablex = require("core.tablex")
local animationUtil = require("ui.animationUtil")
local consts = require("ui.consts")
local pickupUtil = require("ui.pickupUtil")
local queue = require("ui.queue")
local tutorialConsts = require("ui.tutorialConsts")
local uiCalls = require("ui.uicalls")
local util = require("ui.util")
local UI = game.UI
local tutorialStepQueue = queue.Queue.New()
local currentTutorialStep, currentMessage
local shouldUnpauseGameInMenu = true
local saturationRootGameObjects = {}
local saturationGameObjects = {}
local saturationLists = {}
local controllerUnplugged = false
local EnqueueStep = function(tutorialStep)
  assert(tutorialStep ~= nil, "Attempted to enqueue nil step into the tutorial queue!")
  tutorialStepQueue:Enqueue(tutorialStep)
end
local DequeueStep = function()
  return tutorialStepQueue:Dequeue()
end
local PeekStep = function()
  return tutorialStepQueue:Peek()
end
local ClearStepQueue = function()
  tutorialStepQueue:Clear()
end
local SetupTutorialStepQueue = function(tutorials, tutorialName)
  local tutorial = tutorials[tutorialName]
  assert(tutorial ~= nil, "SetupTutorialStepQueue could not find tutorial named: " .. tostring(tutorialName) .. " to set up!")
  for index, step in ipairs(tutorial) do
    step.TutorialName = tutorialName
    step.Index = index
    EnqueueStep(step)
  end
end
local IsStepQueueEmpty = function()
  return tutorialStepQueue:IsEmpty()
end
local BeginTutorial = function(state)
  if not IsStepQueueEmpty() then
    state:SendEventToUIFsm("tutorialMode", "EVT_BEGIN_TUTORIAL")
  end
end
local SetupAndBeginTutorial = function(state, tutorials, tutorialResource, tutorialName)
  if IsStepQueueEmpty() then
    game.Wallets.AddResource("HERO", tutorialResource, 1, "NO_TELEMETRY")
    SetupTutorialStepQueue(tutorials, tutorialName)
    BeginTutorial(state)
  end
end
local SetCurrentTutorialStep = function(step)
  currentTutorialStep = step
end
local GetCurrentTutorialStep = function()
  return currentTutorialStep
end
local CurrentlyShowingStep = function()
  return currentTutorialStep ~= nil and currentMessage ~= nil
end
local SetCurrentMessage = function(messageRequest)
  currentMessage = messageRequest
  if currentMessage ~= nil then
    shouldUnpauseGameInMenu = currentMessage:get_unpauseWhenComplete()
  end
end
local GetCurrentMessage = function()
  return currentMessage
end
local SetControllerUnplugged = function(unplugged)
  controllerUnplugged = unplugged
end
local AreEventsAllowed = function(eventNames, classObj)
  local areAllowed = false
  if controllerUnplugged then
    return
  end
  if not CurrentlyShowingStep() then
    areAllowed = true
  elseif currentMessage:get_active() then
    local conditionArgs = currentTutorialStep.ConditionArgs
    local allowedEvents = currentTutorialStep.AllowedEvents
    for _, name in ipairs(eventNames) do
      if tablex.Contains(allowedEvents, name) == true then
        areAllowed = true
        if conditionArgs.EventType == tutorialConsts.EVENT_TYPE_HANDLE_EVENT and classObj ~= nil and conditionArgs.Item ~= nil and tablex.Contains(conditionArgs.EventNames, name) == true then
          local item
          if classObj.get_item ~= nil then
            item = classObj:get_item()
          elseif classObj.GetSelectedItem ~= nil then
            item = classObj:GetSelectedItem()
          end
          if conditionArgs.Item == 0 then
            areAllowed = true
          elseif classObj._ItemCompare ~= nil then
            areAllowed = classObj._ItemCompare(conditionArgs.Item, item)
          else
            areAllowed = item == conditionArgs.Item
          end
          if name == "EVT_MOUSE_RELEASED" and classObj.GetSelectedButton ~= nil then
            local button = classObj:GetSelectedButton()
            local selected = UI.GetEventSenderGameObject()
            if button:GetInstancedChildObject() ~= selected then
              areAllowed = false
            end
          end
        end
        break
      end
    end
  end
  return areAllowed
end
local CheckConditionMet = function(state, eventType, item, itemCompareFunc)
  local conditionMet = false
  if CurrentlyShowingStep() then
    local conditionArgs = currentTutorialStep.ConditionArgs
    local currEventType = conditionArgs.EventType
    if eventType == currEventType then
      if currEventType == tutorialConsts.EVENT_TYPE_HANDLE_EVENT then
        conditionMet = tablex.Contains(conditionArgs.EventNames, item)
      elseif conditionArgs.ItemCompareFunc ~= nil then
        conditionMet = conditionArgs.ItemCompareFunc(item)
      else
        local currItem = conditionArgs.Item
        if itemCompareFunc ~= nil then
          conditionMet = itemCompareFunc(currItem, item)
        else
          conditionMet = item == currItem
        end
      end
    end
    if conditionMet then
      state:SendHookToUI("EVT_Tutorial_ConditionMet")
    end
  end
  return conditionMet
end
local HandleEvent = function(classObj, events, handler)
  if classObj:get_active() and handler ~= nil and AreEventsAllowed(events, classObj) == true then
    handler(classObj)
    for _, event in ipairs(events) do
      if CheckConditionMet(classObj._state, tutorialConsts.EVENT_TYPE_HANDLE_EVENT, event, nil) then
        break
      end
    end
  end
end
local ShouldUnpauseGameInMenu = function()
  local unpauseGame = shouldUnpauseGameInMenu
  shouldUnpauseGameInMenu = true
  return unpauseGame
end
local GetCenterOffset = function(step)
  assert(step ~= nil, "GetCenterOffset called with no step!")
  local centerOffset = step.MessageArgs.CenterOffset
  if step.MessageArgs.ScaledCenterOffset ~= nil then
    local scaledCenterOffset = step.MessageArgs.ScaledCenterOffset
    return {
      UI.GetAccessibilityScaling(centerOffset[1], scaledCenterOffset[1]),
      UI.GetAccessibilityScaling(centerOffset[2], scaledCenterOffset[2])
    }
  end
  return centerOffset
end
local CreateCenterOffset = function(xOffset, yOffset)
  assert(xOffset <= tutorialConsts.OFFSET_X_MAX, "Attempted to create center offset for tutorial step of (" .. tostring(xOffset) .. ", " .. tostring(yOffset) .. ") but " .. tostring(xOffset) .. " is larger than " .. tutorialConsts.OFFSET_X_MAX .. "!")
  assert(xOffset >= tutorialConsts.OFFSET_X_MIN, "Attempted to create center offset for tutorial step of (" .. tostring(xOffset) .. ", " .. tostring(yOffset) .. ") but " .. tostring(xOffset) .. " is smaller than " .. tutorialConsts.OFFSET_X_MIN .. "!")
  assert(yOffset <= tutorialConsts.OFFSET_Y_MAX, "Attempted to create center offset for tutorial step of (" .. tostring(xOffset) .. ", " .. tostring(yOffset) .. ") but " .. tostring(yOffset) .. " is larger than " .. tutorialConsts.OFFSET_Y_MAX .. "!")
  assert(yOffset >= tutorialConsts.OFFSET_Y_MIN, "Attempted to create center offset for tutorial step of (" .. tostring(xOffset) .. ", " .. tostring(yOffset) .. ") but " .. tostring(yOffset) .. " is smaller than " .. tutorialConsts.OFFSET_Y_MIN .. "!")
  return {xOffset, yOffset}
end
local CreateOffscreenOffset = function()
  return {-100, -100}
end
local SetPosition = function(messageRequest)
  if CurrentlyShowingStep() then
    local centerOffset = GetCenterOffset(currentTutorialStep)
    local xOffset = 0
    local yOffset = 0
    if centerOffset ~= nil then
      xOffset = centerOffset[1]
      yOffset = centerOffset[2]
    end
    local requestedOffset = engine.Vector.New(xOffset, yOffset, 0)
    animationUtil.Tutorial_DoDefaultTransitionAnim(messageRequest:get_goMessageRequestChild(), requestedOffset)
  end
end
local SetPositionInfoText = function()
  if CurrentlyShowingStep() and game.build.GOLD_VERSION ~= 1 then
    local centerOffset = GetCenterOffset(currentTutorialStep)
    local thHeader = currentMessage:GetTextHandle("Header")
    local posText = "(" .. string.format("%0.2f", centerOffset[1]) .. ", " .. string.format("%0.2f", centerOffset[2]) .. ")"
    currentMessage:SetText(thHeader, posText)
    local thBody = currentMessage:GetTextHandle("Body")
    currentMessage:SetText(thBody, "Tutorial Name: " .. tostring(currentTutorialStep.TutorialName) .. [[

Step Index: ]] .. tostring(currentTutorialStep.Index))
  end
end
local UpdateCenterOffset = function(xOffset, yOffset)
  if CurrentlyShowingStep() and game.build.GOLD_VERSION ~= 1 then
    local centerOffset = GetCenterOffset(currentTutorialStep)
    centerOffset[1] = centerOffset[1] + xOffset
    centerOffset[2] = centerOffset[2] + yOffset
    local goMessageRequestChild = currentMessage:get_goMessageRequestChild()
    local requestedOffset = engine.Vector.New(centerOffset[1], centerOffset[2], 0)
    animationUtil.Tutorial_SetGOTransformInterpolated(goMessageRequestChild, requestedOffset)
    SetPositionInfoText()
  end
end
local CreateFooterButtonInfoTable = function(text, eventNames, eventHandler)
  return {
    {
      Text = text,
      EventHandlers = {
        {Events = eventNames, Handler = eventHandler}
      }
    }
  }
end
local VerifyAllowedEvents = function(tutorialStep)
  local allowedEvents = tutorialStep.AllowedEvents
  local conditionArgs = tutorialStep.ConditionArgs
  local conditionEvents = conditionArgs.EventNames
  if conditionArgs.EventType == tutorialConsts.EVENT_TYPE_HANDLE_EVENT then
    assert(allowedEvents ~= nil and 0 < #allowedEvents, "Tutorial " .. tostring(currentTutorialStep.TutorialName) .. ", Step Index " .. tostring(currentTutorialStep.Index) .. " is using a tutorialUtil.On<event name>Handled() function to create the CondidtionArgs, but no AllowedEvents are set. This step can never complete!")
  elseif conditionEvents ~= nil then
    for _, allowedEvent in ipairs(allowedEvents) do
      for _, conditionEvent in ipairs(conditionEvents) do
        assert(allowedEvent ~= conditionEvent, "Tutorial " .. tostring(currentTutorialStep.TutorialName) .. ", Step Index " .. tostring(currentTutorialStep.Index) .. " has event " .. tostring(allowedEvent) .. " in both the allowed events and condition events! This is unreliable. Please use an appropriate tutorialUtil.On<event name>Handled() function to create the ConditionArgs.")
      end
    end
  end
end
local VerifyMessageArgs = function(tutorialStep)
  local messageArgs = tutorialStep.MessageArgs
  if IsStepQueueEmpty() then
    assert(messageArgs.UnpauseWhenComplete == true, "Tutorial " .. tostring(currentTutorialStep.TutorialName) .. ", Step Index " .. tostring(currentTutorialStep.Index) .. " is the last step and not does not have UnpauseWhenComplete set to true in the messageArgs.")
  end
end
local CreateFooterButtonInfo = function(tutorialStep)
  local messageArgs = tutorialStep.MessageArgs
  local conditionArgs = tutorialStep.ConditionArgs
  local eventType = conditionArgs.EventType
  local eventNames
  VerifyAllowedEvents(tutorialStep)
  VerifyMessageArgs(tutorialStep)
  if eventType == tutorialConsts.EVENT_TYPE_PAD_EVENT then
    eventNames = conditionArgs.EventNames
  else
    eventNames = {
      "EVT_Tutorial_ConditionMet"
    }
  end
  return CreateFooterButtonInfoTable(messageArgs.FooterButtonText, eventNames, tutorialStep.EventHandler)
end
local ConvertAllowedEventsTable = function(requestedEvents)
  for index = #requestedEvents, 1, -1 do
    local currEvent = requestedEvents[index]
    local releaseEvent = consts.RELEASE_EVENTS[currEvent]
    if releaseEvent ~= nil then
      tablex.FastInsert(requestedEvents, releaseEvent, #requestedEvents + 1)
    end
  end
  return requestedEvents
end
local CreateConditionArgsTable = function(advanceType, eventType, eventNames, item, itemCompareFunc)
  return {
    AdvanceType = advanceType,
    EventType = eventType,
    EventNames = eventNames,
    Item = item,
    ItemCompareFunc = itemCompareFunc
  }
end
local CreatePadEventConditionArgsTable = function(eventNames, item)
  return CreateConditionArgsTable(uiCalls.msgParam.ADVANCE_PRESS, tutorialConsts.EVENT_TYPE_PAD_EVENT, eventNames, nil, nil)
end
local OnAdvanceRelease = function()
  return CreatePadEventConditionArgsTable({
    "EVT_Advance_Release"
  })
end
local OnBackRelease = function()
  return CreatePadEventConditionArgsTable({
    "EVT_Back_Release"
  })
end
local OnL3Release = function()
  return CreatePadEventConditionArgsTable({
    "EVT_L3_Release"
  })
end
local OnButtonGainFocus = function(item)
  return CreateConditionArgsTable(uiCalls.msgParam.ADVANCE_PRESS, tutorialConsts.EVENT_TYPE_BUTTON_ON_GAIN_FOCUS, nil, item, nil)
end
local OnButtonGainFocus_IsUpgradeAllowed = function()
  return CreateConditionArgsTable(uiCalls.msgParam.ADVANCE_PRESS, tutorialConsts.EVENT_TYPE_BUTTON_ON_GAIN_FOCUS, nil, nil, pickupUtil.IsUpgradeAllowed)
end
local OnSliderOnComplete = function(item)
  return CreateConditionArgsTable(uiCalls.msgParam.ADVANCE_PRESS, tutorialConsts.EVENT_TYPE_SLIDER_ON_COMPLETE, nil, item, nil)
end
local CreateHandleEventConditionArgsTable = function(eventNames, item)
  return CreateConditionArgsTable(uiCalls.msgParam.ADVANCE_PRESS, tutorialConsts.EVENT_TYPE_HANDLE_EVENT, eventNames, item, nil)
end
local OnAdvanceReleaseHandled = function(item)
  return CreateHandleEventConditionArgsTable({
    "EVT_Advance_Release",
    "EVT_MOUSE_RELEASED"
  }, item)
end
local OnBackReleaseHandled = function()
  return CreateHandleEventConditionArgsTable({
    "EVT_Back_Release"
  })
end
local OnSquareReleaseHandled = function(item)
  return CreateHandleEventConditionArgsTable({
    "EVT_Square_Release"
  }, item)
end
local OnL3ReleaseHandled = function()
  return CreateHandleEventConditionArgsTable({
    "EVT_L3_Release"
  })
end
local RegisterDesaturationObject = function(name, gameObject)
  assert(not util.IsStringNilOrEmpty(name), "RegisterDesaturationObject called with no name!")
  assert(gameObject ~= nil, "RegisterDesaturationObject called with no game object!")
  saturationGameObjects[name] = gameObject
end
local RegisterRootDesaturationObject = function(name, gameObject)
  assert(not util.IsStringNilOrEmpty(name), "RegisterRootDesaturationObject called with no name!")
  assert(gameObject ~= nil, "RegisterRootDesaturationObject called with no game object!")
  saturationRootGameObjects[name] = gameObject
  RegisterDesaturationObject(name, gameObject)
end
local RegisterDesaturationList = function(name, list)
  assert(not util.IsStringNilOrEmpty(name), "RegisterDesaturationObject called with no name!")
  assert(list ~= nil, "RegisterDesaturationObject called with no list!")
  saturationLists[name] = list
  RegisterDesaturationObject(name, list:GetButtonRootGameObject())
end
local ResetDesaturation = function(gameObject)
  assert(gameObject ~= nil, "ResetDesaturation called with no game object!")
  UI.Desaturation(gameObject, 0, true)
end
local ResetDesaturationAll = function()
  for _, goRoot in pairs(saturationRootGameObjects) do
    ResetDesaturation(goRoot)
  end
end
local ResetDesaturationWhitelist = function()
  local goCurrentMessage = currentMessage:get_goMessageRequest()
  ResetDesaturation(goCurrentMessage)
  local resaturateTable = currentTutorialStep.ResaturateTable
  if resaturateTable ~= nil then
    local gameObjectNames = resaturateTable.GameObjectNames
    if gameObjectNames ~= nil then
      for _, name in pairs(gameObjectNames) do
        local gameObject = saturationGameObjects[name]
        if gameObject ~= nil then
          ResetDesaturation(gameObject)
        end
      end
    end
    local items = resaturateTable.Items
    if items ~= nil then
      for _, item in pairs(items) do
        for _, list in pairs(saturationLists) do
          local buttonArray = list:GetButtons()
          for _, button in ipairs(buttonArray) do
            if button:HasItem(item) then
              local goButton = button:GetGameObject()
              if goButton ~= nil then
                ResetDesaturation(goButton)
              end
              break
            end
          end
        end
      end
    end
  end
end
local SetDesaturation = function(gameObject)
  assert(gameObject ~= nil, "SetDesaturation called with no game object!")
  local desaturationLevel = 0
  if CurrentlyShowingStep() then
    desaturationLevel = tutorialConsts.DesaturationLevel
  end
  UI.Desaturation(gameObject, desaturationLevel, true)
end
local SetDesaturationAll = function()
  for _, goRoot in pairs(saturationRootGameObjects) do
    SetDesaturation(goRoot)
  end
  ResetDesaturationWhitelist()
end
return {
  EnqueueStep = EnqueueStep,
  DequeueStep = DequeueStep,
  PeekStep = PeekStep,
  ClearStepQueue = ClearStepQueue,
  SetupTutorialStepQueue = SetupTutorialStepQueue,
  IsStepQueueEmpty = IsStepQueueEmpty,
  BeginTutorial = BeginTutorial,
  SetupAndBeginTutorial = SetupAndBeginTutorial,
  GetCurrentTutorialStep = GetCurrentTutorialStep,
  SetCurrentTutorialStep = SetCurrentTutorialStep,
  CurrentlyShowingStep = CurrentlyShowingStep,
  SetCurrentMessage = SetCurrentMessage,
  GetCurrentMessage = GetCurrentMessage,
  AreEventsAllowed = AreEventsAllowed,
  CheckConditionMet = CheckConditionMet,
  HandleEvent = HandleEvent,
  ShouldUnpauseGameInMenu = ShouldUnpauseGameInMenu,
  SetControllerUnplugged = SetControllerUnplugged,
  GetCenterOffset = GetCenterOffset,
  CreateCenterOffset = CreateCenterOffset,
  CreateOffscreenOffset = CreateOffscreenOffset,
  SetPosition = SetPosition,
  SetPositionInfoText = SetPositionInfoText,
  UpdateCenterOffset = UpdateCenterOffset,
  CreateFooterButtonInfoTable = CreateFooterButtonInfoTable,
  CreateFooterButtonInfo = CreateFooterButtonInfo,
  ConvertAllowedEventsTable = ConvertAllowedEventsTable,
  OnAdvanceRelease = OnAdvanceRelease,
  OnBackRelease = OnBackRelease,
  OnL3Release = OnL3Release,
  OnButtonGainFocus = OnButtonGainFocus,
  OnButtonGainFocus_IsUpgradeAllowed = OnButtonGainFocus_IsUpgradeAllowed,
  OnSliderOnComplete = OnSliderOnComplete,
  OnAdvanceReleaseHandled = OnAdvanceReleaseHandled,
  OnBackReleaseHandled = OnBackReleaseHandled,
  OnSquareReleaseHandled = OnSquareReleaseHandled,
  OnL3ReleaseHandled = OnL3ReleaseHandled,
  RegisterDesaturationObject = RegisterDesaturationObject,
  RegisterRootDesaturationObject = RegisterRootDesaturationObject,
  RegisterDesaturationList = RegisterDesaturationList,
  ResetDesaturation = ResetDesaturation,
  ResetDesaturationAll = ResetDesaturationAll,
  ResetDesaturationWhitelist = ResetDesaturationWhitelist,
  SetDesaturation = SetDesaturation,
  SetDesaturationAll = SetDesaturationAll
}
