--------------------------------
-- 文件名       ：  CommonRankDB.lua
-- 文件说明     ：  通用排行榜DB数据
-- 创建时间     :   2024/12/09
-- 创建人       :   FC
--------------------------------

local Msg = require("core.Msg")
local Timer = require("core.Timer")
local DB = require("common.DB")
local CommonDB = require("common.CommonDB")
local CommonDefine = require("common.CommonDefine")
local Log = require("common.Log")
local LuaMongo = _G.lua_mongo
local Util = require("common.Util")
local Grid = require("bag.Grid")
local OpenAct = require("present.OpenAct")
local RoleDBLogic = require("role.RoleDBLogic")
local YunYingLogic = require("yunying.YunYingLogic")
local MailManager = require("mail.MailManager")
local MailExcel = require("excel.mail")
local OpenActExcel = require("excel.openAct")
local WeekLoopActCof = require("excel.WeekLoopAct")
local WeekLoopActDef = require("WeekendLoopActivity.WeekendLoopActDefine")

--[[
	开服活动排行DB操作,封装了一些公共处理方法
    db字段只是缓存排名好的数据(只是内存数据)
    Coomondb = {
        [rankType] = {                  -- 排名类型
            rankType = xxx,             -- 排名类型
            uuid2rank = {               -- 根据UUID获取名次   
                [uuid] = rank,          -- 玩家uuid = 排行名次
                ...
            },        
            sendUuid2rank = {           -- 根据UUID获取发送名次
                [uuid] = rank,          -- 玩家uuid = 真实发送的排行名次
                ...
            },
            rank2data = {               -- 根据名次获取DATA
                [rank] = {              -- 排行名次 = 玩家排名数据
                   uuid = xxx,          -- 玩家uuid
                   rankType = xxx,      -- 排名类型
                   value = xxx,         -- 排名值
                   time = xxx,          -- 排名值更新时间
                   _id = xxx,           -- db记录中的uuid
                },      
                ...
            },
            sendRank2data = {           -- 根据名次获取发送DATA
                [rank] = {              -- 排行名次 = 真实发送玩家排名数据
                   uuid = xxx,          -- 玩家uuid
                   rankType = xxx,      -- 排名类型
                   value = xxx,         -- 排名值
                   time = xxx,          -- 排名值更新时间
                   _id = xxx,           -- db记录中的uuid
                },      
                ...
            }
        },
        ...
    }

	DB:
        db_common_rank = {     -- 开服排行类活动
            [uuid] = {
                uuid = xxx,             -- 玩家uuid
                rankType= xxx,          -- 排名类型
                value = xxx,            -- 排行值
                time = xxx,             -- 排行值更新时间
            }
        }
	local:
        CommonDB_GetConfig()              -- 得到指定排名类型的活动配置
        getMinRankValue()				-- 得到排行榜中最低上榜条件数
        getMaxRank()				    -- 得到活动最多排名名次
        getRankNeedValue()			    -- 得到排行榜中名次对应需要达到的条件数
        getRankInfo()					-- 得到指定排行榜指定名次的配置信息
        getRewardInfoByRank()           -- 获取奖励信息
        getBoard()                      -- 获取指定排名类型DB数据
        getRank()                       -- 获取指定排名类型、指定用户的排名
        getSendRank()                   -- 获取指定排名类型、指定用户的发送排名
        cmpRank()                       -- 排名算法
        updateRank()                    -- 更新排名
        updateData()                    -- 更新数据
        loadBoard()                     -- 加载排行榜
        resertBoard()                   -- 重置排行数据
        initBoard()                     -- 初始排行榜
        getDBData()                     -- 根据类型+uuid从db中取data
        updateDBData()                  -- 更新DB数据
        billboardSet()                  -- 设置排名数据
        getMailID()                     -- 获取发送奖励的邮件ID
        getRewardInfoByInfo()			-- 得到指定排行榜指定配置信息的奖励信息
        rewardSend()					-- 奖励发送
        wrapOpenServerRankRewardList    -- 包装档位奖励详情数据
	public:
		initAfterStart()			    -- 启动就初始化	
		getMaxSendRank()				-- 得到最大发送排行名次数据
		wrapOpenServerRankList()		-- 包装档位名次数据
		wrapOwnerData()					-- 包装自己名次数据
        rewardQuery()                   -- 发送奖励详情
		onValueAdd()					-- 排行值增加回调
        onValueSet()                    -- 排行值设置回调
		onRewardSend()					-- 奖励发送回调
--]]


