--节日排行榜服务
local skynet = require "skynet"
require "skynet.manager"
local util = require "util"
local logger = require "logger"
local common_fun = require "model.common_fun"
local stringify = require "stringify"
local pipeline = require "pipeline"
local redisdriver = require "skynet.db.redis"
local asset = require "model.asset"
local redis
local usercenter 
local activitytime
local mailbox


local KEY_STR = "act_ranking:list:%d"
local KEY_ACT_INFO = "act_ranking:act_info"

local THIS_OPEN = 1     -- 开启
local THIS_SYN = 2      -- 最后一次同步数据
local THIS_AWARD = 3    -- 发奖
local THIS_CLOSE = 4    -- 结束


local cjson     = require "cjson"
local cjson_decode   = cjson.decode   -- 解码
local cjson_encode   = cjson.encode   -- 编码

local MAX_RANK = 300
local SHOW_RANK = 100

local compare = function(a, b)
    if a.num > b.num then
        return true
    end
    if a.num == b.num and a.num2 and b.num2 then
        return a.num2 > b.num2
    end
    return false
end

local function inserting(t, n, f)
    assert(type(t) == "table")
    local f = f or compare
	local size = #t
	local n = n or size + 1
	for i = 2, size do
		local num = math.min(i, n)
		if i > n and f(t[i], t[num]) then
			t[i], t[num] = t[num], t[i]
		end
		local temp = t[num]
		local j = num -1 
		while j >= 1 and f(temp, t[j]) do
			t[j+1] = t[j]
			j = j - 1
		end
		t[j+1] = temp
	end
	-- if n < size then
	-- 	t[n+1] = nil
	-- end
    for i = n + 1, size do
        t[i] = nil
    end
	return t
end

local CMD = {}
local ranklist = {

}

local showlist = {

}

-- 活动状态表
local act_list = {
    --[[
        [id] = {
            state = 开启, 发奖, 结束
            list = { -- 参与人员
                [uid] = 1
            }
        }
    ]]
}

local function find_uid(list, uid)
    for k, info in ipairs(list or {})do
        if info.role.uid == uid then
            return k, info
        end
    end
end

local function init_act_info(id, state)
    return {
        id = id,
        state = state or THIS_OPEN,
        list = {}
    }
end

local function get_act_conf(id)
    return asset.ActivityConfig_proto[id]
end

local function save_act_info(id)
    if act_list[id] then
        redis:hset(KEY_ACT_INFO, id, cjson_encode(act_list[id]))
    else
        redis:hdel(KEY_ACT_INFO, id)
    end
end

local function send_award(id)
    local conf = assert(get_act_conf(id), id)
    local act_type = conf.type
    mailbox = mailbox or skynet.localname(".mailbox")

    local send_list = {-- 已发送奖励的玩家
        --[[
            [uid] = 1
        ]]
    } 
    local join_list = (act_list[id] or {}).list or {}
    local rank_list = showlist[id] or {}

    local mail_type_1_100
    local mail_type_100_n
    local source = {
        module = "act_ranking",
        brief = "排行活动奖励",
    }
    if act_type == MODULE_ID_HERO_RANK then
        source.brief = "英雄排行奖励"
        mail_type_1_100 = 11
        mail_type_100_n = 15
    elseif act_type == MODULE_ID_SIMPLE_BATTLE_RANK then
        source.brief = "闯关排行奖励"
        mail_type_1_100 = 12
        mail_type_100_n = 16
    elseif act_type == MODULE_ID_POWER_RANK then
        source.brief = "战力排行奖励"
        mail_type_1_100 = 13
        mail_type_100_n = 17
    elseif act_type == MODULE_ID_ELITE_BATTLE_RANK then
        source.brief = "精英关卡排行奖励"
        mail_type_1_100 = 14
        mail_type_100_n = 18
    else
        assert(false, act_type)
    end

    for i=1, #conf.reward do
        local r1 = assert(conf.parameter1[i*2-1], i)
        local r2 = assert(conf.parameter1[i*2], i)
        local award_id = assert(conf.reward[i], i)
        local _, award_list = common_fun.get_award(award_id, "NewawardConfig_proto")
        for rank = r1, r2 do
            local rankinfo = rank_list[rank]
            if not rankinfo then
                break
            end
            if rank <= SHOW_RANK then
                if rankinfo.role and rankinfo.role.uid then 
                    local uid = rankinfo.role.uid
                    source.context = string.format("第%s名奖励", rank)
                    send_list[uid] = 1
                    skynet.call(mailbox, "lua", "off_line_mail", {uid}, mail_type_1_100, rank, award_list, source)
                else
                    logger.error("错误的玩家数据,%s,%s",id, rank)
                end
            else
                if rank_list[SHOW_RANK] then
                    local uids = {}
                    source.context = string.format("第%s名奖励", rank)
                    for uid in pairs(join_list) do
                    if not send_list[uid] then
                        table.insert(uids, uid)
                    end
                    end
                    if next(uids) then
                        skynet.call(mailbox, "lua", "off_line_mail", uids, mail_type_100_n, "", award_list, source)
                    end
                end
                break
            end
        end
    end
end

local function update()
    for act_id, list in pairs(ranklist) do
        inserting(list, MAX_RANK)
        showlist[act_id] = {}
        local key = string.format(KEY_STR, act_id)
        redis:del(key)
        local batch = pipeline(100)
        for rank, data in ipairs(list) do
            batch.add("hset", key, data.role.uid, cjson_encode(data))
            showlist[act_id][rank] = common_fun.scopy(data)
        end
        batch.execute(redis,{})
    end
