%% --------------------------------
%% desc     :跨服排行(等级/战力)
%% module   :cross_rank_mgr.erl
%% author   :hh
%% time     :20/10/22
%% --------------------------------

-module (cross_rank_mgr).
-behaviour (gen_server).

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

-export ([
    start_link/0
    ,local_rank_upload_callback/2
    ,timer_update/0
    ,download_zone_rank/2
    ,recalc_zone/1
    ,zero_clock/0
    ]).

-export([
    get_target_role_figure/4
    ,return_rec_pic/4
    ,return_rec_pic_callback/3
    ]).

-include("common_rank.hrl").
-include("common.hrl").

-define(INIT_DELAY,             30000).     %% 初始化
-define(FORCE_UPDATE_PRE_TIME,  60000).     %% 强制更新同步状态检查
-define(FORCE_UPFATE_TIME,      30000).     %% 强制更新同步状态

-define(INTERVAL_UPDATE,       600000).

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

local_rank_upload_callback(ServerId, ZipRankL) ->
    gen_server:cast(?MODULE, {local_rank_upload_callback, ServerId, ZipRankL}).

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

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

download_zone_rank(ServerId, RankType) ->
    gen_server:cast(?MODULE, {download_zone_rank, ServerId, RankType}).

recalc_zone(Map) ->
    gen_server:cast(?MODULE, {recalc_zone, Map}).

init(_) ->
    process_flag(trap_exit, true),
    Ref  = erlang:send_after(?INIT_DELAY, self(), {rank_init}),
    Maps = #{?RANK_INIT => Ref},
    {ok, #cross_rank_state{maps = Maps}}.


handle_call(Msg, From, State) ->
    try
        do_handle_call(Msg, From, State)
    catch ?EXCEPTION(_Class, _Error, StackTrace) ->
        ?ERR("do_handle_call Msg:~p, Error:~p, ~p~n", [Msg, _Error, ?GET_STACK(StackTrace)]),
        {reply, error, State}
    end.

handle_cast(Msg, State) ->
    try
        do_handle_cast(Msg, State)
    catch ?EXCEPTION(_Class, _Error, StackTrace) ->
        ?ERR("handle_cast Msg:~p, Error:~p, ~p~n", [Msg, _Error, ?GET_STACK(StackTrace)]),
        {noreply, State}
    end.

handle_info(Msg, State) ->
    try
        do_handle_info(Msg, State)
    catch ?EXCEPTION(_Class, _Error, StackTrace) ->
        ?ERR("handle_info Msg:~p~n Error:~p~n ~p~n", [Msg, _Error, ?GET_STACK(StackTrace)]),
        {noreply, State}
    end.

terminate(_Reason, _State) ->
    ok.

code_change(_OldVsn, _State, _Extra) ->
    ok.


do_handle_call(_Msg, _From, State) ->
    {reply, ok, State}.

do_handle_cast({local_rank_upload_callback, ServerId, ZipRankL}, State) ->
    #cross_rank_state{srv_zones = SrvZoneMap, upload_status = UploadStatus} = State,
    case maps:get(ServerId, SrvZoneMap, undefined) of
        ZoneId when is_integer(ZoneId) ->
            UnZipRankLBin = zlib:unzip(ZipRankL),
            {RankType, LocRankL} = util:bitstring_to_term(UnZipRankLBin),
            #cross_rank_state{srv_upload_status = SrvUploadMap} = NewState = update_rank(ServerId, {RankType, LocRankL}, State),
            {NewSrvUploadMap, NewUploadStatus} = update_upload_status(ZoneId, ServerId, RankType, SrvUploadMap, UploadStatus, SrvZoneMap),
            {noreply, NewState#cross_rank_state{srv_upload_status = NewSrvUploadMap, upload_status = NewUploadStatus}};
        _ ->
            ?ERR("server [~p] have no zone", [ServerId]),
            {noreply, State}
    end;

do_handle_cast({download_zone_rank, ServerId, RankType}, State) ->
    #cross_rank_state{srv_zones = SrvZoneMap, upload_status = UploadStatus} = State,
    case maps:get(ServerId, SrvZoneMap, undefined) of
        ZoneId when is_integer(ZoneId) ->
            case lists:keyfind(ZoneId, 1, UploadStatus) of
                {_, 1} ->
                    ZoneTypeRankL = get_rank_list({ZoneId, RankType}),
                    ZipRankL = zlib:zip(util:term_to_bitstring({RankType, ZoneTypeRankL})),
                    download_top_rec_pic(ServerId, RankType, ZoneTypeRankL),
                    clusters_center:apply_cast(ServerId, cross_rank_local, download_zone_rank_callback, [ZipRankL]);
                _A ->
                    % ?ERR("zone srv upload not finish ~p ~p ~p", [ZoneId, ServerId, UploadStatus]),
                    skip
            end;
        _ ->
            ?ERR("server [~p] have no zone", [ServerId]),
            skip
    end,
    {noreply, State};

do_handle_cast({recalc_zone, Map}, State) ->
    #cross_rank_state{
        srv_zones = _OldSrvZoneMap, rank_limit = RankLimit, 
        srv_upload_status = SrvUploadMap, upload_status = UploadStatus} = State,
    #{zone_map := NewZoneMap, svr_zone_map := NewSrvZoneMap} = Map,
    RankTypeL   = get_cross_rank_type(),
    %% 更新变动分区的排行数据
    {NewRankLimit, ChangZones} = maps:fold(fun(OldZoneId, NewZoneId, {TempRankLimit, TempChangeZones}) -> 
        NewTempRankLimit = recalc_zone_update_rank(OldZoneId, NewZoneId, RankTypeL, TempRankLimit),
        {NewTempRankLimit, [{NewZoneId, OldZoneId}|TempChangeZones]}
    end, {RankLimit, []}, NewZoneMap),
    {NewSrvUploadMap, NewUploadStatus} = recalc_zone_srv_upload_status(ChangZones, SrvUploadMap, UploadStatus),
    % ?ERR("cross rank recalc zone ~n ~p ~n ~p ~n ~p ~p", [SrvUploadMap, NewSrvUploadMap, NewUploadStatus, ChangZones]),
    NewState = State#cross_rank_state{
        srv_zones = NewSrvZoneMap, rank_limit = NewRankLimit, 
        srv_upload_status = NewSrvUploadMap, upload_status = NewUploadStatus
    },
    %% 通知所在分区排行更新
    RankTypeList = get_cross_rank_type(),
    notify_zone_local_rank_update([ZoneIdX || {ZoneIdX, _OldZoneIdX} <- ChangZones], RankTypeList, NewSrvZoneMap),
    {noreply, NewState};

