local Msg = require("core.Msg")
local Timer = require("core.Timer")
local DB = require("common.DB")
local CommonDB = require("common.CommonDB")
local Log = require("common.Log")
local LuaMongo = _G.lua_mongo
local Util = require("common.Util")
local Grid = require("bag.Grid")
local AbsActLogic = require("absAct.AbsActLogic")
local RoleDBLogic = require("role.RoleDBLogic")
local YunYingLogic = require("yunying.YunYingLogic")
local MailManager = require("mail.MailManager")
local MailExcel = require("excel.mail")
local AbsActExcel = require("excel.absAct")
--[[
	abs公共排行类活动DB操作,封装了一些公共处理方法
    db字段只是缓存排名好的数据(只是内存数据)
    db = {
        [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
                },      
                ...
            },
            funcID = xxx,              -- 活动id
            funcRankType = xxx,        -- 活动排名类型
        },
        ...
    }
	local:
        getActConfig()                  -- 得到指定排名类型的活动配置
        getMaxRank()				    -- 得到活动最多排名名次
        getRankType()                   -- 拼接排名类型
        resertBoard()                   -- 重置排行数据
        initBoard()                     -- 初始排行榜
        getBoard()                      -- 获取指定排名类型DB数据
        getRankInfo()					-- 得到指定排行榜指定名次的配置信息
        getRewardInfo()                 -- 得到指定排行榜指定配置信息的奖励信息
        getRankNeedValue()			    -- 得到排行榜中名次对应需要达到的条件数
        getMailID()                     -- 获取发送奖励的邮件ID
        getRewardInfoByInfo()			-- 得到指定排行榜指定配置信息的奖励信息
        getRank()                       -- 获取指定排名类型、指定用户的排名
        getSendRank()                   -- 获取指定排名类型、指定用户的发送排名
        cmpRank()                       -- 排名算法
        updateRank()                    -- 更新排名
        updateData()                    -- 更新数据
        loadBoard()                     -- 加载排行榜
        getDBData()                     -- 根据类型+uuid从db中取data
        billboardSet()                  -- 设置排名数据
        rewardSend()					-- 奖励发送
        wrapRankRewardList              -- 包装档位奖励详情数据
	public:	
		getMaxSendRank()				-- 得到最大发送排行名次数据
        getMinRankValue()				-- 得到排行榜中最低上榜条件数
		wrapRankList()		            -- 包装档位名次数据
		wrapOwnerData()					-- 包装自己名次数据
        rewardQuery()                   -- 发送奖励详情
		onValueAdd()					-- 排行值增加回调
		onRewardSend()					-- 奖励发送回调
--]]

FUNC_RANK_DAY_TYPE = 1                 -- 同活动每日榜类型
FUNC_RANK_TOTAL_TYPE = 2               -- 同活动累计榜类型

FUNC_RANK_TYPE_INFOS = {               -- 不同配置命名映射
    [FUNC_RANK_DAY_TYPE] = {
        rankValueName = "dayNeedValue",
        rewardName = "dayRewards",
        mailIDName = "dayMailID",
        minRankValueName = "dayMinRankValue",
        isDayReward = true, 
    },
    [FUNC_RANK_TOTAL_TYPE] = {
        rankValueName = "totalNeedValue",
        rewardName = "totalRewards",
        mailIDName = "totalMailID",
        minRankValueName = "totalMinRankValue",
    }
}

MAX_SEND_RANK = 10						-- 最大发送排名数
DEFAULT_MAX_RANK = 200

db = db or {}                  			-- [rankType] = board
QueryForData = {rankType = 1, uuid = 1} -- 通过排名类型和玩家uuid查找DB数据
QueryByUuid = {_id = 1}                 -- 通过DB的uuid更新DB数据
QueryByRankType = {rankType = 1}        -- 通过排名类型查找DB数据

