local manager = class("guide.guideManager")
local tag = manager.getName()
manager:importPartialClass(require("modules.guide.guideManager_parser"))
manager:importPartialClass(require("modules.guide.guideManager_checker"))
local guideGroupTpl = L_GameTpl:getGuideGroupTpl()
local guideTpl = L_GameTpl:getGuideTpl()
local isShowSkip = L_Config:getConfig("game", "GUIDE_UI_SKIP_BUTTON_DISPLAY")

function manager:ctor()
  self._event = require("core.event").new()
  
  function self._updateHandler()
    self:update()
  end
  
  self:enableUpdate()
  self._bavTreeData = {}
  self._guideStatus = {}
  self._guideFinishedCallback = {}
  self._localGuideIdCache = {}
  self.guideTpl = {}
  self.isActive = true
  self.isDebugActive = false
  self.blockStatus = false
  self.guideBlockUis = L_GameTpl:getGameConstTpl():getValue("GUIDE_UI_BLACKLIST")
  self.behaviorWaitCache = {}
  self.restartCache = {}
  self.isDetectSkill = false
end

function manager:onUIShowed(pageName)
  self:updateBlockStatus(pageName, true)
  if pageName == "pageMainCity" then
    self:triggerGuideByParams()
  end
end

function manager:onUIClosed(pageName)
  self:updateBlockStatus(pageName, false)
  if pageName == "loading" then
    self:triggerGuideByParams()
  end
end

function manager:initialize()
  self:stopAllGuide(true)
  self:addEventListener()
end

function manager:addEventListener()
  L_UI:addListener(L_UI.pageEvent.showed, self.onUIShowed, self)
  L_UI:addListener(L_UI.pageEvent.closed, self.onUIClosed, self)
  self.guideHandle = self.guideHandle or handler(self, self.startGuideHandle)
  C_ExternalManager.AddEvent(C_EExternalEventType.TriggerGuideById, self.guideHandle)
  L_UI:addListener(L_UI.pageEvent.opened, self.dealUIGuide, self)
  L_SystemUnlockStore:listenCallFunc(L_SystemUnlockStore.event.systemUnlockGuideEvent, self.onSystemUnlock, self)
  L_SceneManager:addListener(L_SceneManager.event.switchSceneEnd, self.onSwitchSceneEnd, self)
  L_PlayerStore:listenCallFunc(L_PlayerStore.event.playerLevelUp, self.onPlayerLevelUp, self)
  L_TechnologyStore:listenCallFunc(L_TechnologyStore.event.refresh, self.onTechnologyRefresh, self)
  self.onEvent_OnSceneLoadStart = handler(self, self.onEvent_OnSceneLoadStart)
  C_WorldEvent.instance:Listen(C_EWorldEvent.OnSceneLoadStart, self.onEvent_OnSceneLoadStart)
  L_Net:RegisterGameConnectEvent(C_EGameConnectStatus.FullReconnected, handler(self, self.onReconnected))
  self._onEvent_storyStartHandle = self._onEvent_storyStartHandle or handler(self, self._onEvent_storyStart)
  self._onEvent_storyEndHandle = self._onEvent_storyEndHandle or handler(self, self._onEvent_storyEnd)
  C_StoryEvent.instance:Listen(C_EStoryEvent.StoryStart, self._onEvent_storyStartHandle)
  C_StoryEvent.instance:Listen(C_EStoryEvent.StoryEnd, self._onEvent_storyEndHandle)
end

function manager:_onEvent_storyStart()
  self:stopAllGuide()
end

function manager:_onEvent_storyEnd()
  self:restartAllGuide()
end

function manager:onReconnected()
  self:stopAllGuide(true)
  Timer.once(0.1, function()
    self:restartAllGuide()
  end)
end

function manager:onFormationRefresh(fType)
  if fType == L_FormationConst.FormationType.world then
    self:triggerGuideByParams()
  end
end

function manager:onEvent_OnSceneLoadStart()
  self._ESyncFormationServerDataHandler = handler(self, self.onFormationRefresh)
  AzurWorld.formationMgr:RegisterEvent(C_EFormationEvent.ESyncFormationServerData, self._ESyncFormationServerDataHandler)
end

function manager:onPlayerLevelUp()
  self:triggerGuideByParams()
end

function manager:onTechnologyRefresh()
  self:triggerGuideByParams()
end

function manager:onSwitchSceneEnd()
  local sceneId = AzurWorldInstance.CurWorldId
  local params = {
    guideParams = {
      {
        type = L_Const.triggerGuideType.switchScene,
        param = tostring(sceneId)
      }
    }
  }
  self:triggerGuideByParams(params)
