%% --------------------------------
%% module   :cross_rank_local.erl
%% desc     :跨服排行本地缓存数据(阶段性更新)
%% author   :hh
%% time     :20/10/23
%% --------------------------------

-module (cross_rank_local).

-behaviour (gen_server).

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

-export ([
    start_link/0
    ,get_zone_rank/1                %% 获取分区
    ,get_rank_role_figure/2         %% 查询人物外观
    ,local_rank_upload/1            %% 本服排行上报
    ,local_rank_update/1            %% 本服排行更新,重新去跨服中心取数据
    ]).

-export([
    download_zone_rank_callback/1
    ,download_top_rec_pic_callback/3
    ,center_get_rank_role_figure/4
    ,local_get_rank_figure/3
    ,return_rank_figure/3
    ,return_rank_figure_callback/2
    ]).

-include("common_rank.hrl").
-include("figure.hrl").
-include("def_fun.hrl").
-include("czone.hrl").
-include("common.hrl").
-include("errcode.hrl").

-define (EXPIRE_TIME, 3600).

-record (rank_local_state, {
    rank_list = [],     %% 排行数据
    figures = [],       %% 跨服外观形象
    sync_status = []    %% 同步状态数据 [RankType,...]
    }).

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

get_zone_rank({RoleId, Val, SecVal, RankType, StartIndex, Len}) ->
    gen_server:cast(?MODULE, {get_zone_rank, RoleId, Val, SecVal, RankType, StartIndex, Len}).

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

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

get_rank_role_figure(Sid, TarRole) ->
    gen_server:cast(?MODULE, {get_rank_role_figure, Sid, TarRole}).

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

download_top_rec_pic_callback(RankType, Rank, Figure) ->
    gen_server:cast(?MODULE, {download_top_rec_pic_callback, RankType, Rank, Figure}).

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