local funcConfigs
local function getActConfig(funcID, actId)
    local funcConfig = AbsActExcel.absActivity[funcID]
    if not funcConfig then return end

    local tempConfig = AbsActExcel[funcConfig.param[1]]
    if not tempConfig then return end

    if not actId then
        actId = funcConfig.actId
    end
    
    if not actId or actId == 0 then return end

    funcConfigs = funcConfigs or {}
    funcConfigs[funcID] = funcConfigs[funcID] or {} 
    if funcConfigs[funcID][actId] then return funcConfigs[funcID][actId] end

    funcConfigs[funcID][actId] = {configs={}}
    local actConfigs = funcConfigs[funcID][actId]

    local dayMinRankValue,totalMinRankValue,maxRank
    local len = 0
    for id,info in Util.pairsByKeys(tempConfig) do
        if actId == info.actId then
            len = len + 1

            info.id = id
            actConfigs.configs[len] = info                      -- 缓存abs活动指定期配置信息 

            -- 缓存最低上榜需要达到的排行值
            if info.dayNeedValue then                           -- 活动有单日榜
		        if not dayMinRankValue or dayMinRankValue > info.dayNeedValue then 
			        dayMinRankValue = info.dayNeedValue
		        end
            elseif info.totalNeedValue then                      -- 活动有累计榜
 		        if not totalMinRankValue or totalMinRankValue > info.totalNeedValue then 
			        totalMinRankValue = info.totalNeedValue
		        end             
            end 

		    if not maxRank or maxRank <= info.ranks[2] then 
			    maxRank = info.ranks[2]
            end
        end    
    end  
    
    actConfigs.maxRank = maxRank
    actConfigs.dayMinRankValue = dayMinRankValue
    actConfigs.totalMinRankValue = totalMinRankValue

    return funcConfigs[funcID][actId]
end

local function getMaxRank(funcID)
    return DEFAULT_MAX_RANK
end

local function getRankType(funcID, funcRankType)
    return funcID .. "-" .. funcRankType
end

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

local function initBoard(funcID, funcRankType)
    local rankType = getRankType(funcID, funcRankType)
    if db[rankType] then return end

    local board = {}
    resertBoard(board)

    board.rankType = rankType
    board.funcID = funcID
    board.funcRankType = funcRankType

    db[rankType] = board
end

local function getBoard(funcID, funcRankType)
    local rankType = getRankType(funcID, funcRankType)
    local board = db[rankType]
    if not board then 
        initBoard(funcID, funcRankType)
    end
    return db[rankType]
end

local function getRankInfo(funcID, rank, actId)
    local actConfig = getActConfig(funcID, actId)
    if not actConfig then return end

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

local function getRewardInfo(funcID, rank, funcRankType)
    local rankInfo = getRankInfo(funcID, rank)
    if not rankInfo then return end

    local info = FUNC_RANK_TYPE_INFOS[funcRankType]
    return info and rankInfo[info.rewardName]
end

local function getRankNeedValue(funcID, funcRankType, rank)
    local rankInfo = getRankInfo(funcID, rank)
    if not rankInfo then return end  

    local info = FUNC_RANK_TYPE_INFOS[funcRankType]
    return info and rankInfo[info.rankValueName]
end

local function getMailID(funcRankType, rankInfo)
    local info = FUNC_RANK_TYPE_INFOS[funcRankType]
    return info and rankInfo[info.mailIDName]
end

local function getRewardInfoByInfo(funcRankType, rankInfo)
    local info = FUNC_RANK_TYPE_INFOS[funcRankType]
    return info and rankInfo[info.rewardName]
end

local function getRank(funcID, funcRankType, uuid)
    local board = getBoard(funcID, funcRankType)
    return board.uuid2rank[uuid]
end

local function getSendRank(funcID, funcRankType, uuid)
    local board = getBoard(funcID, funcRankType)
    if not board then return end

    return board.sendUuid2rank[uuid]
end