do_handle_cast({zero_clock}, State) ->
    RankMaps = lists:foldl(fun
        (?CROSS_RANK_TYPE_KEY({ZoneId, RankType}), TmpMap) -> 
            RankList = get_rank_list({ZoneId, RankType}),
            NewTmpMap = maps:put({ZoneId, RankType}, RankList, TmpMap),
            NewTmpMap;
        (_, TmpMap) -> TmpMap
     end, #{}, erlang:get_keys()),
    Now = utime:unixtime(),
    spawn(fun() -> daily_log(Now, RankMaps) end),
    {noreply, State};

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


do_handle_info({rank_init}, State) ->
    #cross_rank_state{maps = Map} = State,
    InitRef = maps:get(?RANK_INIT, Map, undefined),
    util:cancel_timer(InitRef),
    ForceRef = erlang:send_after(?FORCE_UPDATE_PRE_TIME, self(), {force_update_preview}),
    IntervalUpdateRef = erlang:send_after(?INTERVAL_UPDATE, self(), {timer_update}),
    NewMap = maps:put(?RANK_INIT, undefined, Map),
    NewMap1 = maps:put(?FORCE_UPDATE_PRE, ForceRef, NewMap),
    NewMap2 = maps:put(?INTERVAL_UPDATE_REF, IntervalUpdateRef, NewMap1),
    SrvZoneMap = czone:get_svr_zone_map(),
    UploadStatus = reset_srv_upload_status(SrvZoneMap),
    NewState = State#cross_rank_state{
        srv_zones = SrvZoneMap, maps = NewMap2, srv_upload_status = UploadStatus},
    notify_local_rank_upload(SrvZoneMap),
    {noreply, NewState};