end

function manager:onSystemUnlock(systemId)
  local params = {
    guideParams = {
      {
        type = L_Const.triggerGuideType.funcUnlock,
        param = tostring(systemId)
      }
    }
  }
  self:triggerGuideByParams(params)
end

function manager:dealUIGuide(pageName)
  if table.containsValue(self.guideBlockUis, pageName) then
    return
  end
  local params = {
    guideParams = {
      {
        type = L_Const.triggerGuideType.uiOpen,
        param = pageName
      }
    }
  }
  self:triggerGuideByParams(params)
  local page = L_UI:getPage(pageName)
  if page ~= nil and page.options and page.options.buildingGuid then
    local serverData = L_HomeStore:getHomeBuildingByGuid(page.options.buildingGuid)
    local homeBuildingTpl = L_GameTpl:getHomeBuildingTpl()
    local tpl_building = homeBuildingTpl:getTplById(serverData.build_id)
    local groupId = homeBuildingTpl:getGroupId(tpl_building)
    local buildParams = {
      guideParams = {
        {
          type = L_Const.triggerGuideType.buildType,
          param = tostring(groupId)
        }
      }
    }
    self:triggerGuideByParams(buildParams)
  end
end

function manager:triggerGuideByParams(params)
  if #self.restartCache > 0 then
    return
  end
  params = params or {}
  local guidGroupIds = self:parseGuideParams(params)
  if not guidGroupIds then
    return
  end
  for _, groupId in ipairs(guidGroupIds) do
    self:startGuide(groupId)
  end
end

function manager:startGuideHandle(param)
  if param.guideId then
    self._guideFinishedCallback[param.guideId] = param.callBack
    self:startGuide(param.guideId)
    printf(tag, "C#\229\188\149\229\175\188\228\186\139\228\187\182  guideId: " .. param.guideId)
  end
  if param.guideParams then
    local temp = {
      callBack = param.callBack,
      guideParams = {}
    }
    for i = 0, param.guideParams.Count - 1 do
      table.insert(temp.guideParams, param.guideParams[i])
      printf(tag, "C#\229\188\149\229\175\188\228\186\139\228\187\182  type: " .. param.guideParams[i].type .. " param: " .. param.guideParams[i].param)
    end
    self:triggerGuideByParams(temp)
  end
end

function manager:updateBlockStatus(pageName, bShow)
  self.blockStatus = self:isBlockPageOpened(pageName, bShow)
end

function manager:getTpl()
  if table.isEmpty(self.guideTpl) then
    self.guideTpl = L_GameTpl:getGuideGroupTpl()
  end
  return self.guideTpl
end

function manager:setGuideStatus(guideType, isRunning)
  self._guideStatus[guideType] = isRunning
  printf(tag, "setGuideStatus:" .. tostring(isRunning))
end

function manager:getAllGuideStatus()
  for _, v in pairs(self._guideStatus) do
    if v == true then
      return true
    end
  end
  return false
end

function manager:setGuideActive(isActive)
  self.guideActive = isActive
  if isActive then
    L_CommonUtil.setLocalValue("guideActive", 1)
  else
    L_CommonUtil.setLocalValue("guideActive", 0)
  end
end

function manager:canStartGuide()
  return self.guideActive
end

function manager:setGuideDebug(isDebugActive)
  if not isDebugActive and self:isGuideOnRunning(self.debugGuideId) then
    return
  end
  self.isDebugActive = isDebugActive
end

function manager:isOnDebugMode()
  return self.isDebugActive
end

function manager:startAllGuide()
  local active = L_CommonUtil.getLocalValue("guideActive")
  self.guideActive = active == nil or tonumber(active) == 1
end

function manager:forceEndGuide(guideId)
  self:endGuideTree(guideId)
end

function manager:stopAllGuide(isClearAll)
  for guideId, tree in pairs(self._bavTreeData) do
    if tree then
      tree:onReset()
    end
    if not isClearAll then
      table.insert(self.restartCache, guideId)
    end
  end
  self:clearAllControl()
  self._bavTreeData = {}
  self._guideStatus = {}
  self.isDetectSkill = false
end

function manager:clearAllControl()
  L_GuideManager:internalSendEvent(G_GuideClasses.GuideEvent.GuideControlMouse, {key = "guideClick", isShow = false})
  C_InputManager.DisableHighLevelControl()
  C_ScreenTouch.SetEnabled(true)
  L_UI:setTempTopGuideMask(false)
  self:setWorldPause(false)
end

function manager:restartAllGuide()
  for _, guideId in ipairs(self.restartCache) do
    self:startGuide(guideId)
  end
  self.restartCache = {}
