%%%-------------------------------------------------------------------
%%% @author liushl
%%% @doc
%%%     活动管理进程：本服/跨服
%%% 1、本服每天0点检测是否需要开启本服活动。
%%% 2、本服活动开启后记录当前轮次为1，此后依次递增
%%% 3、则轮次0表明尚未开启活动，则轮次1表明当前正在开启本服活动，轮次大于1表明正在开启跨服活动。
%%% 4、跨服每周一开启活动
%%% 5、本服活动管理根据自己的模式选择将数据上传至跨服进行跨服活动还是只进行本服模式
%%% 6、跨服活动结束，将16强数据发到各个服务器，每个服务器都保存一份。
%%% 7、本服轮次不大于1的服务器会忽略所有来自跨服中心的消息数据
%%% @end
%%%-------------------------------------------------------------------
-module(beauty_contest).
-author("liushl").

-include("beauty_contest.hrl").
-include("common.hrl").
-include("def_fun.hrl").
-include("server_kv.hrl").
-include("figure.hrl").

-behaviour(gen_server).

%% API
-export([start_link/0]).

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

-define(SERVER, ?MODULE).

-export([
    is_cluster_pattern/1                    %% 判断游戏服当前活动是本服模式还是开服模式
    %% 本服接口
    , zero_clock/0                          %% 零点消息
    , get_act_state/1                       %% 查看活动数据
    , get_my_info/1                         %% 玩家个人信息
    , get_sign_state/1                      %% 查看报名状态
    , sign_up/3                             %% 报名
    , get_vote_candidate/1                  %% 请求投票候选
    , vote/3                                %% 投票
    , get_rank_info/1                       %% 查看榜单
    , get_log_info/1                        %% 上期排名
    , update_contest_data/2                 %% 调整外观

    , check_sign_reward/0                   %% 检查报名奖励

    , update_role_info/3                    %% 玩家数据更新

    , after_pick_out/3                      %% 选出投票候选后，记录
    , record_log_in_server/2                %% 记录上期日志
    , notice_champion_in_server/3           %% 广播冠军传闻
    , check_sign_reward_in_server/2         %% 检查发送报名奖励
    , clean_sign_data_in_server/0           %% 清理报名数据
    , notify_recalc_zone/1
    %% 跨服接口
    , zero_clock/1                          %% 周一0点消息：开启活动
    , recalc_zone/1                         %% 跨服小分区合区：合并数据
    , handle_merge/4                        %% 合服处理
    , re_get_vote_candidate/2               %% 获得投票候选
    , vote_center/4                         %% 投票
    , get_rank_info_center/1                %% 获得榜单
    , update_role_info_center/3             %% 游戏服玩家数据更新

    , member_sync_to_center/1               %% 比赛开始游戏服玩家数据同步到跨服中心
]).

-export([
    init_act_stage/1                        %% 初始化比赛
    , handle/3                              %% 游戏服和跨服一些通用逻辑处理
    , simulate_stage/4                      %% 手动模拟活动流程
    , compare_vote_func/2                   %% 按票数排序
    , compare_fashion_func/2                %% 按收集数量排序
]).

-export([
    gm_begin/0,
    gm_next/0,
    apply_cast/1,
    apply_info/1,
    cast_local/1,
    cast_center/2,
    common_cast_local/1,
    common_cast_center/2
]).

%%%===================================================================
%%% API
%%%===================================================================
%% 判断游戏服当前活动是本服模式还是开服模式
is_cluster_pattern(State) ->
    State#beauty_contest.round > ?BC_LOCAL_PATTERN_ROUND.

%%%===================================================================
%%% @doc 本服接口
%%%===================================================================
%% 零点消息
zero_clock() ->
    cast_local(zero_clock).
%% 查看活动数据
get_act_state(Sid) ->
    cast_local({get_act_state, Sid}).
%% 玩家个人信息
get_my_info(RoleId) ->
    cast_local({get_my_info, RoleId}).
%% 查看报名状态
get_sign_state(RoleId) ->
    cast_local({get_sign_state, RoleId}).
%% 报名
sign_up(RoleId, Name, FashionPower) ->
    cast_local({sign_and_award, RoleId, Name, FashionPower}).
%% 请求投票候选
get_vote_candidate(RoleId) ->
    cast_local({get_vote_candidate, RoleId}).
%% 投票
vote(RoleId, VoteRole, DailyVote) ->
    cast_local({vote, RoleId, VoteRole, DailyVote}).
%% 查看榜单
get_rank_info(Sid) ->
    cast_local({get_rank_info, Sid}).
%% 上期排名
get_log_info(Sid) ->
    cast_local({get_log_info, Sid}).
%% 调整外观
update_contest_data(RoleId, Data) ->
    cast_local({update_contest_data, RoleId, Data}).

%% 检查报名奖励
check_sign_reward() ->
    cast_local({check_sign_reward, all}).

%% 玩家数据更新
update_role_info(RoleId, Key, Value) ->
    cast_local({update_role_info, RoleId, Key, Value}).

%% 选出投票候选后，到游戏服记录
after_pick_out(Node, RoleId, Picks) ->
    clusters_center:apply_cast(Node, {?MODULE, cast_local, [{after_pick_out, RoleId, Picks}]}).
%% 记录上期日志
record_log_in_server(ZoneId, ActLogs) ->
    czone:zone_apply_cast(ZoneId,    {?MODULE, cast_local, [{set_log, ActLogs, ?NODE_CENTER}]}).
%% 广播冠军传闻
notice_champion_in_server(ZoneId, ServerNum, Name) ->
    czone:zone_apply_cast(ZoneId,    {?MODULE, cast_local, [{notice_champion, ServerNum, Name}]}).
%% 检查发送报名奖励
check_sign_reward_in_server(ServerId, RoleId) ->
    clusters_center:apply_cast(ServerId, ?MODULE, cast_local, [{check_sign_reward, RoleId}]).
%% 清理报名数据
clean_sign_data_in_server() ->
    clusters_center:apply_to_all_node(?MODULE, cast_local, [clean_sign_data]).
%% 清理本地的投票缓存
notify_recalc_zone(ZoneId) ->
    czone:zone_apply_cast(ZoneId,    {?MODULE, cast_local, [notify_recalc_zone]}).
%% GM处理
gm_begin() ->
    Info = {gm, start},
    clusters_node:apply_cast(?MODULE, apply_info, [Info]).