%% 检查是否还有未同步的游戏服
do_handle_info({force_update_preview}, State) ->
    #cross_rank_state{maps = Maps, srv_upload_status = SrvUploadMap} = State,
    Ref = maps:get(?FORCE_UPDATE_PRE, Maps, undefined),
    util:cancel_timer(Ref),
    check_srv_upload_status(SrvUploadMap),
    NewMaps = maps:put(?FORCE_UPDATE_PRE, undefined, Maps),
    Ref1 = erlang:send_after(?FORCE_UPFATE_TIME, self(), {force_update}),
    NewMaps1 = maps:put(?FORCE_UPDATE, Ref1, NewMaps),
    NewState = State#cross_rank_state{maps = NewMaps1},
    {noreply, NewState};

%% 强制重置分区同步状态,但保留游戏服的同步状态
do_handle_info({force_update}, State) ->
    #cross_rank_state{maps = Maps, srv_upload_status = SrvUploadMap, srv_zones = SrvZoneMap} = State,
    Ref = maps:get(?FORCE_UPDATE, Maps, undefined),
    util:cancel_timer(Ref),
    NewMaps1 = maps:put(?FORCE_UPDATE, undefined, Maps),
    %% 游戏服的上传状态先保留
    {ChangZones, NewUploadStatus} = update_all_zone_upload_status(SrvUploadMap),
    notify_zone_local_rank_update(ChangZones, get_cross_rank_type(), SrvZoneMap),
    % ?ERR("~p", [NewUploadStatus]),
    NewState = State#cross_rank_state{maps = NewMaps1, upload_status = NewUploadStatus},
    {noreply, NewState};

%% 周期性通知更新,清除服务器上传状态
do_handle_info({timer_update}, State) ->
    #cross_rank_state{maps = Maps, srv_zones = SrvZoneMap} = State,
    % ?ERR("timer update", []),
    notify_local_rank_upload(SrvZoneMap),
    OldInterRef = maps:get(?INTERVAL_UPDATE_REF, Maps, undefined),
    NewSrvUploadMap = reset_srv_upload_status(SrvZoneMap),
    Ref = erlang:send_after(?FORCE_UPDATE_PRE_TIME, self(), {force_update_preview}),
    NewInterRef = util:send_after(OldInterRef, ?INTERVAL_UPDATE, self(), {timer_update}),
    NewMaps  = maps:put(?FORCE_UPDATE_PRE, Ref, Maps),
    NewMaps1 = maps:put(?INTERVAL_UPDATE_REF, NewInterRef, NewMaps),
    RankKeys = erlang:get_keys(),
    lists:map(fun(Key) -> 
        case Key of
            {cross_rank_type, _RankKey} ->
                erlang:put(Key, []);
            _ -> skip
        end
    end, RankKeys),
    NewState = State#cross_rank_state{
        srv_upload_status = NewSrvUploadMap, maps = NewMaps1,
        upload_status = [], rank_limit = #{}
    },
    {noreply, NewState};

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

get_rank_list({ZoneId, RankType}) ->
    case erlang:get(?CROSS_RANK_TYPE_KEY({ZoneId, RankType})) of
        RankList = [_|_] -> ok;
        _ -> RankList = []
    end,
    RankList;

get_rank_list(RankTypeL) when is_list(RankTypeL) ->
    lists:foldl(fun({ZoneId, RankType}, Acc) -> 
        [{{ZoneId, RankType}, get_rank_list({ZoneId, RankType})}|Acc]
    end, [], RankTypeL).

insert_rank_list({ZoneId, RankType}, RankList) ->
    erlang:put(?CROSS_RANK_TYPE_KEY({ZoneId, RankType}), RankList).

del_rank_list({ZoneId, RankType}) ->
    erlang:erase(?CROSS_RANK_TYPE_KEY({ZoneId, RankType})).

%% 通知游戏服上报
notify_local_rank_upload(SrvZoneMap) ->
    RankTypeList = get_cross_rank_type(),
    spawn_link(fun() ->
        maps:map(fun(SrvId, _ZoneId) ->
            timer:sleep(urand:rand(50, 100)),
            clusters_center:apply_cast(SrvId, cross_rank_local, local_rank_upload, [RankTypeList])
        end, SrvZoneMap)
    end),
    ok.

