%% ------------------------------
%% desc     :经验副本波数排行
%% module   :exp_dun_wave_rank.erl
%% author   :hh
%% time     :21/05/11
%% ------------------------------

-module (exp_dun_wave_rank).

-behaviour (gen_server).

-include ("common.hrl").
-include ("dungeon.hrl").
-include ("figure.hrl").

%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).

-export ([
    start_link/0
    ,refresh_exp_wave_rank/3
    ,get_wave_rank/3
    ,get_top_wave_rank/2
    ]).

-export([
    role_login/2
    ,calc_rank_range/2
    ]).

% -define (RANK_MAX, 50).

-define (REFRESH_RANK, 30).

-define (RANK_INTERVAL, 180000).

-define (SELECT_EXP_DUN_RANK, 
    <<"SELECT `role_id`, `wave`, `time` FROM role_exp_wave_rank">>).
-define (REPLACE_EXP_DUN_RANK, 
    <<"REPLACE INTO role_exp_wave_rank SET `role_id` = ~p, `wave` = ~p, `time` = ~p">>).
-define (DELETE_EXP_DUN_RANK, 
    <<"DELETE FROM role_exp_wave_rank WHERE role_id = ~p">>).

-record (exp_dun_rank_state, {
    rank = [],          %% #exp_dun_rank_state{} 排行数据
    limit = 0,          %% 上榜限制
    last_time = 0,      %% 上次更新时间
    ref = undefined,
    need_rank = 0       %% 是否需要刷新数据 0否|1是
    }).

-record (exp_wave_rank_data, {
    id = 0,             %% 玩家id
    wave = 0,
    time = 0            %% 上榜时间
    }).

start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

role_login(RoleId, Now) ->
    [DunId|_] = data_dungeon:get_ids_by_type(?DUNGEON_TYPE_EXP),
    case db:get_one(<<"SELECT data FROM dungeon_best_record WHERE player_id = ~p AND dun_id = ~p">>, [RoleId, DunId]) of
        DataBin when is_binary(DataBin) orelse is_bitstring(DataBin) ->
            DataList = util:bitstring_to_term(DataBin),
            case lists:keyfind(?DUNGEON_REC_MAX_SCORE, 1, DataList) of
                {_, MaxWave} ->
                    refresh_exp_wave_rank(RoleId, MaxWave, Now);
                _ ->
                    skip
            end;
        _ ->
            skip
    end.

get_wave_rank(RoleId, Sid, {PageSize, PageNo}) ->
    gen_server:cast(?MODULE, {get_wave_rank, RoleId, Sid, {PageSize, PageNo}}).

get_top_wave_rank(RoleId, Sid) ->
    gen_server:cast(?MODULE, {get_top_wave_rank, RoleId, Sid}).

%% 通关完成副本更新排行榜数据
refresh_exp_wave_rank(RoleId, Wave, Time) ->
    gen_server:cast(?MODULE, {refresh_exp_wave_rank, RoleId, Wave, Time}).