end

function manager:dispose()
  self._bavTreeData = {}
  self._guideStatus = {}
  self._guideFinishedCallback = {}
  self._localGuideIdCache = {}
  self.restartCache = {}
  L_UI:removeListener(L_UI.pageEvent.showed, self.onUIShowed, self)
  L_UI:removeListener(L_UI.pageEvent.closed, self.onUIClosed, self)
  self.guideHandle = self.guideHandle or handler(self, self.startGuideHandle)
  C_ExternalManager.RemoveEvent(C_EExternalEventType.TriggerGuideById, self.guideHandle)
  L_UI:removeListener(L_UI.pageEvent.opened, self.dealUIGuide, self)
  L_SystemUnlockStore:unListenCallFunc(L_SystemUnlockStore.event.systemUnlockGuideEvent, self.onSystemUnlock, self)
  L_SceneManager:removeListener(L_SceneManager.event.switchSceneEnd, self.onSwitchSceneEnd, self)
  L_PlayerStore:unListenCallFunc(L_PlayerStore.event.playerLevelUp, self.onPlayerLevelUp, self)
  L_TechnologyStore:unListenCallFunc(L_TechnologyStore.event.refresh, self.onTechnologyRefresh, self)
  self.onEvent_OnSceneLoadStart = handler(self, self.onEvent_OnSceneLoadStart)
  C_WorldEvent.instance:Cancel(C_EWorldEvent.OnSceneLoadStart, self.onEvent_OnSceneLoadStart)
  L_Net:UnregisterGameConnectEvent(C_EGameConnectStatus.FullReconnected, handler(self, self.onReconnected))
  if self._onEvent_storyStartHandle ~= nil then
    C_StoryEvent.instance:Cancel(C_EStoryEvent.StoryStart, self._onEvent_storyStartHandle)
  end
  if self._onEvent_storyEndHandle ~= nil then
    C_StoryEvent.instance:Cancel(C_EStoryEvent.StoryEnd, self._onEvent_storyEndHandle)
  end
end

function manager:startGuide(guideId)
  if not self:checkCanRunGuide(guideId) then
    self:setGuideDebug(false)
    return
  end
  local config = self:getTpl():getTplById(guideId)
  if not self:getTpl():getIsWorking(config) then
    self:setGuideDebug(false)
    return
  end
  if config then
    local isInBattle = C_BattleManager:IsInBattle()
    if self:getTpl():getIsBattle(config) and not isInBattle then
      printf(tag, "guide:", string.format("id: %d\228\187\133\229\156\168\230\136\152\230\150\151\228\184\173\232\167\166\229\143\145", guideId))
      self:setGuideDebug(false)
      return
    end
    if self:getTpl():getIsNoBattle(config) and isInBattle then
      printf(tag, "guide:", string.format("id: %d\228\187\133\229\156\168\233\157\158\230\136\152\230\150\151\228\184\173\232\167\166\229\143\145", guideId))
      self:setGuideDebug(false)
      return
    end
    local path = L_Config:getPathByHash(config.bavTreePath)
    if not string.isEmpty(path) then
      local jsonHandle = C_LuaUtility.LoadTextSync(path)
      if jsonHandle then
        self:dealPriority(guideId)
        printf(tag, "\229\188\128\229\167\139\229\188\149\229\175\188 guideId:" .. guideId)
        local bevTree = L_BevTree:generateBevTree()
        bevTree:initialize(jsonHandle.text, {guideId = guideId})
        bevTree.jsonPath = path
        self._bavTreeData[guideId] = bevTree
        self:dealGuideInBattle()
        C_LuaUtility.DestroyTextAsset(jsonHandle)
      end
    end
  end
end

function manager:testGuide(guideId)
  local config = self:getTpl():getTplById(guideId)
  if config then
    local path = L_Config:getPathByHash(config.bavTreePath)
    if not string.isEmpty(path) then
      local jsonHandle = C_LuaUtility.LoadTextSync(path)
      if jsonHandle then
        printf(tag, "\229\188\128\229\167\139\229\188\149\229\175\188 guideId:" .. guideId)
        local bevTree = L_BevTree:generateBevTree()
        bevTree:initialize(jsonHandle.text, {guideId = guideId})
        bevTree.jsonPath = path
        self._bavTreeData[guideId] = bevTree
        self:dealGuideInBattle()
        C_LuaUtility.DestroyTextAsset(jsonHandle)
      end
    end
  end
end

