
local BattleManager = class("BattleManager", BaseModule)
local BattleConst = GConst.BattleConst

BattleManager.SKILL_CFG = ConfigManager:getConfig("skill")

local BATTLE_CONTROLLER_BASE = "app/module/battle/controller/battle_controller"

local BATTLE_CONTROLLER = {
    [BattleConst.BATTLE_TYPE.STAGE] = "app/module/battle/controller/battle_controller_stage",
    [BattleConst.BATTLE_TYPE.DAILY_CHALLENGE] = "app/module/battle/controller/battle_controller_daily_challenge",
    [BattleConst.BATTLE_TYPE.DUNGEON_GOLD] = "app/module/battle/controller/battle_controller_dungeon_gold",
    [BattleConst.BATTLE_TYPE.DUNGEON_SHARDS] = "app/module/battle/controller/battle_controller_dungeon_shards",
    [BattleConst.BATTLE_TYPE.ARENA] = "app/module/battle/controller/battle_controller_arena",
    [BattleConst.BATTLE_TYPE.DUNGEON_WEAPON] = "app/module/battle/controller/battle_controller_dungeon_weapon",
    [BattleConst.BATTLE_TYPE.DUNGEON_ARMOR] = "app/module/battle/controller/battle_controller_dungeon_armor",
    [BattleConst.BATTLE_TYPE.ACT_BOSS_RUSH] = "app/module/battle/controller/battle_controller_boss_rush",
    [BattleConst.BATTLE_TYPE.DUNGEON_RUNE] = "app/module/battle/controller/battle_controller_dungeon_rune",
    [BattleConst.BATTLE_TYPE.ACT_PVP] = "app/module/battle/controller/battle_controller_act_pvp",
    [BattleConst.BATTLE_TYPE.FULL_MOON_SKIN] = "app/module/battle/controller/battle_controller_full_moon_skin",
}

function BattleManager:showPauseUI(battleType, battleController)
    if battleType == BattleConst.BATTLE_TYPE.ARENA or
        battleType == BattleConst.BATTLE_TYPE.ACT_PVP then
        UIManager:showUI("app/ui/battle/battle_arena_pause_ui", {battleType = battleType, battleController = battleController})
    else
        UIManager:showUI("app/ui/battle/battle_pause_ui", {battleType = battleType, battleController = battleController})
    end
end

function BattleManager:showBattleResultUI(battleType, rewards, combatReport, mysteryBoxIdx, firstPassIdx, noMergeReward)
    UIManager:showUI("app/ui/battle/battle_result_ui", {
        battleType = battleType,
        isWin = combatReport.victory,
        rewards = rewards,
        atkReport = combatReport.atkReport,
        wave = combatReport.wave,
        mysteryBoxIdx = mysteryBoxIdx or 0,
        firstPassIdx = firstPassIdx or 0,
        remainRound = combatReport.remainRound or 0,
        noMergeReward = noMergeReward,
    })
end

function BattleManager:showBattleArmorResultUI(battleType, rewards, combatReport, mysteryBoxIdx, firstPassIdx, noMergeReward)
    UIManager:showUI("app/ui/battle/battle_armor_result_ui", {
        battleType = battleType,
        isWin = combatReport.victory,
        rewards = rewards,
        atkReport = combatReport.atkReport,
        wave = combatReport.wave,
        mysteryBoxIdx = mysteryBoxIdx or 0,
        firstPassIdx = firstPassIdx or 0,
        remainRound = combatReport.remainRound or 0,
        noMergeReward = noMergeReward,
    })
end

function BattleManager:showArenaBattleResultUI(settlement, rewards, checkCount)
    UIManager:showUI("app/ui/battle/battle_result_ui", {
        battleType = GConst.BattleConst.BATTLE_TYPE.ARENA,
        isWin = settlement.win,
        rewards = rewards,
        incrScore = settlement.incr_score,
        arenaTotalCount = checkCount,
    })
end

function BattleManager:showBossRushBattleResultUI(battleType, combatReport, taskProgress)
    UIManager:showUI("app/ui/battle/battle_bossrush_result_ui", {
        battleType = battleType,
        combatReport = combatReport,
        taskProgress = taskProgress,
    })
end

function BattleManager:showBattleRuneResultUI(battleType, rewards, combatReport, remainRound)
    UIManager:showUI("app/ui/battle/battle_rune_result_ui", {
        battleType = battleType,
        combatReport = combatReport,
        rewards = rewards,
        remainRound = remainRound,
    })