local function cmpRank(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

    if not getActConfig(board.funcID) then return end

    local minRankValue = getMinRankValue(board.funcID, board.funcRankType)-- 排名最小需要的排名值
    local aleadyRankCnt = 0                                             -- 已经排好的玩家数
    local maxRank = getMaxRank(board.funcID)                            -- 最多排名玩家数量
    for rank = 1, maxRank do
        local rankNeedValue = getRankNeedValue(board.funcID, board.funcRankType, 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(data)
    local board = getBoard(data.funcID, data.funcRankType)
    if not board then return end

    local uuid = data.uuid
    local oldRank = getRank(data.funcID, data.funcRankType, 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(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(data1, data2) then
            break
        end
        board.rank2data[i] = data2
        board.rank2data[i + 1] = data1
    end  

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

function loadBoardData(absAct, funcID, uuid, now)
    local boardData = absAct.boardData
    if boardData == nil then
        --QueryForData.funcID = funcID
        --QueryForData.uuid = uuid
     else
        if absAct.endTime > now then
            for funcRankType, data in pairs(boardData) do 
               local info = FUNC_RANK_TYPE_INFOS[funcRankType]
               if info.isDayReward then
                   if data.time and Util.isSameDay(data.time) then
                       initBoard(data.funcID, data.funcRankType)
                       updateData(data)
                   end
               else
                   initBoard(data.funcID, data.funcRankType)
                   updateData(data)
               end
            end
        end
    end
end


function loadBoardRank()
   for _, board in pairs(db) do
       updateRank(board)
   end
end

local function loadBoard()
    for _, board in pairs(db) do
        updateRank(board)
    end
end

local function getDBData(funcID, funcRankType, uuid)
    local boardDB = getBoard(funcID, funcRankType)
    local rank = boardDB.uuid2rank[uuid]
    local uidDB = boardDB.rank2data[rank]
    return uidDB
end

local function billboardSet(data)
    local board = getBoard(data.funcID, data.funcRankType)
    if not board then return end

    updateData(data)
    updateRank(board)
end

MAIL_SEND_CACHE = MAIL_SEND_CACHE or nil
local function rewardSend(funcID, funcRankType, actId)
	local board = getBoard(funcID, funcRankType)
    if not board then return end

    local rankType = getRankType(funcID, funcRankType)
    MAIL_SEND_CACHE = MAIL_SEND_CACHE or {}
    MAIL_SEND_CACHE[rankType] = {}  

    local mailID
    for rank, rankData in ipairs(board.sendRank2data) do
        if rankData and rankData ~= -1 then
            local rankInfo = getRankInfo(funcID, rank, actId)
            if rankInfo then
                MAIL_SEND_CACHE[rankType][rank] = rankData.uuid
                if not mailID then
                    mailID = getMailID(funcRankType, rankInfo)
                end
            end          	
        end
    end

    resertBoard(board)
    Timer.addLater(10, boardReward, mailID, funcID, funcRankType, actId)
	
    -- 日志
    Log.write(Log.LOGID_DEBUG, "AbsReachRank", rankType)	
end

local function wrapRankRewardList(funcID, funcRankType, net, info)
    net.minRank = info.ranks[1]
    net.maxRank = info.ranks[2]

    local typeInfo = FUNC_RANK_TYPE_INFOS[funcRankType]
     
    local rankInfo = getRankInfo(funcID, info.ranks[1])
    local rewardInfo = rankInfo[typeInfo.rewardName]
    local rankNeedValue = rankInfo[typeInfo.rankValueName]

    net.rankNeedValue = rankNeedValue
 
 	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
 
_init = false
function init()
    if _G.is_middle == true then return end
    if not _init then
        _init = true
        --db = {}
        loadBoard()
    end
end 
 
function getMaxSendRank(funcID, funcRankType)
	return MAX_SEND_RANK
end

function getMinRankValue(funcID, funcRankType)
    local actConfig = getActConfig(funcID)
    if not actConfig then return end

    local info = FUNC_RANK_TYPE_INFOS[funcRankType]
    return info and actConfig[info.minRankValueName]
end

function wrapRankList(funcID, funcRankType, net, rank)
	local board = getBoard(funcID, funcRankType)
	local sendRankData = board.sendRank2data[rank]

	net.rank = rank
	net.rankNeedValue = getRankNeedValue(funcID, funcRankType, 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
        net.lv = userDB.lv
	else
		net.uid = "-1"
		net.name = ""
		net.head = -1
		net.rankValue = 0
		net.headFrame = -1
        net.lv = -1
	end
 	
 	local rewardInfo = getRewardInfo(funcID, rank, funcRankType)
 	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(funcID, funcRankType, net, uuid)
    local actConfig = getActConfig(funcID)
    if not actConfig then return end

	local maxRank = actConfig.maxRank
	local sendRank = getSendRank(funcID, funcRankType, uuid)
	local board = getBoard(funcID, funcRankType)
	local rankData
    if sendRank then
        rankData = board.sendRank2data[sendRank]
    else
        local rank = getRank(funcID, funcRankType, 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 = getRewardInfo(funcID, sendRank, funcRankType)
		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, funcID, funcRankType, msgRet)
    local actConfig = getActConfig(funcID)
    if not actConfig then return end

    msgRet.actId = funcID
	msgRet.funcRankType = funcRankType

    local len = 0
    for _,info in ipairs(actConfig.configs) do
        len = len + 1
        wrapRankRewardList(funcID, funcRankType, msgRet.list[len], info)
    end
    msgRet.list[0] = len

    Msg.send(msgRet, human.fd)
end

function onValueAdd(human, funcID, value)
    local actConfig = getActConfig(funcID)
    if not actConfig then return end

    AbsActLogic.checkAbsActClean(human, funcID)

    for funcRankType,info in ipairs(FUNC_RANK_TYPE_INFOS) do
        if getMinRankValue(funcID, funcRankType) then
            local rankType = getRankType(funcID, funcRankType)

            human.db.absAct[funcID].boardData = human.db.absAct[funcID].boardData or {}
            human.db.absAct[funcID].boardData[funcRankType] = human.db.absAct[funcID].boardData[funcRankType] or {
                rankType = rankType,
                uuid = human.db._id,
                funcID = funcID,
                funcRankType = funcRankType,
            }

            local dbData = human.db.absAct[funcID].boardData[funcRankType]
	        dbData.value = (dbData.value or 0) + value
	        dbData.time = os.time()
            billboardSet(dbData)
        end
    end
end

function onRewardSend(funcID)
    local funcConfig = AbsActExcel.absActivity[funcID]
    if not funcConfig then return end

    local actConfig = getActConfig(funcID)
    if not actConfig then return end
    
    local state = AbsActLogic.isStarted(human, funcID)
    local diffDay
    if not state then -- 活动结束
        diffDay = Util.diffDay(funcConfig.realEndTime)
        if diffDay ~= 1 then     
            return
        end
    end

    for funcRankType,info in ipairs(FUNC_RANK_TYPE_INFOS) do
        if getMinRankValue(funcID, funcRankType) then           -- 判断此活动是否有指定功能类型
            if info.isDayReward then                            -- 每天一结算
               rewardSend(funcID, funcRankType, funcConfig.actId)
            elseif diffDay == 1 then                            -- 活动结束时跨天结算
                rewardSend(funcID, funcRankType, funcConfig.actId)           
            end
        end
    end
end

-- 定时器 邮件分开发，预防崩溃
function boardReward(mailID, funcID, funcRankType, actId)
    local rankType = getRankType(funcID, funcRankType)

    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(funcID, rank, actId)
            if rankInfo then
		        local rewardInfo = getRewardInfoByInfo(funcRankType, 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
end

function updateDaily(human, funcID)
    local funcConfig = AbsActExcel.absActivity[funcID]
    if not funcConfig then return end

    if not human.db.absAct[funcID] or not human.db.absAct[funcID].boardData then return end

    for funcRankType,info in ipairs(FUNC_RANK_TYPE_INFOS) do
        if info.isDayReward and human.db.absAct[funcID].boardData[funcRankType] then -- 每天一结算
            human.db.absAct[funcID].boardData[funcRankType] = nil
        end
    end
end

--[[
function gmSendReward()
    print("send reward start")

    local funcID = 40006
    local actID = 101
    local now = os.time()

    local cnt = 0
    LuaMongo.find(DB.db_char, nil, require("billboard.BillboardDB").fields)
    while true do
        local humanDb = {}
        if not LuaMongo.next(humanDb) then
            break
        end

        cnt = cnt + 1
        if cnt % 1000 == 0 then
            _G.collectgarbage("step", 100000)
        end
      
      local absAct = humanDb.absAct[funcID]
      if absAct and absAct.boardData then
            for funcRankType, data in pairs(absAct.boardData) do 
                local info = FUNC_RANK_TYPE_INFOS[funcRankType]
                if info.isDayReward then
                    if data.time and Util.isSameDay(data.time) then
                        initBoard(data.funcID, data.funcRankType)
                        updateData(data)
                    end
                else
                    initBoard(data.funcID, data.funcRankType)
                    updateData(data)
                end
            end
       end
    end

   for _, board in pairs(db) do

        if board then 
            -- 清空普通排名和发送排名玩家数据
            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

            if getActConfig(board.funcID, actID) then 
                local minRankValue = 1                                              -- 排名最小需要的排名值
                local aleadyRankCnt = 0                                             -- 已经排好的玩家数
                local maxRank = getMaxRank(board.funcID)                            -- 最多排名玩家数量
                for rank = 1, maxRank do
                    local rankNeedValue
                    if rank >= 1 and rank <= 3 then
                        rankNeedValue = 500
                    elseif rank >= 4 and rank <= 20 then
                        rankNeedValue = 100
                    else
                        rankNeedValue = 1
                    end

                    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
        end

   end
   
   rewardSend(funcID, FUNC_RANK_TOTAL_TYPE, actID)
   print("send reward end")
end
--]]