%% ------------------------------
%% desc     :幻光副本扫荡排行
%% module   :visus_dun_rank.erl
%% author   :hh
%% time     :21/05/11
%% ------------------------------

-module (visus_dun_rank).

-behaviour (gen_server).

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

%% API
-export ([
    start_link/0
    ,get_visus_dun_rank/5
    ,refresh_visus_rank/3
    ,load_visus_rank/1
    ,handle_event/2
    ]).

-include ("dungeon.hrl").
-include ("common.hrl").
-include ("server.hrl").
-include ("figure.hrl").
-include ("def_event.hrl").
-include ("rec_event.hrl").

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

-record (visus_rank_data, {
    id = 0,             %% 玩家id
    level = 0,          %% 排序值
    show = {0, 0},      %% {层,波数}
    power = 0           %% 战力
    }).

-ifdef(DEV_SERVER).
-define (RANK_MAX, 100).
-else.
-define (RANK_MAX, 100).
-endif.

-define (REFRESH_RANK, 30).

-define (RANK_INTERVAL, 180000).

-define (SELECT_VISUS_RANK, 
    <<"SELECT `role_id`, `level`, `show`, `power` FROM role_visus_rank">>).
-define (REPLACE_VISUS_RANK, 
    <<"REPLACE INTO role_visus_rank SET `role_id` = ~p, `level` = ~p, `show` = ~ts, `power` = ~p">>).
-define (DELETE_VISUS_RANK, 
    <<"DELETE FROM role_visus_rank WHERE role_id = ~p">>).

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

get_visus_dun_rank(RoleId, Sid, Start, Len, {MaxDunId, MaxDunScore}) ->
    gen_server:cast(?MODULE, {get_visus_dun_rank, RoleId, Sid, Start, Len, {MaxDunId, MaxDunScore}}).

refresh_visus_rank(Player, DunId, WaveNum) ->
    RankRole = change_to_visus_rank_role(Player, DunId, WaveNum),
    RankRole#visus_rank_data.level =/= 0 andalso refresh_visus_rank(RankRole).

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

refresh_visus_rank(RankRole) ->
    gen_server:cast(?MODULE, {refresh_visus_rank, RankRole}).

handle_event(Player, #event_callback{type_id = ?EVENT_LOGIN_CAST}) ->
    #player_status{id = RoleId, figure = #figure{power = Power}} = Player,
    {Level, {DunId, WaveNum}} = load_visus_rank(RoleId),
    RankRole = #visus_rank_data{
        id = RoleId,
        level = Level,
        show = {DunId, WaveNum},
        power = Power    
    },
    Level =/= 0 andalso refresh_visus_rank(RankRole),
    {ok, Player}.

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("visus dun 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("visus dun 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({get_visus_dun_rank, RoleId, Sid, Start, Len, {MaxDunId, MaxDunScore}}, State) ->
    %% 获取排行数据前先检查是否需要更新排序数据
    #visus_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),
    NewState =
    case Start > Sum orelse Start =< 0 orelse Len =< 0 of
        true ->
            Rank = calc_role_rank(RoleId, NewRankList),
            lib_server_send:send_to_sid(Sid, pt_611, 61117, [Rank, MaxDunId, MaxDunScore, Sum, []]),
            if NeedRank == 1 ->
                    State#visus_rank_state{rank = NewRankList, limit = MinLimit, need_rank = 0, last_time = utime:unixtime()};
                true ->
                    State
            end;
        _ ->
            SubRankList = lists:sublist(NewRankList, Start, Len),
            RankInfos   = pack_rank_list(SubRankList, Start),
            Rank = calc_role_rank(RoleId, NewRankList),
            % case lists:keyfind(RoleId, 1, RankInfos) of
            %     {_, _, _, _, Rank} -> ok;
            %     _ -> Rank = 0
            % end,
            % ?ERR("~p ~p", [RankInfos, Rank]),
            lib_server_send:send_to_sid(Sid, pt_611, 61117, [Rank, MaxDunId, MaxDunScore, Sum, RankInfos]),
            if NeedRank == 1 ->
                    State#visus_rank_state{rank = NewRankList, limit = MinLimit, need_rank = 0, last_time = utime:unixtime()};
                true ->
                    State
            end
    end,
    {noreply, NewState};

%% 打副本通波时更新数据
do_handle_cast({refresh_visus_rank, RankRole}, State) ->
    #visus_rank_state{rank = RankData, last_time = LastTime, limit = LimitLevel} = State,
    Now = utime:unixtime(),
    RestTime = Now - LastTime,
    {NewRankData, MinLimit} = update_visus_rank(RankRole, RankData, LimitLevel),
    if
        RestTime >= ?REFRESH_RANK ->
            {SortRankData, MinLimit1} = sort_rank_info(NewRankData),
            NewState = State#visus_rank_state{rank = SortRankData, limit = MinLimit1, last_time = Now, need_rank = 0},
            {noreply, NewState};
        true ->
            NewState = State#visus_rank_state{rank = NewRankData, limit = MinLimit, need_rank = 1},
            {noreply, NewState}
    end;

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

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

do_handle_info({sort_rank_info}, State) ->
    #visus_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#visus_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#visus_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, Level, ShowBin, Power|_]|Rest], AccRank) ->
    RankInfo = #visus_rank_data{
        id = RoleId,
        level = Level,
        show = util:bitstring_to_term(ShowBin),
        power = Power
    },
    make_rank_info(Rest, [RankInfo|AccRank]).