function manager:checkCanRunGuide(guideId)
  if guideId == nil then
    return false
  end
  if self._bavTreeData[guideId] then
    return false
  end
  if self:checkGuideType(guideId) then
    return false
  end
  if not self:canStartGuide() then
    return false
  end
  if not self:triggerScene(guideId) then
    return false
  end
  self:updateBlockStatus()
  if self.blockStatus then
    if L_LoadingManager:isLoading() then
      table.insert(self.behaviorWaitCache, {guideId = guideId})
    end
    return false
  end
  if not self:isOnDebugMode() and L_GuideStore:isGuideComplete(guideId) then
    if self._guideFinishedCallback[guideId] then
      self._guideFinishedCallback[guideId]()
    end
    self._guideFinishedCallback[guideId] = nil
    return false
  end
  if self:checkFormationHp() then
    return false
  end
  return true
end

function manager:checkFormationHp()
  local heroDic = L_PlayerManager:getPlayerUnit():getAllHero()
  for i, v in pairs(heroDic) do
    if not C_EntityManager.IsEntityDead(v:getCsEntityId()) and C_MyFloatUtility.GetFloat(v:getCsEntity().data.aliveProperty.hp) > 0 then
      return false
    end
  end
  return true
end

function manager:checkGuideType(guideId)
  local tpl = guideGroupTpl:getTplById(guideId)
  local guideType = guideGroupTpl:getGuideType(tpl)
  local curPriority = guideGroupTpl:getGuidePriority(tpl)
  for k, _ in pairs(self._bavTreeData) do
    local type = guideGroupTpl:getGuideType(guideGroupTpl:getTplById(k))
    if type == guideType then
      local priority = guideGroupTpl:getGuidePriority(guideGroupTpl:getTplById(k))
      return curPriority <= priority
    end
  end
  return false
end

function manager:dealPriority(guideId)
  local tpl = guideGroupTpl:getTplById(guideId)
  local guideType = guideGroupTpl:getGuideType(tpl)
  local curPriority = guideGroupTpl:getGuidePriority(tpl)
  for k, _ in pairs(self._bavTreeData) do
    local type = guideGroupTpl:getGuideType(guideGroupTpl:getTplById(k))
    if type == guideType then
      local priority = guideGroupTpl:getGuidePriority(guideGroupTpl:getTplById(k))
      if curPriority > priority then
        self:saveStepId(k, 0, true)
        break
      end
    end
  end
end

function manager:dealGuideInBattle()
  local isInBattle = C_BattleManager:IsInBattle()
  local isBattleGuideRunning = self:isBattleGuideRunning()
  if isInBattle then
    if isBattleGuideRunning then
      C_WorldProxy:GetInstance():Pause()
    else
      C_WorldProxy:GetInstance():Resume()
    end
  else
    C_WorldProxy:GetInstance():Resume()
  end
end

function manager:isBlockPageOpened(pName, bShow)
  for _, pageName in pairs(self.guideBlockUis) do
    local page = L_UI:getPage(pageName)
    if pageName == pName and bShow then
      return true
    end
    if page then
      return true
    end
  end
  return false
end

function manager:debugGuide(guideId)
  if guideId == nil then
    return
  end
  self.debugGuideId = guideId
  self:setGuideDebug(true)
  self:testGuide(guideId)
end

function manager:enableUpdate()
  C_UpdateSource.AddUpdateEventHandler(self._updateHandler, "guideManager.update")
end

function manager:disableUpdate()
  C_UpdateSource.RemoveUpdateEventHandler(self._updateHandler)
end

function manager:update()
  if not self:canStartGuide() then
    return
  end
  self:checkWaitCache()
  local timeScale = C_Time.deltaTime
  for _, tree in pairs(self._bavTreeData) do
    if tree then
      tree:onUpdate(timeScale)
    end
  end
  if table.count(self._bavTreeData) > 0 then
    if not L_UI:checkPageOpen("pageGuideSkip") then
      L_UI:open("pageGuideSkip")
    end
  elseif L_UI:checkPageOpen("pageGuideSkip") then
    L_UI:close("pageGuideSkip")
  end
  self.isCloseToSpecial = self:checkRangeEntities()
end

function manager:checkWaitCache()
  if not self.blockStatus and #self.behaviorWaitCache > 0 then
    for _, treeData in ipairs(self.behaviorWaitCache) do
      if self._bavTreeData[treeData.guideId] then
        printf(treeData.guideId .. " \229\183\178\231\187\143\229\138\160\232\189\189\232\191\135\228\186\134")
      else
        self:startGuide(treeData.guideId)
      end
    end
    self.behaviorWaitCache = {}
  end
end

function manager:internalSendEvent(eventName, ...)
  self._event:send(eventName, ...)
end