end

local function check_over()
    local now = os.time()
    for id, data in pairs(act_list) do
        if data.state ~= THIS_CLOSE then
            activitytime = activitytime or skynet.localname(".activitytime")
            usercenter = usercenter or skynet.localname(".usercenter")
            local info = skynet.call(activitytime, "lua", "get_sid_info", id)
            local conf = get_act_conf(id)
            if info and conf then
                if data.state == THIS_OPEN then
                    if now > info.endtime - conf.parameter3[1]*HOUR_SEC then
                        data.state = THIS_SYN
                        -- 通知在线玩家同步
                        skynet.call(usercenter, "lua", "command", "syn_act_rank", conf.type)
                        save_act_info(id)
                    end
                elseif data.state == THIS_SYN then
                    if now > info.endtime - conf.parameter3[1]*HOUR_SEC + 10 * MIN_SEC then
                        -- 发奖
                        local ok, msg = xpcall(send_award, debug.traceback, id)
                        data.state = THIS_AWARD
                        save_act_info(id)
                        if not ok then
                            logger.errno(msg)
                        end
                    end
                elseif data.state == THIS_AWARD then
                    if now > info.endtime then
                        data.state = THIS_CLOSE
                        -- 清空数据
                        save_act_info(id)
                    end
                end
            else
                logger.error("活动排行,错误的数据info:%s, conf%s, id:%d", type(info), type(conf), id)
            end
        end
    end
end

local function init()
    local conf = assert(option.redis)
    redis = redisdriver.connect(conf)
    redis:select(12)
    local retnuma = redis:hgetall(KEY_ACT_INFO)
    for k = 1, #retnuma, 2 do
        local key = tonumber(retnuma[k])
        local data = cjson_decode(retnuma[k+1])
        if key and data then
            act_list[key] = data
        end
    end
    for act_id, info in pairs(act_list) do
        if info.state ~= THIS_CLOSE then
            ranklist[act_id] = {}
            local key = string.format(KEY_STR, act_id)
            local retnuma = redis:hgetall(key)
            for k = 1, #retnuma, 2 do
                local data = cjson_decode(retnuma[k+1])
                ranklist[act_id][#ranklist[act_id]+1] = data
            end
            
        end
    end
end

local function watchtime()
    skynet.fork(function()
        local interval = 60*2 -- 更新间隔为2分钟
        local up_time = os.time()
        while(true) do
            local now = os.time()
            if now > up_time then
                up_time = now + interval
                update()
                check_over()
            end
            skynet.sleep(300)
        end
    end)
end

function CMD.player_rename(uid, name)
    for _, list in pairs(ranklist) do
        local _, info = find_uid(list, uid)
        if info then
            info.role.nickname = name
        end
    end
end

function CMD.upload(act_id, data)
    if not act_list[act_id] then
        activitytime = activitytime or skynet.localname(".activitytime")
        local info = skynet.call(activitytime, "lua", "get_sid_info", act_id)
        if not info then
            logger.error("活动排行 未找到活动数据:%s", act_id)
            return 
        end
        if info.state == STATE_OPEN then
            act_list[act_id] = init_act_info(act_id)
        else
            act_list[act_id] = init_act_info(act_id, THIS_CLOSE)
        end
        save_act_info(act_id)
    end

    if act_list[act_id].state ~= THIS_OPEN and act_list[act_id].state ~= THIS_SYN then
        return
    end
    act_list[act_id].list = act_list[act_id].list or {}
    if not act_list[act_id].list[data.role.uid] then
        act_list[act_id].list[data.role.uid] = 1
        save_act_info(act_id)
    end
    local key = string.format(KEY_STR, act_id)
    ranklist[act_id] = ranklist[act_id] or {}
    local list = ranklist[act_id]

    local k = find_uid(list, data.role.uid)
    if k then
        list[k] = data
        redis:hset(key, data.role.uid, cjson_encode(data))
        return
    end

    local show = showlist[act_id] or {}
    if show[MAX_RANK] and show[MAX_RANK].num > data.num then
        return
    end
    list[#list+1] = data
    redis:hset(key, data.role.uid, cjson_encode(data))
end

function CMD.get_ranking(act_id)
    logger.trace("showlist"..stringify(showlist))
    return showlist[act_id] or {}
end

-- 从排行榜中删除玩家
function CMD.del(uid)
    for act_id, list in pairs(showlist) do
        local act_info = act_list[act_id]
        if not act_info or (act_info.state == THIS_AWARD or act_info.state == THIS_SYN) then
            local k = find_uid(list, uid)
            if k then
                table.remove(list, k)
            end
        end
    end

    for act_id, list in pairs(ranklist) do
        local act_info = act_list[act_id]
        if not act_info or (act_info.state == THIS_AWARD or act_info.state == THIS_SYN) then
            local key = string.format(KEY_STR, act_id)
            local k = find_uid(list, uid)
            if k then
                table.remove(list, k)
                redis:hdel(key, uid)
            end
        end
    end
end

function CMD.start()
    init()
    watchtime()
end

skynet.init(function()
  skynet.register(".act_rankinglist")
end)

skynet.start(function()
  skynet.dispatch("lua", function(session, _, cmd, ...)
    local f = assert(CMD[cmd])
    if 0==session then
      f(...)
    else
      skynet.retpack(f(...))
    end
  end)
end)