init(_) ->
    {ok, #rank_local_state{}}.


handle_call(Msg, From, State) ->
    try
        do_handle_call(Msg, From, State)
    catch ?EXCEPTION(_Class, _Error, StackTrace) ->
        io:format("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) ->
        io:format("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) ->
        io:format("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({get_zone_rank, RoleId, Val, SecVal, RankType, StartIndex, Len}, State) ->
    #rank_local_state{rank_list = RankList, figures = RoleFigures, sync_status = SyncStatus} = State,
    case lists:keyfind(RankType, 1, RankList) of
        {_, TypeRankList} -> ok;
        _ -> TypeRankList = []
    end,
    CzoneLocalInfo = czone_api:env_get_zone(),
    Sum = length(TypeRankList),
    IsSync = lists:member(RankType, SyncStatus),
    CombatLvRankTypeL = ?RANK_TYPE_ALL_COMBATS ++ ?RANK_TYPE_ALL_LVS,
    IsAccessRank = lists:member(RankType, CombatLvRankTypeL),
    % ?ERR("~p ~p", [CzoneLocalInfo, IsSync]),
    if 
        IsAccessRank =/= true ->
            lib_server_send:send_to_uid(RoleId, pt_221, 22108, [?ERRCODE(err211_rank_data_error), RankType, 0, 0, 0, Sum, [], []]);
        CzoneLocalInfo == undefined orelse CzoneLocalInfo#czone_svr_local.stage == 0 -> %% 不在分区或1区1服阶段
            {ok, BinData} = pt_221:write(22108, [?SUCCESS, RankType, 0, 0, 0, Sum, [], []]),
            lib_server_send:send_to_uid(RoleId, BinData);
        Sum == 0 andalso IsSync == false -> %% 同步未完成
            {ok, BinData} = pt_221:write(22108, [?ERRCODE(err211_not_finish), RankType, 0, 0, 0, Sum, [], []]),
            lib_server_send:send_to_uid(RoleId, BinData);
        StartIndex > Sum ->
            {ok, BinData} = pt_221:write(22108, [?SUCCESS, RankType, 0, 0, 0, Sum, [], []]),
            lib_server_send:send_to_uid(RoleId, BinData);
        true ->
            case StartIndex > Sum orelse StartIndex =< 0 orelse Len =< 0 of
                true ->
                    lib_server_send:send_to_uid(RoleId, pt_221, 22108, [?FAIL, RankType, 0, 0, 0, Sum, [], []]);
                _ ->
                    SubRankList = lists:sublist(TypeRankList, StartIndex, Len),
                    case lists:keyfind(RoleId, #cross_common_rank_role.role_id, TypeRankList) of
                        #cross_common_rank_role{rank = RoleRank, value = Value, second_value = SecValue} ->
                            ok;
                        _ ->
                            RoleRank = 0, Value = Val, SecValue = SecVal
                    end,
                    % ?ERR("~p ~p ~p", [RoleRank, Value, SecValue]),
                    TopRecPics = get_top_three_rec_pic(TypeRankList, RoleFigures),
                    PackRankL  = pack_rank_info(RoleId, SubRankList),
                    {ok, BinData} = pt_221:write(22108, [?SUCCESS, RankType, RoleRank, Value, SecValue, Sum, PackRankL, TopRecPics]),
                    lib_server_send:send_to_uid(RoleId, BinData)
            end
    end,
    {noreply, State};

do_handle_cast({download_zone_rank_callback, ZipRank}, State) ->
    #rank_local_state{rank_list = RankList, figures = Figures, sync_status = SyncStatus} = State,
    RankBin = zlib:unzip(ZipRank),
    {RankType, TypeRankList} = util:bitstring_to_term(RankBin),
    SortedRankList = lists:keysort(#cross_common_rank_role.rank, TypeRankList),
    NewRankList = lists:keystore(RankType, 1, RankList, {RankType, SortedRankList}),
    NewSyncStatus = ?IF(lists:member(RankType, SyncStatus) == true, SyncStatus, [RankType|SyncStatus]),
    RankSyncLen = length(NewSyncStatus),
    AllTypes = data_ranking:get_all_target_type(),
    Len = length(AllTypes),
    {NewFigures, NewRankList1} = 
    if RankSyncLen >= Len ->
            Figures1 = refresh_local_figure(Figures, NewRankList),
            {Figures1, NewRankList};
        true ->
            {Figures, NewRankList}
    end,

    NewState = State#rank_local_state{rank_list = NewRankList1, figures = NewFigures, sync_status = NewSyncStatus},
    {noreply, NewState};

do_handle_cast({download_top_rec_pic_callback, _RankType, _Rank, Figure}, State) ->
    #rank_local_state{figures = Figures} = State,
    ExpireTime = utime:unixtime() + ?EXPIRE_TIME,
    NewFigures = lists:keystore(Figure#figure.id, 1, Figures, {Figure#figure.id, Figure, ExpireTime}),
    NewState = State#rank_local_state{figures = NewFigures},
    {noreply, NewState};

do_handle_cast({get_rank_role_figure, Sid, TarRole}, State) ->
    #rank_local_state{figures = Figures} = State,
    ServerId = config:get_server_id(),
    TarServId = mod_player_create:get_real_serid_by_id(TarRole),
    if TarServId == ServerId ->
            Figure = lib_role:get_role_figure(TarRole),
            lib_server_send:send_to_sid(Sid, pt_221, 22109, [Figure]);
        true ->
            case lists:keyfind(TarRole, 1, Figures) of
                {TarRole, Figure = #figure{}, ExpireTime} ->
                    Now = utime:unixtime(),
                    if Now >= ExpireTime -> %% 过期了重新拿数据
                            clusters_node:apply_cast(?MODULE, center_get_rank_role_figure, [ServerId, Sid, TarRole, TarServId]);
                        true ->
                            lib_server_send:send_to_sid(Sid, pt_221, 22109, [Figure])
                    end;
                _ -> %% 去其他服查询
                    clusters_node:apply_cast(?MODULE, center_get_rank_role_figure, [ServerId, Sid, TarRole, TarServId])
            end
    end,
    {noreply, State};

do_handle_cast({get_rank_role_figure_callback, Figure}, State) ->
    #rank_local_state{figures = Figures} = State,
    ExpireTime = utime:unixtime() + ?EXPIRE_TIME,
    NewFigures = lists:keystore(Figure#figure.id, 1, Figures, {Figure#figure.id, Figure, ExpireTime}),
    NewState = State#rank_local_state{figures = NewFigures},
    {noreply, NewState};

do_handle_cast({local_rank_upload, RankTypeL}, State) ->
    mod_common_rank:local_rank_upload(RankTypeL),
    NewState = State#rank_local_state{sync_status = []},
    {noreply, NewState};


do_handle_cast({local_rank_update, RankTypeL}, State) ->
    ServId = config:get_server_id(),
    % ?ERR("local noticed update ~p", [ServId]),
    spawn(fun() ->
        timer:sleep(urand:rand(1000, 5000)),
        [clusters_node:apply_cast(cross_rank_mgr, download_zone_rank, [ServId, RankType]) || RankType <- RankTypeL]
    end),
    NewState = State#rank_local_state{rank_list = [], sync_status = []},

    {noreply, NewState};

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


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


pack_rank_info(_RoleId, RankList) ->
    PackRankL = lists:map(fun(CrossRankRole) ->
        #cross_common_rank_role{
            role_id = RoleIdX, value = ValueX, second_value = SecValueX, rank = RankX,
            vip = Vip, sup_vip = SupVip, name = Name, gender = Gender,
            server_num = ServerNum, server_id = ServerId} = CrossRankRole, 
        {RoleIdX, ValueX, SecValueX, RankX, Name, Vip, SupVip, Gender, ServerNum, ServerId}
    end, RankList),
    PackRankL.

get_top_three_rec_pic(RankList, Figures) ->
    RoleRanks =
    lists:map(fun(Rank) -> 
        case lists:keyfind(Rank, #cross_common_rank_role.rank, RankList) of
            #cross_common_rank_role{role_id = RoleId, server_num = ServerNum, server_id = ServerId} -> 
                {Rank, RoleId, ServerNum, ServerId};
            _ -> {Rank, 0, 0, 0}
        end
    end, [1, 2, 3]),
    lists:map(fun({RankX, RoleIdX, ServerNumX, ServerIdX}) -> 
        case lists:keyfind(RoleIdX, 1, Figures) of
            {_, Figure = #figure{}, _ExpireTime} -> {RankX, lib_role:figure_to_rec_picture(Figure), ServerNumX, ServerIdX};
            _  -> {RankX, lib_role:figure_to_rec_picture(#figure{}), ServerNumX, ServerIdX}
        end
    end, RoleRanks).

%% 跨服中心执行
center_get_rank_role_figure(FromServerId, Sid, TarRole, TarServId) ->
    TarServId1 = clusters_center:find_now_server_id(TarServId),
    clusters_center:apply_cast(TarServId1, ?MODULE, local_get_rank_figure, [FromServerId, Sid, TarRole]).

%% 目标服执行
local_get_rank_figure(FromServerId, Sid, TarRole) ->
    Figure = lib_role:get_role_figure(TarRole),
    clusters_node:apply_cast(?MODULE, return_rank_figure, [FromServerId, Sid, Figure]).

%% 跨服中心执行
return_rank_figure(FromServerId, Sid, Figure) ->
    clusters_center:apply_cast(FromServerId, ?MODULE, return_rank_figure_callback, [Sid, Figure]).

%% 源服执行
return_rank_figure_callback(Sid, Figure) ->
    lib_server_send:send_to_sid(Sid, pt_221, 22109, [Figure]),
    get_rank_role_figure_callback(Figure),
    ok.


%% 根据本次更新排行数据更新最新外观数据
refresh_local_figure(Figures, RankList) ->
    RankList1 = [TypeRankList || {_RankType, TypeRankList} <- RankList],
    RankList2 = lists:flatten(RankList1),
    NewFigures = lists:foldl(fun({RoleId, Figure, ExpireTime}, Acc) -> 
        case lists:keyfind(Figure#figure.id, #cross_common_rank_role.role_id, RankList2) of
            #cross_common_rank_role{} -> [{RoleId, Figure, ExpireTime}|Acc];
            _  -> Acc
        end
    end, [], Figures),
    NewFigures.