WeekAct_RankName = "WeekAct_ChargeRank"

MAX_SEND_RANK = 10						-- 最大发送排名数
MAX_SEND_WEEKACTRANK = 21               -- 周活动排行榜发送排名数

Coomondb = Coomondb or {}                  			-- [rankType] = board
CoomonQueryForData = {rankType = 1, uuid = 1} -- 通过排名类型和玩家uuid查找DB数据
CoomonQueryByUuid = {_id = 1}                 -- 通过DB的uuid更新DB数据
CoomonQueryByRankType = {rankType = 1}        -- 通过排名类型查找DB数据


local function CommonDB_GetConfig(rankType)
    if CommonDefine.COMMONRANK_TYPE_WEEKRANK == rankType  then
        return WeekLoopActCof.Rank
    end

    return nil
end

-- 得到排行榜中最低上榜条件数
local MIN_RANK_VALUES
local function getMinRankValue(rankType)
	MIN_RANK_VALUES = MIN_RANK_VALUES or {}
	if MIN_RANK_VALUES[rankType] then return MIN_RANK_VALUES[rankType] end

    local tWeekRankConfig = CommonDB_GetConfig(rankType)
    if not tWeekRankConfig then return end

    -- 这里后续有排行榜类型，保持命名一致
    local minRankValue
    for _,info in ipairs(tWeekRankConfig) do
        if not minRankValue or minRankValue > info.nMoney then 
            minRankValue = info.nMoney
        end
    end

	MIN_RANK_VALUES[rankType] = minRankValue
	return MIN_RANK_VALUES[rankType]
end

--local MAX_RANKS
local function getMaxRank(rankType)
    return 200
end

 -- 得到排行榜中名次对应需要达到的条件数
local RANK_NEED_VALUES
local function getRankNeedValue(rankType, rank)
	RANK_NEED_VALUES = RANK_NEED_VALUES or {}

	local minRankValue = getMinRankValue(rankType)
	if RANK_NEED_VALUES[rankType] then 
		local nValue = nil
        for _, tData in ipairs(RANK_NEED_VALUES[rankType]) do
            if tData.nMinRank <= rank and tData.nMaxRank >= rank then
                nValue = tData.nValue
                break
            end
        end

        return nValue or minRankValue
	end

    local tWeekRankConfig = CommonDB_GetConfig(rankType)
    if not tWeekRankConfig then return end

	RANK_NEED_VALUES[rankType] = {}

    local nValue = nil
    for _, info in pairs(tWeekRankConfig) do
        local nMinRank = info.ranks[1]
        local nMaxRank = info.ranks[2]
        table.insert(RANK_NEED_VALUES[rankType], {nMinRank = nMinRank, nMaxRank = nMaxRank, nValue = info.nMoney})

        if nMinRank <= rank and nMaxRank >= rank then
            nValue = info.nMoney
        end
    end
 
	return nValue or minRankValue
end

-- 得到指定排行榜指定名次的配置信息
local function getRankInfo(rankType, rank)
    local tWeekRankConfig = CommonDB_GetConfig(rankType)
    if not tWeekRankConfig then return end

    local rankInfo
    for _,info in pairs(tWeekRankConfig) do
		if rank >= info.ranks[1] and rank <= info.ranks[2] then
			rankInfo = info
			break
		end
    end
 
	return rankInfo
end

-- 获取奖励信息
local function getRewardInfoByRank(rankType, rank)
    local tWeekRankConfig = CommonDB_GetConfig(rankType)
    if not tWeekRankConfig then return end

	local rewardInfo
    for _,info in pairs(tWeekRankConfig) do
        if rank >= info.ranks[1] and rank <= info.ranks[2] then
            rewardInfo = info.prize
            break
        end
    end

	return rewardInfo
end

-- 获取指定排名类型DB数据
local function getBoard(rankType)
    return Coomondb[rankType]
end

-- -- 获取指定排名类型、指定用户的排名
local function getRank(rankType, uuid)
    local board = getBoard(rankType)
    if not board then return end

    return board.uuid2rank[uuid]