end

function BattleManager:showActPvpResultUI(battleType, rewards, combatReport)
    UIManager:showUI("app/ui/battle/battle_act_pvp_result_ui", {
        battleType = battleType,
        combatReport = combatReport,
        rewards = rewards,
    })
end

function BattleManager:showBoxOpenUI(rewards, callback)
    UIManager:showUI("app/ui/battle/battle_box_open_ui", {rewards = rewards, callback = callback})
end

function BattleManager:reqSkillRefresh(isAd)
    if not isAd then
        local cost = GFunc.getConstReward("refresh_skill_cost")
        if not GFunc.checkCost(GFunc.getRewardId(cost), GFunc.getRewardNum(cost), true) then
            return
        end
    end

    self:sendMessage(ProtoMsgType.FromMsgEnum.BattleSkillRefreshReq, {ad = isAd}, {}, self.rspSkillRefresh, BIReport.ITEM_GET_TYPE.BATTLE_SKILL_REFRESH, true)
end

function BattleManager:rspSkillRefresh(result)
    if result.err_code == GConst.ERROR_STR.SUCCESS then
        if self.battleController and result.reqData then
            self.battleController.battleData:addRefreshSkillCount(result.reqData.ad)
        end
        EventManager:dispatchEvent(EventManager.CUSTOM_EVENT.SKILL_REFRESH_SUCC)
    end
end

function BattleManager:isInBattle()
    return self.battleController ~= nil
end

-- params = {atkFormation= {elementType = heroEntity}, defFormation= ?}
function BattleManager:playBattle(battleType, params, returnFunc, snapshot)
    UIManager:showLoading(UIManager.LOADING_TYPE.CLOUD, function()
        params = params or {}
        if self.battleController then -- 同一时间只能有一场战斗
            self:clear()
        end
        self.returnFunc = returnFunc
        UIManager:stopCurrentBGM()
        self:_play(battleType, params, snapshot)
    end)
end

function BattleManager:_play(battleType, params, snapshot)
    params.battleType = battleType
    if not params.atkFormation then
        params.atkFormation = {}
        local formation = DataManager.FormationData:getStageFormation()
        for elementType, heroId in pairs(formation) do
            local heroEntity = DataManager.HeroData:getHeroById(heroId)
            if heroEntity then
                params.atkFormation[elementType] = heroEntity
            end
        end
    end
    -- 上报英雄数据,方便检查是否作弊
    BIReport:postFightHeroInfo(battleType, params.atkFormation)
    local controllerPath = BATTLE_CONTROLLER[battleType] or BATTLE_CONTROLLER_BASE
    self.battleController = require(controllerPath):create()
    self.battleController:init(params, snapshot)
end

function BattleManager:onSelectSkill(skillId, value, pos)
    if not self.battleController then
        return
    end

    self.battleController:onSelectSkill(skillId, value, pos)
end

function BattleManager:endBattleAndExit(proactivelyExit)
    if self.battleController then
        if proactivelyExit then
            self.battleController:postWaveOver(false, true)
        end
        self.battleController:endBattleAndExit()
    else
        self:exitBattle()
    end
end

function BattleManager:exitBattle()
    self:clear()
    if self.returnFunc then
        local returnFunc = self.returnFunc
        self.returnFunc = nil
        returnFunc()
    else -- 没有指定返回则直接去主城
        UIManager:closeAllUI()
        ModuleManager.MaincityManager:showMainCityUI()
    end
    self:performWithDelayGlobal(function()
        Game:garbageCollect()
    end, 0.02)
end

function BattleManager:clear()
    if self.battleController == nil then
        return
    end
    GFunc.killDOTween(GConst.DOTWEEN_IDS.BATTLE)
    self.battleController:clear()
    self.battleController = nil
    self.bindUnitAttributeData = nil
end