init([]) ->
    erlang:send_after(1000, self(), {base_init}),
    {ok, #exp_dun_rank_state{}}.

handle_call(_Request, _From, State) ->
    {reply, ok, State}.

handle_cast(Msg, State) ->
    try do_handle_cast(Msg, State) of
    Reply -> Reply
    catch ?EXCEPTION(Class, Reason, StackTrace) ->
        Error = {Class, Reason, ?GET_STACK(StackTrace)},
        ?ERR("exp_dun wave rank ~w:state error:~p~n", [self(), Error]),
        {noreply, State}
    end.

handle_info(Msg, State) ->
    try do_handle_info(Msg, State) of
    Reply -> Reply
    catch ?EXCEPTION(Class, Reason, StackTrace) ->
        Error = {Class, Reason, ?GET_STACK(StackTrace)},
        ?ERR("exp_dun wave rank ~w:state error:~p~n", [self(), Error]),
        {noreply, State}
    end.

terminate(_Reason, _State) ->
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

do_handle_cast({refresh_exp_wave_rank, RoleId, Wave, Time}, State) ->
    #exp_dun_rank_state{rank = RankData, last_time = LastTime, limit = LimitLevel} = State,
    Now = utime:unixtime(),
    RestTime = Now - LastTime,
    RankRole = #exp_wave_rank_data{id = RoleId, wave = Wave, time = Time},
    {NewRankData, MinLimit} = update_exp_wave_rank(RankRole, RankData, LimitLevel),
    if
        RestTime >= ?REFRESH_RANK ->
            {SortRankData, MinLimit1} = sort_rank_info(NewRankData),
            NewState = State#exp_dun_rank_state{rank = SortRankData, limit = MinLimit1, last_time = Now, need_rank = 0},
            {noreply, NewState};
        true ->
            NewState = State#exp_dun_rank_state{rank = NewRankData, limit = MinLimit, need_rank = 1},
            {noreply, NewState}
    end;

do_handle_cast({get_wave_rank, _RoleId, Sid, {PageSize, PageNo}}, State) ->
    #exp_dun_rank_state{rank = RankList, need_rank = NeedRank, limit = OldMinLimit} = State,
    if NeedRank == 1 ->
            {NewRankList, MinLimit} = sort_rank_info(RankList);
        true ->
            NewRankList = RankList, MinLimit = OldMinLimit
    end,
    Sum = length(NewRankList),
    {Start, Len} = calc_rank_range(PageSize, PageNo),
    case Start > Sum orelse Start =< 0 orelse Len =< 0 of
        true ->
            RankInfos   = pack_rank_list(NewRankList, 1),
            lib_server_send:send_to_sid(Sid, pt_610, 61054, [umath:ceil(Sum/PageSize), PageNo, RankInfos]),
            NewState =
            if NeedRank == 1 ->
                    State#exp_dun_rank_state{rank = NewRankList, limit = MinLimit, need_rank = 0, last_time = utime:unixtime()};
                true ->
                    State
            end;
        _ ->
            RankInfos   = pack_rank_list(NewRankList, 1),
            SubRankInfos = lists:sublist(RankInfos, Start, Len),
            lib_server_send:send_to_sid(Sid, pt_610, 61054, [umath:ceil(Sum/PageSize), PageNo, SubRankInfos]),
            NewState =
            if NeedRank == 1 ->
                    State#exp_dun_rank_state{rank = NewRankList, limit = MinLimit, need_rank = 0, last_time = utime:unixtime()};
                true ->
                    State
            end
    end,
    {noreply, NewState};

do_handle_cast({get_top_wave_rank, RoleId, Sid}, State) ->
    #exp_dun_rank_state{rank = RankList, need_rank = NeedRank, limit = OldMinLimit} = State,
    if NeedRank == 1 ->
            {NewRankList, MinLimit} = sort_rank_info(RankList);
        true ->
            NewRankList = RankList, MinLimit = OldMinLimit
    end,
    RankInfos = pack_rank_list(NewRankList, 1),
    SubRankInfos = pack_top_role_pic(RankInfos),
    case lists:keyfind(RoleId, 3, RankInfos) of
        {_Wave, Rank, RoleId, _RoleName, _RoleLv, _SupVip, _Vip, _Sex, _GuildName} ->
            ok;
        _ -> Rank = 0
    end,
    {ok, BinData} = pt_610:write(61055, [Rank, SubRankInfos]),
    lib_server_send:send_to_sid(Sid, BinData),
    NewState = 
    if NeedRank == 1 ->
            State#exp_dun_rank_state{rank = NewRankList, limit = MinLimit, need_rank = 0, last_time = utime:unixtime()};
        true ->
            State
    end,
    {noreply, NewState};

do_handle_cast(_Msg, State) ->
    {noreply, State}.

do_handle_info({base_init}, State) ->
    NewState =
    case db:get_all(?SELECT_EXP_DUN_RANK) of
        DataList = [_|_] ->
            {RankData, LimitLevel} = make_rank_info(DataList),
            Now = utime:unixtime(),
            State#exp_dun_rank_state{rank = RankData, last_time = Now, limit = LimitLevel};
        _ ->
            State
    end,
    Ref = erlang:send_after(?RANK_INTERVAL, self(), {sort_rank_info}),
    {noreply, NewState#exp_dun_rank_state{ref = Ref}};

do_handle_info({sort_rank_info}, State) ->
    #exp_dun_rank_state{rank = RankData, last_time = LastTime, ref = OldRef} = State,
    Now = utime:unixtime(),
    RestTime = Now - LastTime,
    if 
        RestTime >= ?REFRESH_RANK -> %% 超过30秒,刷新一波
            {SortRankData, MinLimit} = sort_rank_info(RankData),
            NewRef = util:send_after(OldRef, ?RANK_INTERVAL, self(), {sort_rank_info}),
            NewState = State#exp_dun_rank_state{rank = SortRankData, ref = NewRef, last_time = Now, limit = MinLimit, need_rank = 0};
        true ->
            NewRef = util:send_after(OldRef, ?RANK_INTERVAL, self(), {sort_rank_info}),
            NewState = State#exp_dun_rank_state{ref = NewRef}
    end,
    {noreply, NewState};

do_handle_info(_Msg, State) ->
    {noreply, State}.

make_rank_info(DataList) ->
    DataList1 = make_rank_info(DataList, []),
    sort_rank_info(DataList1).

make_rank_info([], AccRank) -> AccRank;
make_rank_info([[RoleId, Wave, Time|_]|Rest], AccRank) ->
    RankInfo = #exp_wave_rank_data{
        id = RoleId,
        wave = Wave,
        time = Time
    },
    make_rank_info(Rest, [RankInfo|AccRank]).

sort_rank_info(RankList) ->
    SortFun = fun(
        #exp_wave_rank_data{wave = W1, time = T1}, 
        #exp_wave_rank_data{wave = W2, time = T2}) -> 
            if
                W1 > W2  -> true;
                W1 == W2 -> T1 =< T2;
                true -> false 
            end
    end,
    SortRankData = lists:sort(SortFun, RankList),
    RankLen = length(SortRankData),
    RankMax = data_exp_dun_drop:get_kv(10),
    if
        RankLen >= RankMax ->
            {SortRankData1, DeleteList} = ulists:sublist(SortRankData, RankMax),
            %% 定期删除数据
            DeleteList =/= [] andalso delete_old_rank_data(DeleteList),
            #exp_wave_rank_data{wave = MinLimit} = lists:last(SortRankData1),
            {SortRankData1, MinLimit};
        true ->
            if SortRankData == [] -> {SortRankData, 0};
                true -> 
                    #exp_wave_rank_data{wave = MinLimit} = lists:last(SortRankData),
                    {SortRankData, MinLimit}
            end
    end.

delete_old_rank_data(RankList) ->
    [db:execute(?DELETE_EXP_DUN_RANK, [RoleId]) || #exp_wave_rank_data{id = RoleId} <- RankList],
    ok.

db_replace_rank_role(RankRole) ->
    #exp_wave_rank_data{id = RoleId, wave = Wave, time = Time} = RankRole,
    db:execute(?REPLACE_EXP_DUN_RANK, [RoleId, Wave, Time]),
    ok.

update_exp_wave_rank(RankRole, RankData, LimitWave) ->
    RankLen = length(RankData),
    #exp_wave_rank_data{id = RoleId, wave = Wave} = RankRole,
    RankMax = data_exp_dun_drop:get_kv(10),
    if
        RankLen >= RankMax ->
            if Wave < LimitWave ->
                    {RankData, LimitWave};
                true ->
                    case lists:keyfind(RoleId, #exp_wave_rank_data.id, RankData) of
                        #exp_wave_rank_data{wave = OldWave} when Wave > OldWave ->
                            NewRankData = lists:keystore(RoleId, #exp_wave_rank_data.id, RankData, RankRole),
                            db_replace_rank_role(RankRole),
                            {NewRankData, min(LimitWave, Wave)};
                        false ->
                            NewRankData = lists:keystore(RoleId, #exp_wave_rank_data.id, RankData, RankRole),
                            db_replace_rank_role(RankRole),
                            {NewRankData, min(LimitWave, Wave)};
                        _ ->
                            {RankData, LimitWave}                      
                    end
            end;
        true ->
            case lists:keyfind(RoleId, #exp_wave_rank_data.id, RankData) of
                #exp_wave_rank_data{wave = OldWave} when Wave > OldWave ->
                    NewRankData = lists:keystore(RoleId, #exp_wave_rank_data.id, RankData, RankRole),
                    db_replace_rank_role(RankRole),
                    {NewRankData, min(LimitWave, Wave)};
                false ->
                    NewRankData = lists:keystore(RoleId, #exp_wave_rank_data.id, RankData, RankRole),
                    db_replace_rank_role(RankRole),
                    {NewRankData, min(LimitWave, Wave)};
                _ ->
                    {RankData, LimitWave}                      
            end
    end.

calc_rank_range(PageSize, PageNo) ->
    if PageNo =< 0 -> {1, PageSize};
        true ->
            Start = (PageNo-1)*PageSize + 1,
            Len   = PageSize,
            {Start, Len}
    end.

pack_rank_list(SubRankList, Index) ->
    {_, RankInfos} = lists:foldl(fun(RankData, {AccIndex, AccRankInfo}) -> 
        #exp_wave_rank_data{id = RoleId, wave = Wave} = RankData,
        #figure{name = RoleName, lv = RoleLv, vip = Vip, 
            sup_vip = SupVip, sex = Sex, guild_name = GuildName} = lib_role:get_role_figure(RoleId),
        {AccIndex+1, [{Wave, AccIndex, RoleId, RoleName, RoleLv, SupVip, Vip, Sex, GuildName}|AccRankInfo]}
    end, {Index, []}, SubRankList),
    lists:keysort(2, RankInfos).

pack_top_role_pic(RankInfos) ->
    case lists:sublist(RankInfos, 1, 3) of
        SubRankInfos = [_|_] ->
            lists:foldl(fun({_Wave, Rank, RoleId, _RoleName, _RoleLv, _SupVip, _Vip, _Sex, _GuildName}, AccPics) -> 
                Figure = lib_role:get_role_figure(RoleId),
                RecPicture = lib_role:figure_to_rec_picture(Figure),
                [{Rank, RecPicture}|AccPics]
            end, [], SubRankInfos);
        _ -> []
    end.