sort_rank_info(RankList) ->
    SortFun = fun(#visus_rank_data{level = L1, power = P1}, 
        #visus_rank_data{level = L2, power = P2}) -> 
            if
                L1 > L2  -> true;
                L1 == L2 -> P1 >= P2;
                true -> false 
            end
    end,
    SortRankData = lists:sort(SortFun, RankList),
    RankLen = length(SortRankData),
    if
        RankLen >= ?RANK_MAX ->
            {SortRankData1, DeleteList} = ulists:sublist(SortRankData, ?RANK_MAX),
            %% 定期删除数据
            DeleteList =/= [] andalso delete_old_rank_data(DeleteList),
            #visus_rank_data{level = MinLimit} = lists:last(SortRankData1),
            {SortRankData1, MinLimit};
        true ->
            if SortRankData == [] -> {SortRankData, 0};
                true -> 
                    #visus_rank_data{level = MinLimit} = lists:last(SortRankData),
                    {SortRankData, MinLimit}
            end
    end.

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

db_replace_rank_role(RankRole) ->
    #visus_rank_data{id = RoleId, level = Level, show = Show, power = Power} = RankRole,
    db:execute(?REPLACE_VISUS_RANK, [RoleId, Level, util:term_to_bitstring(Show), Power]),
    ok.

update_visus_rank(RankRole, RankData, LimitLevel) ->
    RankLen = length(RankData),
    #visus_rank_data{id = RoleId, level = Level} = RankRole,
    if
        RankLen >= ?RANK_MAX ->
            if Level < LimitLevel ->
                    {RankData, LimitLevel};
                true ->
                    case lists:keyfind(RoleId, #visus_rank_data.id, RankData) of
                        #visus_rank_data{level = OldLevel} when Level > OldLevel ->
                            NewRankData = lists:keystore(RoleId, #visus_rank_data.id, RankData, RankRole),
                            db_replace_rank_role(RankRole),
                            {NewRankData, min(LimitLevel, Level)};
                        false ->
                            NewRankData = lists:keystore(RoleId, #visus_rank_data.id, RankData, RankRole),
                            db_replace_rank_role(RankRole),
                            {NewRankData, min(LimitLevel, Level)};
                        _ ->
                            {RankData, LimitLevel}                      
                    end
            end;
        true ->
            case lists:keyfind(RoleId, #visus_rank_data.id, RankData) of
                #visus_rank_data{level = OldLevel} when Level > OldLevel ->
                    NewRankData = lists:keystore(RoleId, #visus_rank_data.id, RankData, RankRole),
                    db_replace_rank_role(RankRole),
                    {NewRankData, min(LimitLevel, Level)};
                false ->
                    NewRankData = lists:keystore(RoleId, #visus_rank_data.id, RankData, RankRole),
                    db_replace_rank_role(RankRole),
                    {NewRankData, min(LimitLevel, Level)};
                _ ->
                    {RankData, LimitLevel}                      
            end        
    end.

change_to_visus_rank_role(Player, DunId, WaveNum) ->
    #player_status{id = RoleId, figure = #figure{power = Power}} = Player,
    Level = calc_pass_wave(DunId, WaveNum),
    #visus_rank_data{
        id = RoleId,
        level = Level,
        show = {DunId, WaveNum},
        power = Power
    }.

calc_pass_wave(DunId, WaveNum) ->
    DunIds = data_dungeon:get_ids_by_type(?DUNGEON_TYPE_VISUS),
    case lists:filter(fun(ConfigDunId) -> ConfigDunId =< DunId end, DunIds) of
        PassDunIds = [_|_] ->
            lists:foldl(fun(DunIdX, Acc) ->
                if DunId == DunIdX -> Acc + WaveNum;
                    true -> Acc + length(data_dungeon_wave:get_dun_waves(DunIdX))
                end
            end, 0, PassDunIds);
        _ ->
            0
    end.

load_visus_rank(RoleId) ->
    DunIds = data_dungeon:get_ids_by_type(?DUNGEON_TYPE_VISUS),
    lists:foldl(fun(DunId, {AccLevel, {MaxDunId, MaxWaveNum}}) -> 
        case catch 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
                    {_, DunMaxWaveNum} ->
                        if DunId > MaxDunId ->
                                {AccLevel + DunMaxWaveNum, {DunId, DunMaxWaveNum}};
                            true ->
                                {AccLevel + DunMaxWaveNum, {MaxDunId, MaxWaveNum}}
                        end;
                    _ ->
                        {AccLevel, {MaxDunId, MaxWaveNum}}
                end;
            _ -> {AccLevel, {MaxDunId, MaxWaveNum}}
        end
    end, {0, {0, 0}}, DunIds).

%% 打包数据列表
pack_rank_list(SubRankList, Index) ->
    {_, RankInfos} = lists:foldl(fun(RankData, {AccIndex, AccRankInfo}) -> 
        #visus_rank_data{
            id = RoleId,
            show = {DunId, WaveNum}
        } = RankData,
        Figure = lib_role:get_role_figure(RoleId),
        RecPicture = lib_role:figure_to_rec_picture(Figure),
        {AccIndex+1, [{RoleId, RecPicture, DunId, WaveNum, AccIndex}|AccRankInfo]}
    end, {Index, []}, SubRankList),
    RankInfos.

calc_role_rank(RoleId, RankList) ->
    {_, Rank} =
    lists:foldl(fun(#visus_rank_data{id = RoleIdX}, {Index, OldIndex}) -> 
        if RoleId == RoleIdX -> {Index+1, Index};
            true -> {Index+1, OldIndex}
        end
    end, {1, 0}, RankList),
    Rank.