function manager:addListener(eventName, func, register, obj, priority)
  self._event:register(eventName, func, register, obj, priority)
end

function manager:removeListener(eventName, func)
  self._event:unRegister(eventName, func)
end

function manager:endGuideTree(guideId, isSkipTrigger)
  local preGuideData = self._bavTreeData[guideId]
  if preGuideData then
    preGuideData:onDestroy()
    self._bavTreeData[guideId] = nil
    if self._guideFinishedCallback[guideId] then
      self._guideFinishedCallback[guideId]()
    end
    self._guideFinishedCallback[guideId] = nil
    self._localGuideIdCache[guideId] = true
    self:onEndSendEvent(guideId)
    printf(tag, "\231\187\147\230\157\159\228\186\134\228\186\134\232\161\140\228\184\186\230\160\145\239\188\154" .. tostring(guideId))
    if not isSkipTrigger then
      if not self.isDebugActive then
        self:triggerGuideByParams()
      end
      self:setGuideDebug(false)
    end
  end
end

function manager:isLocalGuideCompleted(guideId)
  return self._localGuideIdCache[guideId] == true
end

function manager:endGuide(guideId, isSkipTrigger)
  self:endGuideTree(guideId, isSkipTrigger)
  if not self:isBattleGuideRunning() then
    self:dealGuideInBattle()
  else
    C_WorldProxy:GetInstance():Resume()
  end
end

function manager:onEndSendEvent(guideId)
  self:internalSendEvent(G_GuideClasses.GuideEvent.GuideEnd, guideId)
  local args = CS.Lens.Gameplay.Modules.BigWorld.CommonIntDriveArgs()
  args.intVal = guideId
  C_DriveManager.DispatchDrive(C_EExternalDrive.DoGuideEnd, args)
end

function manager:isBattleGuideRunning()
  local tpl
  for guideId, tree in pairs(self._bavTreeData) do
    tpl = guideGroupTpl:getTplById(guideId)
    local isBattleGuide = guideGroupTpl:getIsBattleGuide(tpl)
    if isBattleGuide ~= 0 then
      return true
    end
  end
  return false
end

function manager:isGuideOnRunning(guideId)
  if guideId then
    return self._bavTreeData[guideId] ~= nil
  end
  return table.count(self._bavTreeData) > 0
end

function manager:getCurrentGuide()
  local res = {}
  for _, tree in pairs(self._bavTreeData) do
    table.insert(res, tree)
  end
  return res
end

function manager:saveStepId(guideId, step, isSkipTrigger)
  L_GuideStore:req_updateGuideData(guideId, step, function(success)
    if success then
      if step == 0 then
        self:saveMutualGuides(guideId)
        self:endGuide(guideId, isSkipTrigger)
      end
      printf(tag, tostring(guideId) .. " \228\191\157\229\173\152\228\186\134\230\173\165\233\170\164id:" .. step)
    end
  end)
end

function manager:saveMutualGuides(id)
  local groupTpl = guideGroupTpl:getTplById(id)
  if groupTpl then
    local mutualIds = guideGroupTpl:getMutualExclusion(groupTpl)
    if #mutualIds == 0 then
      return
    end
    for k, mutualId in ipairs(mutualIds) do
      if not L_GuideStore:isGuideComplete(mutualId, true) then
        local ids = {}
        local tpls = guideTpl:getGroup(mutualId)
        for _, tpl in ipairs(tpls) do
          table.insert(ids, guideTpl:getId(tpl))
        end
        table.insert(ids, 0)
        Timer.once(0.5 * k, function()
          self:saveMutualSteps(mutualId, ids)
        end, self)
      end
    end
  end
end

function manager:saveMutualSteps(guideId, steps)
  if #steps == 0 then
    return
  end
  local step = steps[1]
  table.remove(steps, 1)
  L_GuideStore:req_updateGuideData(guideId, step, function(success)
    if success then
      self:saveMutualSteps(guideId, steps)
      printf(tag, "\228\186\146\230\150\165\229\188\149\229\175\188 " .. tostring(guideId) .. " \228\191\157\229\173\152\228\186\134\230\173\165\233\170\164id:" .. step)
    end
  end)
end

function manager:getGuideBevData()
  return self._bavTreeData
end

function manager:getRunningGuideIds()
  local res = {}
  for k, _ in pairs(self._bavTreeData) do
    table.insert(res, k)
  end
  return res
end

function manager:getIsDetectSkill()
  return self.isDetectSkill
end

function manager:setIsDetectSkill(isDetect)
  self.isDetectSkill = isDetect
end

function manager:clear()
  self.guideActive = false
end

return manager