end

-- 获取指定排名类型、指定用户的发送排名
local function getSendRank(rankType, uuid)
    local board = getBoard(rankType)
    if not board then return end

    return board.sendUuid2rank[uuid]
end

-- 排名算法
local function cmpRank(rankType, data1, data2)
    if data1.value ~= data2.value then
        return data1.value > data2.value
    end

    return data1.time < data2.time
end

-- 更新排名
local function updateRank(board)
    if not board then return end

    -- 清空普通排名和发送排名玩家数据
    for uuid in pairs(board.uuid2rank) do
        board.uuid2rank[uuid] = nil
        board.sendUuid2rank[uuid] = nil
    end

    -- 设置普通排名
    for rank, data in ipairs(board.rank2data) do
		board.uuid2rank[data.uuid] = rank
	end

    -- 清空发送排名
    for rank in ipairs(board.sendRank2data) do
        board.sendRank2data[rank] = nil
    end

    local minRankValue = getMinRankValue(board.rankType)                -- 排名最小需要的排名值
    local aleadyRankCnt = 0                                             -- 已经排好的玩家数
    local maxRank = getMaxRank(board.rankType)                          -- 最多排名玩家数量
    for rank = 1, maxRank do
        local rankNeedValue = getRankNeedValue(board.rankType, rank)    -- 取当前排名需要的排名值

        local rank2data = board.rank2data[aleadyRankCnt + 1]            -- 当前排的玩家
        if rank2data then                                               -- 还有玩家需要排
            if rank2data.value >= rankNeedValue then                    -- 达到排名值,可以排名
    		    board.sendUuid2rank[rank2data.uuid] = rank              -- 设置玩家发送排名
    		    board.sendRank2data[rank] = rank2data                   -- 设置发送排名数据
    		    aleadyRankCnt = aleadyRankCnt + 1                       -- 排名玩家数量+1
            elseif rankNeedValue <= minRankValue then                   -- 未达到排名值 且 当前排名值 小于等于 最小需要的排名值了,则后面的玩家都不需要排了
                    break
             else                                                       -- 未到达最低排名值,当前名次未有玩家达到
                board.sendRank2data[rank] = -1
            end
        else                                                            -- 没玩家排了直接停止
            break
        end
    end
end