----------------------- start 一些公共相关的方法 -----------------------------
function BattleManager:getPosInfo(posId, rowCount)
    rowCount = rowCount or BattleConst.ROW_COUNT
    if not GConst.BattleConst.GRID_POS[rowCount] then
        GConst.BattleConst.GRID_POS[rowCount] = {}
    end
    local posInfo = GConst.BattleConst.GRID_POS[rowCount][posId]
    if not posInfo then
        local r = self:getPosRC(posId).r
        local c = self:getPosRC(posId).c

        local offset = self:posNeedOffset(rowCount)
        local offsetC = c - BattleConst.HALF_COLUMN_COUNT
        local halfR = (rowCount + 1) / 2
        local offsetR = (rowCount + 1) / 2 - r
        local info = {x = offsetC * BattleConst.GRID_STEP_H, y = offsetR * BattleConst.GRID_STEP_H}
        if r > halfR then
            info.y = info.y - offset
        else
            info.y = info.y + offset
        end

        GConst.BattleConst.GRID_POS[rowCount][posId] = info
        posInfo = GConst.BattleConst.GRID_POS[rowCount][posId]
    end
    return posInfo
end

function BattleManager:posNeedOffset(rowCount)
    if rowCount == GConst.BattleConst.PVP_ROW_COUNT then
        return 23
    end
    return 0
end

function BattleManager:getElementIcon(elementType)
    local icon = GConst.BattleConst.ELEMENT_ICON[elementType]
    if not icon then
        return GConst.ATLAS_PATH.COMMON, "common_alpha"
    end
    return GConst.ATLAS_PATH.BATTLE, icon
end

function BattleManager:getPosId(row, column)
    if not BattleConst.RC_2_POS_ID[row] then
        BattleConst.RC_2_POS_ID[row] = {}
    end
    if not BattleConst.RC_2_POS_ID[row][column] then
        local posId
        local rowValue = row * BattleConst.ROW_STEP
        if rowValue >= 0 then
            posId = rowValue + column
        else
            rowValue = rowValue + BattleConst.ROW_STEP
            posId = rowValue - column
        end
        BattleConst.RC_2_POS_ID[row][column] = posId
    end
    return BattleConst.RC_2_POS_ID[row][column]
end

function BattleManager:getFirstLineLastRowPosId(row, column)
    return self:getPosId(1 - row, column)
end

function BattleManager:getLastLineLastRowPosId(row, column, maxRow)
    return self:getPosId(maxRow + row, column)
end

function BattleManager:getAroundPosIdsByList(posId, boardrange, cludePosIdsMap, maxRow)
    local posIdInfos = {}
    local randomExclusion = {}
    for _, info in ipairs(boardrange) do
        local list = self:getAroundPosIds(posId, info.type, info.range, cludePosIdsMap, randomExclusion, maxRow)
        for _, info in ipairs(list) do
            table.insert(posIdInfos, info)
            cludePosIdsMap[info.posId] = nil
            randomExclusion[info.posId] = true
        end
    end

    return posIdInfos
end