gm_next() ->
    Info = {gm, switch},
    clusters_node:apply_cast(?MODULE, apply_info, [Info]).
%%%===================================================================
%%% @doc 跨服接口
%%%===================================================================
%% 周一0点消息：开启活动
zero_clock(1) ->
    apply_info(monday_zero_clock);
zero_clock(_) -> ok.

%% 跨服小分区合区：合并数据
recalc_zone(#{zone_map := ZoneMap}) ->
    [{OldZone, NewZone}] = maps:to_list(ZoneMap),
    apply_info({recalc_zone, OldZone, NewZone});
recalc_zone(_) -> ignore.

%% 合服处理
handle_merge(ZoneId, ServerId, ServerNum, ServerList) ->
    cast_center(ZoneId, {merger, ServerId, ServerNum, ServerList}).

%% 获得投票候选
re_get_vote_candidate(RoleId, DailyVote) ->
    common_cast_to_center({re_get_vote_candidate, node(), RoleId, DailyVote}).
%% 投票
vote_center(RoleId, VoteRole, Other, AddVote) ->
    common_cast_to_center({vote, node(), RoleId, VoteRole, Other, AddVote}).
%% 获得榜单
get_rank_info_center(Sid) ->
    common_cast_to_center({get_rank_info, Sid}).
%% 游戏服玩家数据更新
update_role_info_center(RoleId, Key, Value) ->
    common_cast_to_center({update_role_info, RoleId, Key, Value}).

%% 比赛开始游戏服玩家数据同步到跨服中心
member_sync_to_center(UploadRoles) ->
    cast_to_center({sync_member, UploadRoles}).

%%%===================================================================
%%% common callbacks
%%%===================================================================
%% 本服通用消息处理
common_cast_local(Msg) ->
    apply_cast({common, 0, Msg}).

%% 通用跨服消息处理
common_cast_to_center(Msg) ->
    ZoneId = czone_api:env_get_zone_id(),
    clusters_node:apply_cast(?MODULE, apply_cast, [{common, ZoneId, Msg}]).

%% 通用跨服消息处理
common_cast_center(ZoneId, Msg) ->
    apply_cast({common, ZoneId, Msg}).

%% 本服专用消息处理
cast_local(Msg) ->
    apply_cast({local, 0, Msg}).

%% 跨服专用消息处理
cast_to_center(Msg) ->
    ZoneId = czone_api:env_get_zone_id(),
    clusters_node:apply_cast(?MODULE, apply_cast, [{center, ZoneId, Msg}]).

%% 通用跨服消息处理
cast_center(ZoneId, Msg) ->
    apply_cast({center, ZoneId, Msg}).

apply_cast(Msg) ->
    gen_server:cast(?MODULE, Msg).

apply_info(Msg) ->
    ?MODULE ! Msg.
%%--------------------------------------------------------------------
%% @doc
%% Starts the server
%%
%% @end
%%--------------------------------------------------------------------
-spec(start_link() ->
    {ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
start_link() ->
    gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Initializes the server
%%
%% @spec init(Args) -> {ok, State} |
%%                     {ok, State, Timeout} |
%%                     ignore |
%%                     {stop, Reason}
%% @end
%%--------------------------------------------------------------------
-spec(init(Args :: term()) ->
    {ok, State :: #beauty_contest{}} | {ok, State :: #beauty_contest{}, timeout() | hibernate} |
    {stop, Reason :: term()} | ignore).
init([]) ->
    erlang:process_flag(trap_exit, true),
    ClsType = clusters_api:get_cluster_type(),
    self() ! init,
    %% 本服节点启动的时候，如果活动未开启，判断一次活动，较危险
    %% ClsType =:= ?NODE_GAME andalso NewState#beauty_contest.round =:= 0 andalso zero_clock(),
    {ok, #beauty_contest{cls_type = ClsType}}.

%%%===================================================================
%%% Logic 
%%%===================================================================
do_handle_call(_Request, _From, State) ->
    io:format("time:[~p], module:~p: do_handle_call unexpect message:~p~n", [erlang:localtime(), ?MODULE, _Request]),
    {reply, ok, State}.

%% 数据通用处理
do_handle_cast({common, ZoneId, Msg}, State) ->
    #beauty_contest{
        act_data = ActData
    } = State,
    ZoneInfo    = maps:get(ZoneId, ActData, #beauty_contest_act{zone_id = ZoneId}),
    {State1, NewZoneInfo} = handle(Msg, State, ZoneInfo),
    NewState    = State1#beauty_contest{act_data = maps:put(ZoneId, NewZoneInfo, ActData)},
    {noreply, NewState};

%% 本服逻辑
do_handle_cast({local, ZoneId, Msg}, #beauty_contest{cls_type = ?NODE_GAME} = State) ->
    #beauty_contest{
        act_data = ActData
    } = State,
    ZoneInfo    = maps:get(ZoneId, ActData, #beauty_contest_act{zone_id = ZoneId}),
    {State1, NewZoneInfo}
        = beauty_contest_local:handle(Msg, State, ZoneInfo),
    NewState    = State1#beauty_contest{act_data = maps:put(ZoneId, NewZoneInfo, ActData)},
    {noreply, NewState};

%% 跨服逻辑
do_handle_cast({center, ZoneId, Msg}, #beauty_contest{cls_type = ?NODE_CENTER} = State) ->
    #beauty_contest{
        act_data = ActData
    } = State,
    ZoneInfo    = maps:get(ZoneId, ActData, #beauty_contest_act{zone_id = ZoneId}),
    {State1, NewZoneInfo}
        = beauty_contest_center:handle(Msg, State, ZoneInfo),
    NewState    = State1#beauty_contest{act_data = maps:put(ZoneId, NewZoneInfo, ActData)},
    {noreply, NewState};
do_handle_cast(_Request, State) ->
    io:format("time:[~p], module:~p: handle_cast unexpect message:~p~n", [erlang:localtime(), ?MODULE, _Request]),
    {noreply, State}.

%% 初始化
do_handle_info(init, #beauty_contest{cls_type = ?NODE_GAME} = State) ->
    State1 = beauty_contest_local:init_local_act(State),
    {noreply, State1};
do_handle_info(init, #beauty_contest{cls_type = ?NODE_CENTER} = State) ->
    State1 = beauty_contest_center:init_center_act(State),
    {noreply, State1};

%% 跨服中心每周一0点开启活动，所有举办过本服活动的服务器都可以参加跨服活动
do_handle_info(monday_zero_clock, #beauty_contest{cls_type = ?NODE_CENTER} = State) ->
    State1 = init_act_stage(State),
    {noreply, State1};
%%%-------------------------------------------------------------------
%% 活动阶段切换
%% 报名结束进入海选赛
%% 海选赛结束，决出16强，并进行分组
%%  16强赛结束进入8强赛
%%   8强赛结束进入4强赛
%%   4强赛结束进入总决赛
%%  总决赛结束角逐出总冠军
%%%-------------------------------------------------------------------
%% 报名结束，游戏服加载参赛玩家
do_handle_info(switch_stage, #beauty_contest{stage = ?BC_STAGE_SIGN} = State) ->
    StateLoad =
        case State#beauty_contest.cls_type of
            ?NODE_GAME ->
                {noreply, NewState} = do_handle_cast({local, 0, {load_member, false}}, State),
                NewState;
            ?NODE_CENTER ->
                State
        end,
    %% 10秒钟后，判断参赛人员是否已经报名。如果未报名，会给玩家发一份报名奖励
    erlang:send_after(10000, self(), check_sign_reward),
    %% 进入下一阶段
    StateNext = next_stage(StateLoad),
    {noreply, StateNext};
%% 其他阶段
%% 游戏服，如果当前是跨服模式，那就仅仅切换阶段即可
do_handle_info(switch_stage, #beauty_contest{cls_type = ?NODE_GAME} = State) ->
    StateNext =
        case is_cluster_pattern(State) of
            true ->
                next_stage(State);
            false ->
                switch_stage(State)
        end,
    {noreply, StateNext};
%% 跨服中心，走正常活动流程
do_handle_info(switch_stage, #beauty_contest{cls_type = ?NODE_CENTER} = State) ->
    StateNext = switch_stage(State),
    {noreply, StateNext};

%% 判断参赛人员是否已经报名。如果未报名，会给玩家发一份报名奖励
do_handle_info(check_sign_reward, #beauty_contest{act_data = ActData, cls_type = ?NODE_CENTER} = State) ->
    [
        [
            check_sign_reward_in_server(ServerId, RoleId)
            || #beauty_contest_role{server_id = ServerId, role_id = RoleId} <- ZoneData#beauty_contest_act.members
        ]
        || {_, ZoneData} <- maps:to_list(ActData)
    ],
    %% 通知所有的服务器清理报名数据
    spawn(
        fun() ->
            timer:sleep(5000),
            clean_sign_data_in_server()
        end
    ),
    {noreply, State};
do_handle_info(check_sign_reward, #beauty_contest{cls_type = ?NODE_GAME} = State) ->
    check_sign_reward(),
    {noreply, State};

%% 自动保存
do_handle_info(auto_save, State) ->
    State1 = State#beauty_contest{ref_save = set_timer_auto_save()},
    {NewAData, AllUpdateList} =
        maps:fold(
            fun(ZoneId, ZoneInfo, {MapsTemp, UpdateTemp}) ->
                #beauty_contest_act{members = Members} = ZoneInfo,
                {UpdateList, NewMembers} =
                    lists:foldl(
                        fun(Role, {UpdateListTemp, AllTemp}) ->
                            case Role#beauty_contest_role.update of
                                true  ->
                                    {
                                        [Role|UpdateListTemp],
                                        [Role#beauty_contest_role{update = false}|AllTemp]
                                    };
                                false ->
                                    {UpdateListTemp, [Role|AllTemp]}
                            end
                        end, {[], []}, Members
                    ),
                {
                    MapsTemp#{ZoneId => ZoneInfo#beauty_contest_act{members = NewMembers}},
                        UpdateList ++ UpdateTemp
                }
            end, {#{}, []}, State#beauty_contest.act_data
        ),
    case State#beauty_contest.cls_type of
        ?NODE_GAME ->
            beauty_contest_local:db_save_act_role_batch(AllUpdateList);
        ?NODE_CENTER ->
            beauty_contest_center:db_save_act_role_batch(AllUpdateList)
    end,
    {noreply, State1#beauty_contest{act_data = NewAData}};

%% 跨服分区合并
do_handle_info({recalc_zone, OldZone, NewZone}, State) ->
    case State#beauty_contest.stage =:= ?BC_STAGE_IDLE
        orelse State#beauty_contest.stage =:= ?BC_STAGE_SIGN of
        true ->
            {noreply, State};
        false ->
            %% 比赛阶段，合并两个分区的数据
            NewState = beauty_contest_center:merge_zone_data(State, OldZone, NewZone),
            %% 然后通知各个服务器，清理本地的投票缓存
            notify_recalc_zone(NewZone),
            {noreply, NewState}
    end;

do_handle_info({gm, start}, State) ->
    State#beauty_contest.cls_type =:= ?NODE_CENTER andalso
        clusters_center:apply_to_all_node(?MODULE, apply_info, [{gm, start}]),
    case State#beauty_contest.stage == ?BC_STAGE_IDLE of
        true  ->
            NextRound = State#beauty_contest.round + 1,
            State0 = State#beauty_contest{round = NextRound},
            State1 = calc_act_stage(State0, 1),
            State2 = set_stage_timer(State1),
            case State#beauty_contest.cls_type =:= ?NODE_GAME of
                true  ->
                    {ok, BinData} = pt_110:write(11020, ["GM 开启靓丽大赛~"]),
                    lib_server_send:send_to_all(BinData),
                    beauty_contest_local:record_new_round(NextRound),
                    beauty_contest_local:broadcast_act_stage(State2);
                false ->
                    ok
            end,
            {noreply, State2};
        false ->
            case State#beauty_contest.cls_type =:= ?NODE_GAME of
                true  ->
                    {ok, BinData} = pt_110:write(11020, ["GM 靓丽大赛已经开启~"]),
                    lib_server_send:send_to_all(BinData);
                false ->
                    ok
            end,
            {noreply, State}
    end;
do_handle_info({gm, switch}, State) ->
    State#beauty_contest.cls_type =:= ?NODE_CENTER andalso
        clusters_center:apply_to_all_node(?MODULE, apply_info, [{gm, switch}]),
    case State#beauty_contest.stage > ?BC_STAGE_IDLE of
        true  ->
            self() ! switch_stage,
            case State#beauty_contest.cls_type =:= ?NODE_GAME of
                true  ->
                    Msg =
                    case State#beauty_contest.stage of
                        ?BC_STAGE_SIGN ->
                            "GM 靓丽大赛进入海选阶段~";
                        ?BC_STAGE_PRE ->
                            "GM 靓丽大赛进入16强~";
                        ?BC_STAGE_TOP_16 ->
                            "GM 靓丽大赛进入8强~";
                        ?BC_STAGE_TOP_8 ->
                            "GM 靓丽大赛进入4强~";
                        ?BC_STAGE_TOP_4 ->
                            "GM 靓丽大赛进入决赛~";
                        ?BC_STAGE_TOP_2 ->
                            "GM 靓丽大赛完成结算~"
                    end,
                    {ok, BinData} = pt_110:write(11020, [Msg]),
                    lib_server_send:send_to_all(BinData);
                false ->
                    ok
            end,
            {noreply, State};
        false ->
            case State#beauty_contest.cls_type =:= ?NODE_GAME of
                true  ->
                    {ok, BinData} = pt_110:write(11020, ["GM 靓丽大赛还未开启~"]),
                    lib_server_send:send_to_all(BinData);
                false ->
                    ok
            end,
            {noreply, State}
    end;

do_handle_info(_Info, State) ->
    io:format("time:[~p], module:~p: handle_info unexpect message:~p~n", [erlang:localtime(), ?MODULE, _Info]),
    {noreply, State}.

switch_stage(State) ->
    %% 先处理每一个分区的活动数据
    StateEnd = lists:foldl(
        fun(ZoneId, Temp) ->
            {noreply, NewTemp} = do_handle_cast({common, ZoneId, stage_end}, Temp),
            NewTemp
        end, State, maps:keys(State#beauty_contest.act_data)
    ),
    %% 然后切换到下一阶段
    StateNext = next_stage(StateEnd),
    StateNext.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling call messages
%%
%% @end
%%--------------------------------------------------------------------
-spec(handle_call(Request :: term(), From :: {pid(), Tag :: term()},
    State :: #beauty_contest{}) ->
    {reply, Reply :: term(), NewState :: #beauty_contest{}} |
    {reply, Reply :: term(), NewState :: #beauty_contest{}, timeout() | hibernate} |
    {noreply, NewState :: #beauty_contest{}} |
    {noreply, NewState :: #beauty_contest{}, timeout() | hibernate} |
    {stop, Reason :: term(), Reply :: term(), NewState :: #beauty_contest{}} |
    {stop, Reason :: term(), NewState :: #beauty_contest{}}).
handle_call(Msg, From, State) ->
    try
        do_handle_call(Msg, From, State)
    catch _:_Error:Trace ->
        io:format("[~p]~nhandle_call Msg:~p~n Error:~p~n ~p~n", [erlang:localtime(), Msg, _Error, Trace]),
        {reply, error, State}
    end.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling cast messages
%%
%% @end
%%--------------------------------------------------------------------
-spec(handle_cast(Request :: term(), State :: #beauty_contest{}) ->
    {noreply, NewState :: #beauty_contest{}} |
    {noreply, NewState :: #beauty_contest{}, timeout() | hibernate} |
    {stop, Reason :: term(), NewState :: #beauty_contest{}}).
handle_cast(Msg, State) ->
    try
        do_handle_cast(Msg, State)
    catch _:_Error:Trace ->
        io:format("[~p]~nhandle_cast Msg:~p~n Error:~p~n ~p~n", [erlang:localtime(), Msg, _Error, Trace]),
        {noreply, State}
    end.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling all non call/cast messages
%%
%% @spec handle_info(Info, State) -> {noreply, State} |
%%                                   {noreply, State, Timeout} |
%%                                   {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
-spec(handle_info(Info :: timeout() | term(), State :: #beauty_contest{}) ->
    {noreply, NewState :: #beauty_contest{}} |
    {noreply, NewState :: #beauty_contest{}, timeout() | hibernate} |
    {stop, Reason :: term(), NewState :: #beauty_contest{}}).
handle_info(Msg, State) ->
    try
        do_handle_info(Msg, State)
    catch _:_Error:Trace ->
        io:format("[~p]~nhandle_info Msg:~p~n Error:~p~n ~p~n", [erlang:localtime(), Msg, _Error, Trace]),
        {noreply, State}
    end.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any
%% necessary cleaning up. When it returns, the gen_server terminates
%% with Reason. The return value is ignored.
%%
%% @spec terminate(Reason, State) -> void()
%% @end
%%--------------------------------------------------------------------
-spec(terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()),
    State :: #beauty_contest{}) ->
    term()).
terminate(_Reason, State) ->
    do_handle_info(auto_save, State),
    ok.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Convert process State when code is changed
%%
%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
%% @end
%%--------------------------------------------------------------------
-spec(code_change(OldVsn :: term() | {down, term()}, State :: #beauty_contest{},
    Extra :: term()) ->
    {ok, NewState :: #beauty_contest{}} | {error, Reason :: term()}).
code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

%%%===================================================================
%%% @doc
%%%     比赛流程计算
%%% @end
%%%===================================================================
%% 初始化比赛
init_act_stage(State) ->
    State1 = calc_act_stage(State),
    State2 = set_stage_timer(State1),
    State2.

%% 计算当前活动阶段
calc_act_stage(State) ->
    calc_act_stage(State, utime:day_of_week()).
calc_act_stage(State, Week) ->
    Date      = erlang:date(),
    %% 活动开启时间是这一周的周一0点
    BeginDay  = utime:get_before_day(Date, (Week - 1), 0),
    BeginTime = utime:unixtime({BeginDay, ?BC_BEGIN_TIME}),
    %% 活动结束时间是周日的23：55：0
    EndDay    = utime:get_before_day(Date, 7 - Week, 1),
    EndTime   = utime:unixtime({EndDay, ?BC_END_TIME}),
    %% 因为有的阶段不止有两天，所以不能简单复用next_stage的代码
    %% 根据星期获得当前活动阶段和阶段开始的星期
    {Stage, StageBeginWeek}
        = ?BC_STAGE_BY_WEEK(Week),
    StageDays      = ?BC_STAGE_DAY(Stage),
    %% 阶段开始时间：比如今天是周三，那么周二和周三开启预选赛，周二0点开始，周四0点结束
    StageBeginDay  = utime:get_before_day(Date, (Week - StageBeginWeek), 0),
    StageBeginTime = utime:unixtime({StageBeginDay, ?BC_BEGIN_TIME}),
    %% 根据阶段持续时间计算阶段结束时间
    %% 根据阶段开始天+持续天得出阶段结束天
    StageEndDay    = utime:get_before_day(StageBeginDay, StageDays, 1),
    case Stage =:= ?BC_STAGE_TOP_2 of
        true  ->
            StageEnd = EndTime;
        false ->
            StageEnd = utime:unixtime({StageEndDay, {0, 0, 0}})
    end,
    State#beauty_contest{
        begin_time          = BeginTime
        , end_time          = EndTime
        , stage             = Stage
        , stage_begin       = StageBeginTime
        , stage_end         = StageEnd
    }.

%% 设置下一阶段定时器
set_stage_timer(#beauty_contest{stage = ?BC_STAGE_IDLE} = State) -> State;
set_stage_timer(State) ->
    #beauty_contest{
        cls_type    = ClsType,
        stage       = Stage,
        stage_end   = StageEnd
    } = State,
    util:cancel_timer(State#beauty_contest.ref_stage),
    util:cancel_timer(State#beauty_contest.ref_save),
    Now = utime:unixtime(),
    Set =
    case ClsType of
        ?NODE_GAME ->
            (not is_cluster_pattern(State)) andalso Stage >= ?BC_STAGE_PRE;
        ?NODE_CENTER ->
            Stage >= ?BC_STAGE_PRE
    end,
    State#beauty_contest{
        ref_stage     = erlang:send_after(max(5, StageEnd - Now) * 1000, self(), switch_stage),
        ref_save      = ?IF(Set, set_timer_auto_save(), undefined)
    }.

%% 开启自动保存定时器
set_timer_auto_save() ->
    erlang:send_after(?BC_SAVE_TIME * 1000, self(), auto_save).

%%%-------------------------------------------------------------------
%%% @doc
%%%     游戏服和跨服一些通用逻辑处理
%%% @end
%%%-------------------------------------------------------------------
%% 计算投票候选
handle({re_get_vote_candidate, Node, RoleId, DailyVote}, State, ActData) ->
    #beauty_contest{
        cls_type = ClsType,
        stage = Stage
    } = State,
    StageMembers = [One || One <- ActData#beauty_contest_act.members, One#beauty_contest_role.stage =:= Stage],
    case pick_vote_members(Stage, StageMembers, DailyVote) of
        [_, _] = Picks ->
            case ClsType of
                ?NODE_GAME ->
                    beauty_contest_local:handle({after_pick_out, RoleId, Picks}, State, ActData);
                ?NODE_CENTER ->
                    beauty_contest:after_pick_out(Node, RoleId, Picks),
                    {State, ActData}
            end;
        _ ->
            {State, ActData}
    end;
%% 进行投票
handle({vote, Node, RoleId, VoteRole, Other, AddVote}, State, ActData) ->
    #beauty_contest{
        cls_type = ClsType,
        stage = Stage
    } = State,
    #beauty_contest_act{members = Members} = ActData,
    case lists:keyfind(VoteRole, #beauty_contest_role.role_id, Members) of
        #beauty_contest_role{stage = Stage, picture = #rec_picture{name = VoteName}} = VoteRoleInfo ->
            case lists:keyfind(Other, #beauty_contest_role.role_id, Members) of
                #beauty_contest_role{stage = Stage, picture = #rec_picture{name = OtherName}} = OtherRoleInfo ->
                    %% 双方挑选次数+1
                    NewVoteRoleInfo = VoteRoleInfo#beauty_contest_role{
                        vote = VoteRoleInfo#beauty_contest_role.vote + AddVote,
                        pick_counts = VoteRoleInfo#beauty_contest_role.pick_counts + 1,
                        update = true,
                        time = utime:unixtime()
                    },
                    NewOtherRoleInfo = OtherRoleInfo#beauty_contest_role{
                        pick_counts = OtherRoleInfo#beauty_contest_role.pick_counts + 1,
                        update = true
                    },
                    Members1 = lists:keyreplace(VoteRole, #beauty_contest_role.role_id, Members, NewVoteRoleInfo),
                    Members2 = lists:keyreplace(Other, #beauty_contest_role.role_id, Members1, NewOtherRoleInfo),
                    NewActData = ActData#beauty_contest_act{members = Members2},
                    
                    VoteRoleVote = NewVoteRoleInfo#beauty_contest_role.vote,
                    OtherVote = NewOtherRoleInfo#beauty_contest_role.vote,
                    IsExtraAward =
                        case VoteRoleVote >  OtherVote of
                            true  -> 1;
                            false -> 0
                        end,
                    %% 投票成功，给前端推一次新的数据
                    %Bin = beauty_contest_api:pack_vote_candidate([NewVoteRoleInfo, NewOtherRoleInfo]),
                    Info =
                        [
                            1,
                            VoteName,
                            IsExtraAward,
                            [
                                {VoteRole, VoteRoleVote},
                                {Other, OtherVote}
                            ]
                        ],
                    {ok, Bin2} = pt_416:write(41607, Info),
                    lib_server_send:send_to_uid(Node, RoleId, Bin2),
                    %% 投票完成，到玩家进程记录日志
                    ZoneStage = case ClsType of
                                    ?NODE_GAME -> 0;
                                    ?NODE_CENTER -> czone:get_zone_stage(ActData#beauty_contest_act.zone_id)
                                end,
                    Message = {VoteRole, VoteName, IsExtraAward, AddVote, <<VoteName/binary, $,, OtherName/binary>>, ZoneStage, Stage, VoteRoleVote},
                    beauty_contest_api:after_vote(Node, RoleId, Message),
                    
                    {State, NewActData};
                _ ->
                    {State, ActData}
            end;
        _ ->
            {State, ActData}
    end;

%% 海选赛结束，决出16强，并进行分组
%%  16强赛结束进入8强赛
%%   8强赛结束进入4强赛
%%   4强赛结束进入总决赛
%%  总决赛结束角逐出总冠军
handle(stage_end, #beauty_contest{stage =  Stage, cls_type = ClsType} = State, ActData)
    when Stage =:= ?BC_STAGE_PRE
    orelse Stage =:= ?BC_STAGE_TOP_16
    orelse Stage =:= ?BC_STAGE_TOP_8
    orelse Stage =:= ?BC_STAGE_TOP_4
    orelse Stage =:= ?BC_STAGE_TOP_2 ->
    ActData1 = deal_stage_members(ClsType, Stage, ActData),
    %% 决赛结束，将16强进行记录
    case Stage =:= ?BC_STAGE_TOP_2 of
        true ->
            ActLogs =
                [
                    One || One <- ActData1#beauty_contest_act.members, One#beauty_contest_role.stage >= ?BC_STAGE_TOP_16
                ],
            case ClsType of
                ?NODE_GAME ->
                    beauty_contest_local:handle({set_log, ActLogs, ClsType}, State, ActData1);
                ?NODE_CENTER ->
                    %% 跨服模式，将日志记录到各个游戏服去
                    beauty_contest:record_log_in_server(ActData#beauty_contest_act.zone_id, ActLogs),
                    {State, ActData1}
            end;
        false ->
            {State, ActData1}
    end;

%% 查看榜单
handle({get_rank_info, Sid}, State, ActData) ->
    case State#beauty_contest.stage >= ?BC_STAGE_TOP_16 of
        true  ->
            Info =
                [
                    {ServerNum, RecPicture, CData, RoleStage, OriginRank}
                    ||#beauty_contest_role{
                        stage         = RoleStage
                        , origin_rank   = OriginRank
                        , server_num    = ServerNum
                        , picture       = RecPicture
                        , contest_data  = CData
                    } <- ActData#beauty_contest_act.members, RoleStage >= ?BC_STAGE_TOP_16
                ],
            {ok, Bin} = pt_416:write(41605, [Info]),
            lib_server_send:send_to_sid(Sid, Bin);
        false ->
            ignore
    end,
    {State, ActData};

%% 玩家数据更新
handle({update_role_info, RoleId, Key, Value}, State, ActData) ->
    #beauty_contest_act{members = Members} = ActData,
    case lists:keyfind(RoleId, #beauty_contest_role.role_id, Members) of
        #beauty_contest_role{picture = Picture} = RoleInfo ->
            NewRoleInfo =
                case Key of
                    name ->
                        NewPicture = Picture#rec_picture{name = Value},
                        RoleInfo#beauty_contest_role{picture = NewPicture, update = true};
                    vip ->
                        NewPicture = Picture#rec_picture{vip = Value},
                        RoleInfo#beauty_contest_role{picture = NewPicture, update = true};
                    sup_vip ->
                        NewPicture = Picture#rec_picture{sup_vip = Value},
                        RoleInfo#beauty_contest_role{picture = NewPicture, update = true};
                    turn ->
                        NewPicture = Picture#rec_picture{turn = Value},
                        RoleInfo#beauty_contest_role{picture = NewPicture, update = true};
                    contest_data ->
                        RoleInfo#beauty_contest_role{contest_data = Value, update = true};
                    _ ->
                        RoleInfo
                end,
            Members1 = lists:keyreplace(RoleId, #beauty_contest_role.role_id, Members, NewRoleInfo),
            NewActData = ActData#beauty_contest_act{members = Members1},
            {State, NewActData};
        _ ->
            {State, ActData}
    end;

handle(_Msg, State, ActData) ->
    {State, ActData}.

%%%-------------------------------------------------------------------
%%% @doc
%%%     活动流程，进入下一阶段
%%% @end
%%%-------------------------------------------------------------------
%% 决赛结束-则活动结束
next_stage(#beauty_contest{stage = ?BC_STAGE_TOP_2} = State) ->
    %% 取消所有定时器
    util:cancel_timer(State#beauty_contest.ref_stage),
    util:cancel_timer(State#beauty_contest.ref_save),
    case State#beauty_contest.cls_type of
        ?NODE_GAME ->
            beauty_contest_local:end_local_act(State);
        ?NODE_CENTER ->
            beauty_contest_center:end_center_act(State)
    end;
%% 其他阶段结束，则进入下一阶段
next_stage(#beauty_contest{stage = Stage} = State)
    when Stage > ?BC_STAGE_IDLE ->
    NextStage  = Stage + 1,
    Date       = erlang:date(),
    ActDays    = ?BC_STAGE_DAY(NextStage),
    %% 今天0点就是阶段开始时间
    StageBegin = utime:unixdate(),
    %% 如果是决赛，则是23：55：0结束，其他阶段就0点结算
    StageEnd   = case NextStage =:= ?BC_STAGE_TOP_2 of
                     true  ->
                         utime:unixtime({utime:get_before_day(Date, ActDays - 1, 1), ?BC_END_TIME});
                     false ->
                         utime:unixtime({utime:get_before_day(Date, ActDays, 1), {0, 0, 0 }})
                 end,
    State1     = set_stage_timer(State#beauty_contest{
        stage       = NextStage,
        stage_begin = StageBegin,
        stage_end   = StageEnd,
        vote_candidates = #{}
    }),
    %% 同步活动阶段
    beauty_contest_local:broadcast_act_stage(State1),
    State1;
next_stage(State) -> State.

%%%-------------------------------------------------------------------
%%% @doc
%%%    手动模拟活动流程
%%% @end
%%%-------------------------------------------------------------------
%% 一直结算到当前阶段为止
simulate_stage(_ClsType, NowStage, ActData, NowStage) -> ActData;
simulate_stage(ClsType, NowStage, ActData, Stage) ->
    NewActData = deal_stage_members(ClsType, Stage, ActData),
    simulate_stage(ClsType, NowStage, NewActData, Stage + 1).

%%%-------------------------------------------------------------------
%%% @doc
%%%    晋级玩家处理
%%% @end
%%%-------------------------------------------------------------------
deal_stage_members(ClsType, Stage, ActData) ->
    #beauty_contest_act{
        zone_id = ZoneId,
        members = Members
    } = ActData,
    NextStage = Stage + 1,
    %% 晋级的人数
    CutOut = ?BC_STAGE_ROLES(Stage),
    %% 淘汰玩家排名基数
    BaseRank = CutOut + 1,
    %% 拿出当前轮次参与的玩家
    {StageMembers, Others} =
        lists:partition(
            fun(#beauty_contest_role{stage = RoleStage}) -> RoleStage =:= Stage end,
            Members
        ),
    %% 挑选次数置空
    ResetMembers =
        [
            One#beauty_contest_role{pick_counts = 0, update = true} || One <- StageMembers
        ],
    %% 分成两部分，晋级的玩家和淘汰的玩家
    {RiseMembers, OutMembers} =
        case Stage =:= ?BC_STAGE_PRE of
            true ->
                %% 晋级16强的时候，排名靠前的晋级
                SortMembers = sort_role(ResetMembers),
                partition_rise_16_and_out(SortMembers, CutOut, [], NextStage, 1);
            false ->
                %% 其余晋级的判断，根据分组，组内票数高的晋级
                GroupMask = round(math:pow(10, Stage - ?BC_MASK)),
                partition_rise_and_out_common(ResetMembers, [], [], NextStage, GroupMask)
        end,
    %% 淘汰的玩家设置排名
    Max = ?BC_RANK_LENGTH,
    RankOutMembers = set_rank(BaseRank, OutMembers, Max, []),
    %% 淘汰日志
    log_beauty_contest_rank(ClsType, ZoneId, RankOutMembers),
    %% 冠军设置第一名
    RiseRankMembers =
    case Stage =:= ?BC_STAGE_TOP_2 of
        true  ->
            set_rank(1, RiseMembers, 1, []);
        false ->
            RiseMembers
    end,
    NewMembers = RiseRankMembers ++ RankOutMembers ++ Others,
    case Stage =:= ?BC_STAGE_TOP_2 of
        true ->
            %% 冠军日志
            log_beauty_contest_rank(ClsType, ZoneId, RiseRankMembers),
            %% 决赛结束则玩家发奖结算
            spawn(fun() -> beauty_contest_api:settlement(NewMembers, ClsType) end),
            ok;
        false ->
            ok
    end,
    ActData#beauty_contest_act{members = NewMembers}.

%% 选出晋级的16强玩家和淘汰的玩家
partition_rise_16_and_out(OutMembers, 0, RiseMembers, _, _) -> {lists:reverse(RiseMembers), OutMembers};
partition_rise_16_and_out([], _, RiseMembers, _, _) -> {lists:reverse(RiseMembers), []};
partition_rise_16_and_out([One|Members], Num, RiseMembers, NextStage, Rank) ->
    RiseRole = One#beauty_contest_role{stage = NextStage, origin_rank = Rank},
    partition_rise_16_and_out(Members, Num-1, [RiseRole|RiseMembers], NextStage, Rank + 1).

%% 选出晋级的玩家和淘汰的玩家
partition_rise_and_out_common([], RiseList, OutList, _NextStage, _GroupMask) -> {RiseList, OutList};
partition_rise_and_out_common([H|T], RiseList, OutList, NextStage, GroupMask) ->
    %% 找出分组相同的玩家，票数高的晋级
    Group = get_member_group(H) div GroupMask,
    case find_same_group(Group, GroupMask, T, []) of
        {true, Rival, Left} ->
            case compare_vote_func(H, Rival) of
                true  ->
                    partition_rise_and_out_common(Left, [H#beauty_contest_role{stage = NextStage}|RiseList], [Rival|OutList], NextStage, GroupMask);
                false ->
                    partition_rise_and_out_common(Left, [Rival#beauty_contest_role{stage = NextStage}|RiseList], [H|OutList], NextStage, GroupMask)
            end;
        false ->
            %% 没有对手，直接晋级
            partition_rise_and_out_common(T, [H#beauty_contest_role{stage = NextStage}|RiseList], OutList, NextStage, GroupMask)
    end.

find_same_group(_Group, _GroupMask, [], _Others) -> false;
find_same_group(Group, GroupMask, [H|T], Others) ->
    case (get_member_group(H) div GroupMask) == Group of
        true ->
            {true, H, Others ++ T};
        false ->
            find_same_group(Group, GroupMask, T, [H|Others])
    end.
%%%===================================================================
%%% @doc
%%%     榜单排序
%%% @doc
%%%===================================================================
%% 玩家排序
sort_role(Roles) ->
    lists:sort(fun compare_vote_func/2, Roles).

%% 按票数排序
compare_vote_func(#beauty_contest_role{vote = VoteA, time = TimeA}, #beauty_contest_role{vote = VoteB, time = TimeB}) ->
    if
        VoteA > VoteB -> true;
        VoteA < VoteB -> false;
        true -> TimeA =< TimeB
    end.

%% 按收集数量排序
compare_fashion_func(
    #beauty_contest_role{fashion_num = FashionNumA, picture = #rec_picture{power = PowerA}},
    #beauty_contest_role{fashion_num = FashionNumB, picture = #rec_picture{power = PowerB}}
) ->
    if
        FashionNumA > FashionNumB -> true;
        FashionNumA < FashionNumB -> false;
        true -> PowerA > PowerB
    end.

set_rank(Rank, _, Max, RankMembers) when Rank > Max -> RankMembers;
set_rank(_Rank, [], _Max, RankMembers) -> RankMembers;
set_rank(Rank, [H|Members], Max, RankMembers) ->
    NewOne = H#beauty_contest_role{rank = Rank},
    set_rank(Rank + 1, Members, Max, [NewOne|RankMembers]).

%%%-------------------------------------------------------------------
%%% @doc
%%%     挑选出投票候选
%%% @end
%%%-------------------------------------------------------------------
%% 首先找出挑选次数(pick_counts)最少的人，在其中随机一个作为候选
%% 然后找出跟候选人的挑选次数最相近的玩家，在其中随机一个最为另一个候选
pick_vote_members(_, [], _) -> false;
pick_vote_members(_, [_], _) -> false;
pick_vote_members(_, [One, Other], _) -> [One, Other];
%%pick_vote_members(?BC_STAGE_TOP_4, Members, DailyVote) ->
%%    pick_vote_members_top_four(Members, DailyVote);
pick_vote_members(_Stage, Members, _DailyVote) ->
    %% 先按照挑选次数排个序
    SortMembers = lists:keysort(#beauty_contest_role.pick_counts, Members),
    %% 找到挑选次数最少的玩家
    FewestPickMembers = find_fewest_pick_members(SortMembers, []),
    %% 随机一个作为候选
    [CandidateA|T] = ulists:list_shuffle(FewestPickMembers),
    case T of
        [] ->
            %% 跟自己相等的玩家没有，那就找相差最小的
            [CandidateB|_] = find_closest_pick_members(CandidateA#beauty_contest_role.pick_counts + 1, SortMembers -- [CandidateA]);
        [CandidateB|_] ->
            CandidateB
    end,
    [CandidateA, CandidateB].

%% 找到前几个票数相同的玩家
find_fewest_pick_members([One, Two], List) ->
    case One#beauty_contest_role.pick_counts =:= Two#beauty_contest_role.pick_counts of
        true  ->
            [One, Two|List];
        false ->
            [One|List]
    end;
find_fewest_pick_members([One, Two|T], List) ->
    case One#beauty_contest_role.pick_counts =:= Two#beauty_contest_role.pick_counts of
        true  ->
            find_fewest_pick_members([Two|T], [One|List]);
        false ->
            [One|List]
    end.

%% 找到等于pick的玩家
find_closest_pick_members(Pick, Members) ->
    case find_closest_pick_members_do(Pick, Members) of
        [] -> find_closest_pick_members(Pick+1, Members);
        List -> List
    end.

find_closest_pick_members_do(Pick, Members) ->
    [
        One || One <- Members, One#beauty_contest_role.pick_counts =:= Pick
    ].

%% 4进2的时候根据玩家投票次数特殊处理
%% 四强特殊处理  三强特殊处理 不会出现只有2个人的情况，因为前面已经拦截了
%% 第一次：12   12
%% 第二次：13   13
%% 第三次：14   23
%% 第四次：23   12
%% 第五次：24   13
%% 第六次：34   23
%%pick_vote_members_top_four([One, Two, _] = Members, DailyVote) ->
%%    case DailyVote of
%%        0 -> [get_the_rank_role(Members, 1), get_the_rank_role(Members, 2)];
%%        1 -> [get_the_rank_role(Members, 1), get_the_rank_role(Members, 3)];
%%        2 -> [get_the_rank_role(Members, 2), get_the_rank_role(Members, 3)];
%%        3 -> [get_the_rank_role(Members, 1), get_the_rank_role(Members, 2)];
%%        4 -> [get_the_rank_role(Members, 1), get_the_rank_role(Members, 3)];
%%        5 -> [get_the_rank_role(Members, 2), get_the_rank_role(Members, 3)];
%%        _ -> [One, Two]
%%    end;
%%pick_vote_members_top_four([One, Two, _, _] = Members, DailyVote) ->
%%    case DailyVote of
%%        0 -> [get_the_rank_role(Members, 1), get_the_rank_role(Members, 2)];
%%        1 -> [get_the_rank_role(Members, 1), get_the_rank_role(Members, 3)];
%%        2 -> [get_the_rank_role(Members, 1), get_the_rank_role(Members, 4)];
%%        3 -> [get_the_rank_role(Members, 2), get_the_rank_role(Members, 3)];
%%        4 -> [get_the_rank_role(Members, 2), get_the_rank_role(Members, 4)];
%%        5 -> [get_the_rank_role(Members, 3), get_the_rank_role(Members, 4)];
%%        _ -> [One, Two]
%%    end.

%%get_the_rank_role([H|_] = Members, Rank) ->
%%    case lists:keyfind(Rank, #beauty_contest_role.rank, Members) of
%%        #beauty_contest_role{} = One -> One;
%%        _ -> H
%%    end.

%%%-------------------------------------------------------------------
%%% @doc
%%%     进入16强，分组计算
%% 分组情形，16强按以下规则进行分组，投票的时候是混合投票，但是晋级的时候，只对相应组的两个人进行晋级判断
%% 比如16近8的时候，不管其他票数如何，第1名和第9名之间票数高的人直接晋级8强
%% 个位和十位表明其初始排名
%% 百位表明其16强的分组
%% 千位表明其6强的分组
%% 万位表明其4强的位置
%% 十万位表明其2强的位置
%%
%%      第 1名（111101）-------                                --------（123502）第 2名
%%                           |--- |                     | ---|
%%      第 9名（111109）-------     |                    |     --------（123510）第10名
%%                                 |                   |
%%                                 |---|           |---|
%%                                 |   |          |    |
%%      第 5名（111205）-------     |    |         |     |     --------（123606）第 6名
%%                           |--- |     |         |     | ---|
%%      第11名（111211）-------           |        |           --------（123612）第12名
%%                                       |-------|
%%                                       |       |
%%      第 3名（112303）-------           |        |           --------（124704）第 4名
%%                           |--- |     |         |     | ---|
%%      第13名（112313）-------     |    |          |    |     --------（124714）14名
%%                                 |   |           |   |
%%                                 |---|           |---|
%%                                 |                   |
%%      第 7名（112407）-------     |                    |     --------（124808）第 8名
%%                           |--- |                     | ---|
%%      第15名（112415）-------                                --------（124816）第16名
%%% @end
%%%-------------------------------------------------------------------
get_member_group(#beauty_contest_role{origin_rank = Rank}) ->
    get_member_group(Rank);
get_member_group(1)  -> 111101;
get_member_group(9)  -> 111109;

get_member_group(5)  -> 111205;
get_member_group(11) -> 111211;

get_member_group(3)  -> 112303;
get_member_group(13) -> 112313;

get_member_group(7)  -> 112407;
get_member_group(15) -> 112415;

get_member_group(2)  -> 123502;
get_member_group(10) -> 123510;

get_member_group(6)  -> 123606;
get_member_group(12) -> 123612;

get_member_group(4)  -> 124704;
get_member_group(14) -> 124714;

get_member_group(8)  -> 124808;
get_member_group(16) -> 124816;
get_member_group(_) -> 0.

log_beauty_contest_rank(?NODE_CENTER, ZoneId, Members) ->
    %% 将周一的时间当作期数
    DayOfWeek = utime:day_of_week(),
    {Y, M, D} = utime:get_before_day(erlang:date(), (DayOfWeek - 1), 0),
    Phase = Y * 10000 + M * 100 + D,
    
    ZoneStage = czone:get_zone_stage(ZoneId),
    Now = utime:unixtime(),
    LogInfo =
        [
            [Phase, ZoneId, One#beauty_contest_role.server_id, One#beauty_contest_role.server_num, ZoneStage, One#beauty_contest_role.role_id,
                One#beauty_contest_role.picture#rec_picture.name, One#beauty_contest_role.stage, One#beauty_contest_role.rank, One#beauty_contest_role.vote, Now]
            || One <- Members
        ],
    lib_log_api:log_beauty_contest_rank(LogInfo);
log_beauty_contest_rank(_, _, _) -> ignore.