%% 通知游戏服更新
notify_zone_local_rank_update(ChangZones, RankTypeList, SrvZoneMap) ->
    lists:map(fun(ZoneId) ->
        ZoneServIds = get_zone_serv(ZoneId, SrvZoneMap),
        % ?ERR("noticing servers ~p update", [ZoneServIds]),
        [clusters_center:apply_cast(SrvId, cross_rank_local, local_rank_update, [RankTypeList]) || SrvId <- ZoneServIds]
    end, ChangZones),
    ok.

%% 获取分区内的游戏服
get_zone_serv(ZoneId, SrvZoneMap) ->
    get_zone_serv_1(ZoneId, maps:to_list(SrvZoneMap), []).

get_zone_serv_1(_ZoneId, [], ServerIds) ->
    ServerIds;
get_zone_serv_1(ZoneId, [{SrvId, ZoneId}|Rest], AccServerIds) ->
    get_zone_serv_1(ZoneId, Rest, [SrvId|AccServerIds]);
get_zone_serv_1(ZoneId, [{_SrvId, _ZoneId1}|Rest], AccServerIds) ->
    get_zone_serv_1(ZoneId, Rest, AccServerIds);
get_zone_serv_1(ZoneId, [_|Rest], AccServerIds) ->
    get_zone_serv_1(ZoneId, Rest, AccServerIds).

%% 刷新排行逻辑
update_rank(ServerId, {RankType, LocRankL}, State) ->
    #cross_rank_state{rank_limit = RankLimit, srv_zones = SrvZoneMap} = State,
    case maps:get(ServerId, SrvZoneMap, undefined) of
        ZoneId when is_integer(ZoneId) ->
            ZoneRankL = get_rank_list({ZoneId, RankType}),
            {NewZoneRankL, NewRankLimit} = update_rank_logic(ZoneId, RankType, ZoneRankL, LocRankL, RankLimit),
            insert_rank_list({ZoneId, RankType}, NewZoneRankL),
            State#cross_rank_state{rank_limit = NewRankLimit};
        _ ->
            ?ERR("server [~w] have no zone", [ServerId]),
            State
    end.

% get_rank_limit(RankLimit, RankType) ->
%     #rank_config{rank_limit = Limit} = lib_common_rank:get_rank_config(RankType),
%     Temp = maps:get(RankType, RankLimit, Limit),
%     LastValue = max(Temp, Limit),
%     LastValue.


update_rank_logic(_ZoneId, _RankType, ZoneRankL = [], [], RankLimit) ->
    {ZoneRankL, RankLimit};