function BattleManager:getAroundPosIds(posId, direction, range, cludePosIdsMap, randomExclusion, maxRow)
    maxRow = maxRow or BattleConst.ROW_COUNT
    local rc = self:getPosRC(posId)
    local r = rc.r
    local c = rc.c
    local posIdList = {}

    if direction == BattleConst.BOARD_RANGE_TYPE.RANDOM then
        if cludePosIdsMap then
            local cludePosIds = {}
            for posId, _ in pairs(cludePosIdsMap) do
                if not randomExclusion or not randomExclusion[posId] then
                    table.insert(cludePosIds, posId)
                end
            end

            local count = math.min(#cludePosIds, range)
            for i = 1, count do
                table.insert(posIdList, {posId = table.remove(cludePosIds, math.random(1, #cludePosIds)), direction = direction})
            end
        end
    elseif direction == BattleConst.BOARD_RANGE_TYPE.UP then
        for i = r - 1, r - range, -1 do
            if i >= 1 then
                table.insert(posIdList, {posId = self:getPosId(i, c), direction = direction})
            end
        end
    elseif direction == BattleConst.BOARD_RANGE_TYPE.DOWN then
        for i = r + 1, r + range do
            if i <= maxRow then
                table.insert(posIdList, {posId = self:getPosId(i, c), direction = direction})
            end
        end
    elseif direction == BattleConst.BOARD_RANGE_TYPE.LEFT then
        for i = c - 1, c - range, -1 do
            if i >= 1 then
                table.insert(posIdList, {posId = self:getPosId(r, i), direction = direction})
            end
        end
    elseif direction == BattleConst.BOARD_RANGE_TYPE.RIGHT then
        for i = c + 1, c + range do
            if i <= BattleConst.COLUMN_COUNT then
                table.insert(posIdList, {posId = self:getPosId(r, i), direction = direction})
            end
        end
    elseif direction == BattleConst.BOARD_RANGE_TYPE.LEFT_UP then
        for i = 1, range do
            local newR = r - i
            local newC = c - i
            if newR >= 1 and newC >= 1 then
                table.insert(posIdList, {posId = self:getPosId(newR, newC), direction = direction})
            end
        end
    elseif direction == BattleConst.BOARD_RANGE_TYPE.LEFT_DOWN then
        for i = 1, range do
            local newR = r + i
            local newC = c - i
            if newR <= maxRow and newC >= 1 then
                table.insert(posIdList, {posId = self:getPosId(newR, newC), direction = direction})
            end
        end
    elseif direction == BattleConst.BOARD_RANGE_TYPE.RIGHT_UP then
        for i = 1, range do
            local newR = r - i
            local newC = c + i
            if newR >= 1 and newC <= BattleConst.COLUMN_COUNT then
                table.insert(posIdList, {posId = self:getPosId(newR, newC), direction = direction})
            end
        end
    elseif direction == BattleConst.BOARD_RANGE_TYPE.RIGHT_DOWN then
        for i = 1, range do
            local newR = r + i
            local newC = c + i
            if newR <= maxRow and newC <= BattleConst.COLUMN_COUNT then
                table.insert(posIdList, {posId = self:getPosId(newR, newC), direction = direction})
            end
        end
    end

    if cludePosIdsMap then
        local newList = {}
        for _, info in ipairs(posIdList) do
            if cludePosIdsMap[info.posId] then
                table.insert(newList, info)
            end
        end
        return newList
    else
        return posIdList
    end
end

function BattleManager:getPosRC(posId)
    if not BattleConst.POS_ID_2_RC[posId] then
        local r = posId // BattleConst.ROW_STEP
        local c
        if r <= 0 then
            local adsPosId = math.abs(posId)
            c = adsPosId % BattleConst.ROW_STEP
        else
            c = posId % BattleConst.ROW_STEP
        end
        BattleConst.POS_ID_2_RC[posId] = {r = r , c = c}
    end
    return BattleConst.POS_ID_2_RC[posId]
end

function BattleManager:getBuffElementType(buffName)
   local cfg = ConfigManager:getConfigWithOtherKey("buff", "name")[buffName]
   return cfg and cfg.position
end

function BattleManager:getPosCenterAndDir(pos1, pos2)
    local centerPos = {x = 0, y = 0}
    centerPos.x = (pos1.x + pos2.x) / 2
    centerPos.y = (pos1.y + pos2.y) / 2

    local zEuler = - math.deg(math.atan(pos2.x - pos1.x, pos2.y - pos1.y)) + 90
    return centerPos, zEuler
end

function BattleManager:getPosIdsByDirection(posId, direction, maxRow)
    maxRow = maxRow or BattleConst.ROW_COUNT
    local rc = self:getPosRC(posId)
    local list = {}
    if direction == BattleConst.BOARD_RANGE_TYPE.UP then
        for r = rc.r, 1, -1 do
            local id = self:getPosId(r, rc.c)
            table.insert(list, id)
        end
    elseif direction == BattleConst.BOARD_RANGE_TYPE.DOWN then
        for r = rc.r, maxRow do
            local id = self:getPosId(r, rc.c)
            table.insert(list, id)
        end
    elseif direction == BattleConst.BOARD_RANGE_TYPE.LEFT then
        for c = rc.c, 1, -1 do
            local id = self:getPosId(rc.r, c)
            table.insert(list, id)
        end
    elseif direction == BattleConst.BOARD_RANGE_TYPE.RIGHT then
        for c = rc.c, BattleConst.COLUMN_COUNT do
            local id = self:getPosId(rc.r, c)
            table.insert(list, id)
        end
    end

    return list
end

function BattleManager:getPosDirection(originPosId, posId)
    local originR, originC = self:getPosRC(originPosId).r, self:getPosRC(originPosId).c
    local r, c = self:getPosRC(posId).r, self:getPosRC(posId).c
    if originR < r and originC == c  then
        return BattleConst.BOARD_RANGE_TYPE.DOWN
    elseif originR > r and originC == c then
        return BattleConst.BOARD_RANGE_TYPE.UP
    elseif originR == r and originC < c then
        return BattleConst.BOARD_RANGE_TYPE.RIGHT
    elseif originR == r and originC > c then
        return BattleConst.BOARD_RANGE_TYPE.LEFT
    elseif originR < r and originC > c then
        return BattleConst.BOARD_RANGE_TYPE.LEFT_DOWN
    elseif originR < r and originC < c then
        return BattleConst.BOARD_RANGE_TYPE.RIGHT_DOWN
    elseif originR > r and originC > c then
        return BattleConst.BOARD_RANGE_TYPE.LEFT_UP
    elseif originR > r and originC < c then
        return BattleConst.BOARD_RANGE_TYPE.RIGHT_UP
    end
end

function BattleManager:getPosByDirection(posId, direction, maxRow)
    maxRow = maxRow or BattleConst.ROW_COUNT
    local rc = self:getPosRC(posId)
    if direction == BattleConst.BOARD_RANGE_TYPE.UP then
        local r = rc.r - 1
        if r > 0 then
            return self:getPosId(r, rc.c)
        end
    elseif direction == BattleConst.BOARD_RANGE_TYPE.DOWN then
        local r = rc.r + 1
        if r <= maxRow then
            return self:getPosId(r, rc.c)
        end
    elseif direction == BattleConst.BOARD_RANGE_TYPE.LEFT then
        local c = rc.c - 1
        if c > 0 then
            return self:getPosId(rc.r, c)
        end
    elseif direction == BattleConst.BOARD_RANGE_TYPE.RIGHT then
        local c = rc.c + 1
        if c <= BattleConst.COLUMN_COUNT then
            return self:getPosId(rc.r, c)
        end
    end
end

function BattleManager:getReverseDirection(direction)
    if direction == BattleConst.BOARD_RANGE_TYPE.UP then
        return BattleConst.BOARD_RANGE_TYPE.DOWN
    elseif direction == BattleConst.BOARD_RANGE_TYPE.DOWN then
        return BattleConst.BOARD_RANGE_TYPE.UP
    elseif direction == BattleConst.BOARD_RANGE_TYPE.LEFT then
        return BattleConst.BOARD_RANGE_TYPE.RIGHT
    elseif direction == BattleConst.BOARD_RANGE_TYPE.RIGHT then
        return BattleConst.BOARD_RANGE_TYPE.LEFT
    end
end

----------------------- end 一些公共相关的方法 -----------------------------

function BattleManager:bindBattleUnitAttribute(hashCode, side)
    if self.battleController then
        local team = nil
        if side == 1 then
            team = self.battleController.atkTeam
        else
            team = self.battleController.defTeam
        end
        local unitAttrHelper = nil
        local teamEntity = nil
        for _, unit in ipairs(team.unitList) do
            local code = unit.baseObject:getGameObject():GetHashCode()
            if code == hashCode then
                if side == 1 then
                    unitAttrHelper = unit.baseObject:getComponent(typeof(CS.BF.BattleUnitAttr))
                else
                    unitAttrHelper = unit.baseObject:getComponent(typeof(CS.BF.BattleUnitAttr))
                end
                teamEntity = unit.unitEntity.team
                break
            end
        end
        if unitAttrHelper and teamEntity then
            -- 创建并绑定相关数据
            if self.bindUnitAttributeData == nil then
                self.bindUnitAttributeData = {}
            end
            -- 组合必要数据
            local unitData = {}
            unitData.unitAttrHelper = unitAttrHelper
            unitData.teamEntity = teamEntity

            self.bindUnitAttributeData[hashCode] = unitData
            -- bind方法
            unitAttrHelper:BindGetAttributeFunc(function(hashCode)
                self:getBattleUnitAttribute(hashCode)
            end)
            -- 刷新数据
            self:getBattleUnitAttribute(hashCode)
        end
    end
end

-- 将lua端属性传回CS端
function BattleManager:getBattleUnitAttribute(hashCode)
    if self.bindUnitAttributeData and self.bindUnitAttributeData[hashCode] then
        local data = self.bindUnitAttributeData[hashCode]
        local unitAttrHelper = data.unitAttrHelper
        local teamEntity = data.teamEntity
        if unitAttrHelper and teamEntity then
            local attr = {}
            for key, value in pairs(teamEntity.attr) do
                attr[key] = value
            end
            attr.sheild_hp = teamEntity.shieldHp
            unitAttrHelper:GetAttribute(json.encode(attr))
        end
    end
end

function BattleManager:clearOnExitScene()
    if self.battleController == nil then
        return
    end
    self:clear()
    self.returnFunc = nil
end

return BattleManager