-- 更新数据
local function updateData(rankType, data)
    local board = getBoard(rankType)
    if not board then return end

    local uuid = data.uuid
    local oldRank = getRank(rankType, uuid) or (#board.rank2data + 1)
    board.rank2data[oldRank] = data

    -- 向后比较(data.value 减少时),往排名低方向->
    for i = oldRank + 1, #board.rank2data do
        local data1 = board.rank2data[i - 1]
        local data2 = board.rank2data[i]
        if cmpRank(rankType, data1, data2) then
            break
        end
        board.rank2data[i - 1] = data2
        board.rank2data[i] = data1
    end

    -- 向前比较(data.value 增加时),往排名高方向<-
    for i = oldRank - 1, 1, -1 do
        local data1 = board.rank2data[i]
        local data2 = board.rank2data[i + 1]
        if cmpRank(rankType, data1, data2) then
            break
        end
        board.rank2data[i] = data2
        board.rank2data[i + 1] = data1
    end  

    local maxRank = getMaxRank(rankType)
    board.rank2data[maxRank + 1] = nil  
    return true
end

-- 加载排行榜
local function loadBoard()
    local cnt = 0
    LuaMongo.find(DB.db_common_rank, nil)
    while true do
        local data = {}
        if not LuaMongo.next(data) then
            break
        end

        cnt = cnt + 1
        if cnt % 1000 == 0 then
            _G.collectgarbage("step", 100000)
        end
        updateData(data.rankType, data)
    end

    for _, board in pairs(Coomondb) do
        updateRank(board)
    end
end

-- 重置排行数据
local function resertBoard(board)
    board.uuid2rank = {}        -- 根据UUID获取名次   
    board.sendUuid2rank = {}    -- 根据UUID获取发送名次   
    board.rank2data = {}        -- 根据名次获取DATA
    board.sendRank2data = {}    -- 根据名次获取发送DATA
end

-- 初始排行榜
local function initBoard(rankType)
    local board = {}
    resertBoard(board)
    board.rankType = rankType
    Coomondb[rankType] = board
end

-- 根据类型+uuid从db中取data
local function getDBData(rankType, uuid)
    CoomonQueryForData.rankType = rankType
    CoomonQueryForData.uuid = uuid

    local data = {}
    LuaMongo.find(DB.db_common_rank, CoomonQueryForData)
    return LuaMongo.next(data) and data
end

-- 更新DB数据
local function updateDBData(data)
    local oldData = getDBData(data.rankType, data.uuid)
    if oldData then
        CoomonQueryByUuid._id = oldData._id
        LuaMongo.update(DB.db_common_rank, CoomonQueryByUuid, data)
    else
        LuaMongo.insert(DB.db_common_rank, data)
    end
end

-- 设置排名数据
local function billboardSet(data)
    local board = getBoard(data.rankType)
    if not board then return end

    updateData(data.rankType, data)
    updateRank(board)
    updateDBData(data)
end

-- 获取发送奖励的邮件ID
local function getMailID(rankType, rankInfo)
    if CommonDefine.COMMONRANK_TYPE_WEEKRANK == rankType then
        return WeekLoopActDef.WEEKACT_RANK_MAILID
    end

	return WeekLoopActDef.WEEKACT_RANK_MAILID
end

-- 得到指定排行榜指定配置信息的奖励信息
local function getRewardInfoByInfo(rankType, rankInfo)
    if CommonDefine.COMMONRANK_TYPE_WEEKRANK == rankType then
        return rankInfo.prize
    end

	return rankInfo.prize
end

-- 奖励发送
MAIL_SEND_CACHE = MAIL_SEND_CACHE or nil
local function rewardSend(rankType)
	local board = getBoard(rankType)
    if not board then return end

    MAIL_SEND_CACHE = MAIL_SEND_CACHE or {}
    MAIL_SEND_CACHE[rankType] = {}  

    local mailID = getMailID()

    for rank, rankData in ipairs(board.sendRank2data) do
        if rankData and rankData ~= -1 then
            local rankInfo = getRankInfo(rankType, rank)
            if rankInfo then
                MAIL_SEND_CACHE[rankType][rank] = rankData.uuid
                --mailID = getMailID(rankType, rankInfo)
            --[[
	        local mailID = getMailID(rankType, rankInfo)
		    local rewardInfo = getRewardInfoByInfo(rankType, rankInfo)
			        
		    local mailConfig = MailExcel.mail[mailID]
		    local title = mailConfig.title
		    local senderName = mailConfig.senderName
		    local content = mailConfig.content		
			        			
		    MailManager.add(MailManager.SYSTEM, rankData.uuid, 
						title, Util.format(content, rank), rewardInfo, senderName)   --]]
            end                      	
        end
    end

    Timer.addLater(10, CommonDB_SendPrize, mailID, rankType)

    -- 把内存中的数据重置,并移除DB中数据
    resertBoard(board)
    CoomonQueryByRankType.rankType = rankType
    LuaMongo.remove(DB.db_common_rank, CoomonQueryByRankType)
end

-- 包装档位奖励详情数据
local function wrapOpenServerRankRewardList(rankType, net, info)
    net.minRank = info.ranks[1]
    net.maxRank = info.ranks[2]

    local rewardInfo = info.prize

 	local len = 0
	for index,itemInfo in ipairs(rewardInfo) do
		len = len + 1
		Grid.makeItem(net.items[index], itemInfo[1], itemInfo[2])
	end

	net.items[0] = len
end

-- 启动就初始化
function initAfterStart()
    if _G.is_middle == true then return end

    for nType = CommonDefine.COMMONRANK_TYPE_MIN, CommonDefine.COMMONRANK_TYPE_MAX do
        initBoard(nType)
        print("[initAfterStart] 初始化排行榜 nType = "..nType)
    end

    loadBoard()
end
 
-- 得到最大发送排行名次数据
function getMaxSendRank(rankType)
    if CommonDefine.COMMONRANK_TYPE_WEEKRANK == rankType then
        return MAX_SEND_WEEKACTRANK
    end

    return MAX_SEND_RANK
end

-- 包装档位名次数据
function wrapOpenServerRankList(rankType, net, rank)
	local board = getBoard(rankType)
	local sendRankData = board.sendRank2data[rank]

	net.rank = rank
	net.rankNeedValue = getRankNeedValue(rankType, rank)
	if sendRankData and sendRankData ~= -1 then
		local userDB = RoleDBLogic.getDb(sendRankData.uuid)
		net.uid = sendRankData.uuid
		net.name = userDB.name
		net.head = userDB.head
		net.rankValue = sendRankData.value
		net.headFrame = userDB.headFrame
	else
		net.uid = "-1"
		net.name = ""
		net.head = -1
		net.rankValue = 0
		net.headFrame = -1
	end
 	
 	local rewardInfo = getRewardInfoByRank(rankType, rank)
 	local len = 0
 	if rewardInfo then
		for index,itemInfo in ipairs(rewardInfo) do
			len = len + 1
			Grid.makeItem(net.items[index], itemInfo[1], itemInfo[2])
		end
 	end
	net.items[0] = len
end

-- 包装自己名次数据
function wrapOwnerData(rankType, net, uuid)
	local maxRank = getMaxRank(rankType)
	local sendRank = getSendRank(rankType, uuid)
	local board = getBoard(rankType)
	local rankData
    if sendRank then
        rankData = board.sendRank2data[sendRank]
    else
        local rank = getRank(rankType, uuid)
        rankData = rank and board.rank2data[rank]
    end

	net.rank = sendRank or -1
	net.rankValue = rankData and rankData.value or 0

 	local len = 0
 	if sendRank then
 		local rewardInfo = getRewardInfoByRank(rankType, sendRank)
		for index,itemInfo in ipairs(rewardInfo) do
			len = len + 1
			Grid.makeItem(net.items[index], itemInfo[1], itemInfo[2])
		end
 	end
	net.items[0] = len
end

-- 发送奖励详情
function rewardQuery(human, rankType)
    local tWeekRankConfig = CommonDB_GetConfig(rankType)
 
	local msgRet = Msg.gc.GC_OPEN_SERVER_RANK_REWARD_QUERY
	msgRet.type = rankType

    local len = 0
    for _,info in ipairs(tWeekRankConfig) do
        len = len + 1
        wrapOpenServerRankRewardList(rankType, msgRet.list[len], info)
    end
    msgRet.list[0] = len

    Msg.send(msgRet, human.fd)
end

-- 排行值增加回调
function onValueAdd(human, rankType, value)
 	local dbData = getDBData(rankType, human.db._id) or {}
    local tValue = (dbData.value or 0) + value
    onValueSet(human, rankType, tValue)
end

-- 排行值设置回调
function onValueSet(human, rankType, tValue)
 	local dbData = getDBData(rankType, human.db._id) or {}
	dbData.value = tValue
	dbData.time = os.time()
	dbData.rankType = rankType
	dbData.uuid = human.db._id
	billboardSet(dbData)
end

-- 奖励发送回调
function onRewardSend(nRankType)
    local openDay = CommonDB.getServerOpenDay()
    if not openDay then return end

    rewardSend(nRankType)
end

-- 定时器 邮件分开发，预防崩溃
function CommonDB_SendPrize(mailID, rankType)
    if mailID and MAIL_SEND_CACHE and MAIL_SEND_CACHE[rankType] then
        local mailConfig = MailExcel.mail[mailID]   
	    local title = mailConfig.title
	    local senderName = mailConfig.senderName
	    local content = mailConfig.content	    
        local mailSends = MAIL_SEND_CACHE[rankType] 

        for rank,uuid in pairs(mailSends) do
            local rankInfo = getRankInfo(rankType, rank)
            if rankInfo then
                local rewardInfo = getRewardInfoByInfo(rankType, rankInfo)
			    if rewardInfo then	
		            MailManager.add(MailManager.SYSTEM, uuid, 
						        title, Util.format(content, rank), rewardInfo, senderName)           
                end        
            end
        end

        MAIL_SEND_CACHE[rankType] = nil
    end

    --[[
    -- 发完奖励后,把内存中的数据重置,并移除DB中数据
    local board = getBoard(rankType)
    if board then 
        resertBoard(board)
        CoomonQueryByRankType.rankType = rankType
        LuaMongo.remove(DB.db_common_rank, CoomonQueryByRankType)  
    end--]]
end