update_rank_logic(ZoneId, RankType, ZoneRankL, [], RankLimit) ->
    [#cross_common_rank_role{value = MinVal, rank_type = RankType}|_] = SortedRankL = lists:keysort(#cross_common_rank_role.value, ZoneRankL),
    SortedRankL1 = lists:reverse(SortedRankL),
    SortedRankL2 = make_sort_index(SortedRankL1, [], 1),
    NewRankLimit = maps:put({ZoneId, RankType}, MinVal, RankLimit),
    {SortedRankL2, NewRankLimit};
update_rank_logic(ZoneId, RankType, ZoneRankL, [RankRole|Rest], RankLimit) ->
    #cross_common_rank_role{role_key = Key, value = RankVal} = RankRole,
    #rank_config{rank_limit = Limit} = lib_common_rank:get_rank_config(RankType),
    if
        RankVal >= Limit ->
            NewZoneRankL = lists:keystore(Key, #cross_common_rank_role.role_key, ZoneRankL, RankRole),
            update_rank_logic(ZoneId, RankType, NewZoneRankL, Rest, RankLimit);
        true ->
            update_rank_logic(ZoneId, RankType, ZoneRankL, Rest, RankLimit)
    end.

%% 标记序号
make_sort_index([], Acc, _) -> Acc;
make_sort_index([R = #cross_common_rank_role{}|Rest], Acc, Index) ->
    if Index > 100 ->
            Acc;
        true ->
            make_sort_index(Rest, [R#cross_common_rank_role{rank = Index}|Acc], Index+1)
    end.

%% 下载top列表的头像数据
download_top_rec_pic(FromServId, RankType, RankTypeL) ->
    lists:map(fun(Rank) -> 
        case lists:keyfind(Rank, #cross_common_rank_role.rank, RankTypeL) of
            #cross_common_rank_role{role_id = RoleId} ->
                ServerId = mod_player_create:get_serid_by_id(RoleId),
                TarServId = clusters_center:find_now_server_id(ServerId),
                clusters_center:apply_cast(TarServId, ?MODULE, get_target_role_figure, [FromServId, RankType, Rank, RoleId]);
            _ ->
                skip
        end
    end, [1, 2, 3]).


%% 目标服执行
get_target_role_figure(FromServ, RankType, Rank, RoleId) ->
    Figure = lib_role:get_role_figure(RoleId),
    clusters_node:apply_cast(?MODULE, return_rec_pic, [FromServ, RankType, Rank, Figure]).

%% 跨服中心执行
return_rec_pic(FromServ, RankType, Rank, Figure) ->
    clusters_center:apply_cast(FromServ, ?MODULE, return_rec_pic_callback, [RankType, Rank, Figure]).

%% 源服执行
return_rec_pic_callback(RankType, Rank, Figure) ->
    cross_rank_local:download_top_rec_pic_callback(RankType, Rank, Figure).


%% 分区变动更新排行数据
recalc_zone_update_rank(_OldZoneId, _NewZoneId, [], RankLimit) ->
    RankLimit;
recalc_zone_update_rank(OldZoneId, NewZoneId, [RankType|Rest], RankLimit) ->
    OldZoneRankList = get_rank_list({OldZoneId, RankType}),
    NewZoneRankList = get_rank_list({NewZoneId, RankType}),
    NewZoneRankList1 = lists:append(OldZoneRankList, NewZoneRankList),
    del_rank_list({OldZoneId, RankType}),
    case lists:keysort(#cross_common_rank_role.value, NewZoneRankList1) of
        SortedRankL = [#cross_common_rank_role{value = MinVal}|_] ->
            SortedRankL1 = lists:reverse(SortedRankL),
            SortedRankL2 = make_sort_index(SortedRankL1, [], 1),
            insert_rank_list({NewZoneId, RankType}, SortedRankL2),
            NewRankLimit = maps:put({NewZoneId, RankType}, MinVal, RankLimit),
            recalc_zone_update_rank(OldZoneId, NewZoneId, Rest, NewRankLimit);
        _ ->
            insert_rank_list({NewZoneId, RankType}, []),
            NewRankLimit = maps:put({NewZoneId, RankType}, 0, RankLimit),
            recalc_zone_update_rank(OldZoneId, NewZoneId, Rest, NewRankLimit)
    end.


%% 重置游戏服的上报状态
reset_srv_upload_status(SrvZoneMap) ->
    maps:fold(fun(ServerId, ZoneId, TempMap) -> 
        ZoneSrvs = maps:get(ZoneId, TempMap, []),
        NewZoneSrvs = lists:keystore(ServerId, 1, ZoneSrvs, {ServerId, 0}),
        maps:put(ZoneId, NewZoneSrvs, TempMap)
    end, #{}, SrvZoneMap).

%% 分区更新时游戏服上传状态也更新
recalc_zone_srv_upload_status(ChangZones, SrvUploadMap, UploadStatus) ->
    {NewSrvUploadMap, NewUploadStatus} = lists:foldl(fun({ZoneId, OldZoneId}, {TempUploadMap, TempUploadStatus}) -> 
        OldZoneSrvs = maps:get(OldZoneId, TempUploadMap, []),
        ZoneSrvs    = maps:get(ZoneId, TempUploadMap, []),
        NewZoneSrvs = lists:append(OldZoneSrvs, ZoneSrvs),
        NewTempUploadMap    = maps:remove(OldZoneId, TempUploadMap),
        NewTempUploadMap1   = maps:put(ZoneId, NewZoneSrvs, NewTempUploadMap),
        NewTempUploadStatus = lists:keydelete(OldZoneId, 1, TempUploadStatus),
        {NewTempUploadMap1, NewTempUploadStatus}
    end, {SrvUploadMap, UploadStatus}, ChangZones),
    {NewSrvUploadMap, NewUploadStatus}.

%% 刷新游戏服的上报状态
update_upload_status(ZoneId, ServerId, RankType, SrvUploadMap, UploadStatus, SrvZoneMap) ->
    ZoneSrvs = maps:get(ZoneId, SrvUploadMap, []),
    NewZoneSrvs = lists:keystore(ServerId, 1, ZoneSrvs, {ServerId, 1}),
    NewUploadStatus = 
    case lists:all(fun({_ServerIdX, Status}) -> Status == 1 end, NewZoneSrvs) of
        true ->
            notify_zone_local_rank_update([ZoneId], [RankType], SrvZoneMap),
            lists:keystore(ZoneId, 1, UploadStatus, {ZoneId, 1});
        _ ->
            UploadStatus
    end,
    NewSrvUploadMap = maps:put(ZoneId, NewZoneSrvs, SrvUploadMap),
    {NewSrvUploadMap, NewUploadStatus}.

check_srv_upload_status(SrvUploadMap) ->
    RankTypeList = get_cross_rank_type(),
    maps:map(fun(_ZoneId, ZoneSrvs) -> 
        lists:map(fun({ServerId, Status}) -> 
            if Status == 0 ->
                    clusters_center:apply_cast(ServerId, cross_rank_local, local_rank_upload, [RankTypeList]);
                true ->
                    skip
            end
        end, ZoneSrvs)
    end, SrvUploadMap).

get_cross_rank_type() ->
    RankTypeList = [
        ?RANK_TYPE_COMBAT
        ,?RANK_TYPE_COMBAT_1
        ,?RANK_TYPE_COMBAT_2
        ,?RANK_TYPE_COMBAT_3
        ,?RANK_TYPE_COMBAT_4
        ,?RANK_TYPE_LV
        ,?RANK_TYPE_LV_1
        ,?RANK_TYPE_LV_2
        ,?RANK_TYPE_LV_3
        ,?RANK_TYPE_LV_4
    ],
    RankTypeList.

%% 强制更新上报状态,避免游戏服因某些未及时上报的游戏服的影响而拿不到数据
update_all_zone_upload_status(SrvUploadMap) ->
    {Zones, UploadStatus} =
    maps:fold(fun(ZoneId, _ZoneSrvs, {ChangZones, ZoneUploadStatus}) ->
        case lists:keyfind(ZoneId, 1, ZoneUploadStatus) of
            {_, 1} -> 
                {ChangZones, ZoneUploadStatus};
            _  -> 
                {[ZoneId|ChangZones], lists:keystore(ZoneId, 1, ZoneUploadStatus, {ZoneId, 1})}
        end
    end, {[], []}, SrvUploadMap),
    {Zones, UploadStatus}.

daily_log(Now, RoleMaps) ->
    maps:fold(fun({ZoneId, RankType}, ZoneRankL, Acc) ->
        case lists:member(RankType, ?RANK_TYPE_CAREER) of
            false ->  
                SortedRankL  = lists:keysort(#cross_common_rank_role.value, ZoneRankL),
                SortedRankL1 = lists:reverse(SortedRankL),
                SortedRankL2 = make_sort_index(SortedRankL1, [], 1),
                SortedRankL3 = lists:reverse(SortedRankL2),
                daily_log_do(SortedRankL3, Now, ZoneId, RankType, 1);
            _ -> skip
        end,
        Acc
    end, [], RoleMaps).

daily_log_do([], _, _, _, _) -> ok;
daily_log_do([_CrossRole|_Rest], _Now, _ZoneId, _RankType, Index) when Index > 50 ->
    ok;
daily_log_do([CrossRole|Rest], Now, ZoneId, RankType, Index) ->
    if Index rem 10 == 0 -> timer:sleep(300);
        true -> skip
    end,
    #cross_common_rank_role{
        role_id = RoleId, value = Val, name = Name, rank = Rank,
        server_num = ServerNum, server_id = ServerId
    } = CrossRole,
    lib_log_api:log_cross_rank([RoleId, Name, RankType, Rank, Val, Now, ServerNum, ServerId, ZoneId]),
    daily_log_do(Rest, Now, ZoneId, RankType, Index+1).