%% ---------------------------------------------------------------------------
%% @doc 跨服竞榜游戏服活动管理
%% @author zhaoyu
%% @since  2018-12-04
%% ---------------------------------------------------------------------------
-module(race_rank_act_local).


-behaviour(gen_server).

%% API
-export([
        start_link/0
        ,call/1                             %% call本进程
        ,cast/1                             %% cast本进程
        ,info/1                             %% 给进程发送消息
        ,is_race_rank_act_opening/1         %% 竞榜活动是否开启中
        ,is_race_rank_act_icon_opening/1    %% 竞榜活动图标是否开启中
    ]).

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

-include("common.hrl").
-include("race_rank.hrl").
-include("predefine.hrl").
-include("clusters.hrl").


%% ---------------------------------------------------------------------------
%% @doc Starts the server
-spec start_link() -> {ok, Pid} | ignore | {error, Error} when
    Pid :: pid(),
    Error :: term().
%% ---------------------------------------------------------------------------
start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).


%% ---------------------------------------------------------------------------
%% @doc 竞榜活动是否开启中 {true,Score}|false
is_race_rank_act_opening(ActId) ->
    case data_race_rank:get_race_rank_act(ActId) of
        #race_rank_act{open_day = [DMin,DMax]} ->
            OpenDay = util:get_open_day(),
            case DMin=<OpenDay andalso OpenDay=<DMax of
                true ->
                    try gen_server:call(?MODULE, {is_rr_act_opening, ActId}) of
                        Reply -> Reply
                    catch _Class:_Reason ->
                        false
                    end;
                false ->
                    false
            end;
        _ ->
            false
    end.



%% ---------------------------------------------------------------------------
%% @doc 竞榜活动图标是否开启中
-spec is_race_rank_act_icon_opening(ActId) -> false | true when
    ActId       :: integer().
%% ---------------------------------------------------------------------------
is_race_rank_act_icon_opening(ActId) ->
    case data_race_rank:get_race_rank_act(ActId) of
        #race_rank_act{open_day = [MinD,MaxD]} ->
            OpenDay = util:get_open_day(),
            case MinD=<OpenDay andalso OpenDay=<MaxD of
                true ->
                    try gen_server:call(?MODULE, {is_rr_act_icon_opening, ActId}) of
                        Reply -> Reply
                    catch _Class:_Reason ->
                        false
                    end;
                false ->
                    false
            end;
        _ ->
            false
    end.


%% ---------------------------------------------------------------------------
%% @doc call进程
-spec call(Msg) -> term() | {error, Reason} when
    Msg     :: term(),
    Reason  :: any().
%% ---------------------------------------------------------------------------
call(Msg) ->
    try gen_server:call(?MODULE, Msg) of
    Reply -> Reply
    catch Class:Reason ->
        Error = {Class, Reason, erlang:get_stacktrace()},
        ?ERR("~w:handle_cast error:~p~n", [?MODULE, Error]),
        {error, Reason}
    end.


%% ---------------------------------------------------------------------------
%% @doc cast进程
-spec cast(Msg) -> ok when
    Msg     :: term().
%% ---------------------------------------------------------------------------
cast(Msg) ->
    gen_server:cast(?MODULE, Msg).


%% ---------------------------------------------------------------------------
%% @doc 给进程发送消息
-spec info(Msg) -> ok when
    Msg     :: term().
%% ---------------------------------------------------------------------------
info(Msg) ->
    erlang:send(?MODULE, Msg).


%% ---------------------------------------------------------------------------
%% @doc Initializes the server
-spec init(Args) ->
    {ok, State}
    | {ok, State, Timeout}
    | ignore
    | {stop, Reason} when
    Args    :: term() ,
    State   :: #race_rank_act_info{},
    Timeout :: non_neg_integer() | infinity,
    Reason  :: term().
%% ---------------------------------------------------------------------------
init(_) ->
    process_flag(trap_exit, true),
    Now = utime:unixtime(),
    % DateTime = utime:unixdate(Now),
    % NextMidnight = DateTime + ?ONE_DAY_SECONDS,
    NextMidnight = utime:tomorrow(Now),
    TimeOut = (NextMidnight - Now) * 1000,
    Ref = erlang:send_after(TimeOut, self(), {act_check_msg}),
    % ServerScores = load_server_scores(),
    {ok, #race_rank_act_info{ref = Ref}}.


%% ---------------------------------------------------------------------------
%% @doc Handling call messages
-spec handle_call(Request, From, State) ->
    {reply, Reply, State}
    | {reply, Reply, State, Timeout}
    | {noreply, State}
    | {noreply, State, Timeout}
    | {stop, Reason, Reply, State}
    | {stop, Reason, State} when
    Request :: term(),
    From :: pid(),
    State :: #race_rank_act_info{},
    Reply :: term(),
    State :: #race_rank_act_info{},
    Timeout :: non_neg_integer() | infinity.
%% ---------------------------------------------------------------------------
handle_call(Request, From, State) ->
    try handle_call2(Request, From, State) of
        Result ->
            Result
    catch Class:Reason ->
        Error = {Class, Reason, erlang:get_stacktrace()},
        ?ERR("~w:handle_cast error:~p~n", [?MODULE, Error]),
        {reply, error, State}
    end.


%% ---------------------------------------------------------------------------
%% @doc Handling cast messages
-spec handle_cast(Msg, State) ->
    {noreply, State}
    | {noreply, State, Timeout}
    | {stop, Reason, State} when
    Msg :: term(),
    State :: #race_rank_act_info{},
    Timeout :: non_neg_integer() | infinity,
    Reason :: term().
%% ---------------------------------------------------------------------------
handle_cast(Msg, State) ->
    try handle_cast2(Msg, State) of
        Result ->
            Result
    catch Class:Reason ->
        Error = {Class, Reason, erlang:get_stacktrace()},
        ?ERR("~w:handle_cast error:~p~n", [?MODULE, Error]),
        {noreply, State}
    end.


%% ---------------------------------------------------------------------------
%% @doc Handling all non call/cast messages
-spec handle_info(Info, State) ->
    {noreply, State}
    | {noreply, State, Timeout}
    | {stop, Reason, State} when
    Info :: term(),
    State :: #race_rank_act_info{},
    Timeout :: non_neg_integer() | infinity,
    Reason :: term().
%% ---------------------------------------------------------------------------
handle_info(Info, State) ->
    try handle_info2(Info, State) of
        Result ->
            Result
    catch Class:Reason ->
        Error = {Class, Reason, erlang:get_stacktrace()},
        ?ERR("~w:handle_cast error:~p~n", [?MODULE, Error]),
        {noreply, State}
    end.



%% ---------------------------------------------------------------------------
%% @doc Handling call messages
-spec handle_call2(Request, From, State) ->
    {reply, Reply, State}
    | {reply, Reply, State, Timeout}
    | {noreply, State}
    | {noreply, State, Timeout}
    | {stop, Reason, Reply, State}
    | {stop, Reason, State} when
    Request :: term(),
    From :: pid(),
    State :: #race_rank_act_info{},
    Reply :: term(),
    State :: #race_rank_act_info{},
    Timeout :: non_neg_integer() | infinity.
%% ---------------------------------------------------------------------------
%% 竞榜活动是否开启中
handle_call2({is_rr_act_opening, ActId}, _From, State) ->
    Now = utime:unixtime(),
    #race_rank_act_info{opening = OpenList} = State,
    Reply = case lists:keyfind(ActId, #rr_act_info.act_id, OpenList) of
            #rr_act_info{
                status = ?NOT_SETTLEMENT, start_time = StartTime, end_time = EndTime
            } when StartTime =< Now andalso Now < EndTime ->
                true;
            _ ->
                false
        end,
    {reply, Reply, State};
%% 竞榜活动图标是否开启中
handle_call2({is_rr_act_icon_opening, ActId}, _From, State) ->
    Now = utime:unixtime(),
    #race_rank_act_info{opening = OpenList} = State,
    Reply = case lists:keyfind(ActId, #rr_act_info.act_id, OpenList) of
        #rr_act_info{
            status = ?NOT_SETTLEMENT, start_time = StartTime, end_time = EndTime
        } when StartTime =< Now andalso Now < EndTime ->
            true;
        #rr_act_info{
            start_time = StartTime, show_end_time = ShowEndTime
        } when StartTime =< Now andalso Now < ShowEndTime ->
             true;
        _ ->
            false
    end,
    {reply, Reply, State};
handle_call2(_Request, _From, State) ->
    {reply, ok, State}.



%% ---------------------------------------------------------------------------
%% @doc Handling cast messages
-spec handle_cast2(Msg, State) ->
    {noreply, State}
    | {noreply, State, Timeout}
    | {stop, Reason, State} when
    Msg :: term(),
    State :: #race_rank_act_info{},
    Timeout :: non_neg_integer() | infinity,
    Reason :: term().
%% ---------------------------------------------------------------------------
%% 跨服通知-更新正在开的活动
handle_cast2({reset_opening_act_list, OpeningList}, State) ->
    Now = utime:unixtime(),
    check_act_world_lev_stage(OpeningList, Now),
    NewState = State#race_rank_act_info{opening = OpeningList},
    broadcast_acts_info(NewState),
    {noreply, NewState};
%% 所有的正在开启中的竞榜活动信息
handle_cast2({send_opening_act_info, Sid}, State) ->
    send_opening_act_info(State, Sid),
    {noreply, State};

%%玩家充值事件
handle_cast2({event_recharge, RoleID}, State) ->
    #race_rank_act_info{opening = OpeningList} = State,
    WishList = filter_wish_act(OpeningList,?TYPE_WISH),
    WishList /=[] andalso lib_player:apply_cast(RoleID,?APPLY_CAST_STATUS,lib_role_wish,change_gold,[WishList]),
    {noreply, State};

% %%服务器积分更新
% handle_cast2({draw_add_score,Stage,ActID,Add,Now},State)->
%     #race_rank_act_info{scores=ServerScores} = State,
%     case lists:keyfind(ActID,1,ServerScores) of
%         {_,Score,_}->
%             skip;
%         _->
%             Score = 0
%     end,
%     NScore = Score+Add,
%     NServerScores = lists:keystore(ActID,1,ServerScores,{ActID,NScore,Now}),
%     NState = State#race_rank_act_info{scores=NServerScores},
%     broadcast_server_score(ActID,Stage,Score,NScore),
%     save_server_score(ActID,NScore,Now),
%     {noreply,NState};

% handle_cast2({clear_server_score,ActId},State)->
%     #race_rank_act_info{scores=ServerScores} = State,
%     NServerScores = lists:keydelete(ActId,1,ServerScores),
%     NState = State#race_rank_act_info{scores=NServerScores},
%     {noreply,NState};

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


%% ---------------------------------------------------------------------------
%% @doc Handling all non call/cast messages
-spec handle_info2(Info, State) ->
    {noreply, State}
    | {noreply, State, Timeout}
    | {stop, Reason, State} when
    Info :: term(),
    State :: #race_rank_act_info{},
    Timeout :: non_neg_integer() | infinity,
    Reason :: term().
%% ---------------------------------------------------------------------------
%% 每天零点定时器
handle_info2({act_check_msg}, State) ->
    NewState = act_check(State),
    {noreply, NewState};
handle_info2(_Info, State) ->
    {noreply, State}.


%% ---------------------------------------------------------------------------
%% @doc called by a gen_server when it is about to terminate
-spec terminate(Reason, State) -> ok when
    Reason :: term(),
    State  :: #race_rank_act_info{}.
%% ---------------------------------------------------------------------------
terminate(_Reason, _State) ->
    ok.


%% ---------------------------------------------------------------------------
%% @doc Convert process state when code is changed
-spec code_change(OldVsn, State, Extra) -> {ok, NewState} when
    OldVsn :: term(),
    State :: #race_rank_act_info{},
    Extra :: term(),
    NewState :: #race_rank_act_info{}.
%% ---------------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
    {ok, State}.


%% ---------------------------------------------------------------------------
%% @doc 每天定时器
-spec act_check(State) -> NewState when
    State       :: #race_rank_act{},
    NewState    :: #race_rank_act{}.
%% ---------------------------------------------------------------------------
act_check(State) ->
    #race_rank_act_info{opening = OpeningList, ref = Ref} = State,
    util:cancel_timer(Ref),
    Now = utime:unixtime(),
    IsBroadcast = init_act_world_lev_stage(OpeningList, Now, false),
    % DateTime = utime:unixdate(Now),
    % NextMidnight = DateTime + ?ONE_DAY_SECONDS,
    NextMidnight = utime:tomorrow(Now),
    TimeOut = (NextMidnight - Now) * 1000,
    NewRef = erlang:send_after(TimeOut, self(), {act_check_msg}),
    NewState = State#race_rank_act_info{ref = NewRef},
    IsBroadcast andalso broadcast_acts_info(NewState),
    NewState.


%% 判断是否有活动活动时间到了
init_act_world_lev_stage([], _Now, IsBroadcast) ->
    IsBroadcast;
init_act_world_lev_stage([Act | T], Now, IsBroadcast) ->
    #rr_act_info{act_id = ActId, end_time = EndTime} = Act,
    OpenDay = util:get_open_day(),
    IsBroadcast2 = case data_race_rank:get_race_rank_act(ActId) of
        #race_rank_act{
            open_day = [MinD,MaxD], show_time = ShowTime
        } when EndTime > Now,MinD=<OpenDay,OpenDay=<MaxD -> %% 游戏服现在才达到开活动的条件
            ActStage = get_act_world_lev_stage(ActId),
            WorldLev = util:get_world_lv(),
            Stage = data_race_rank:get_world_lev_stage(WorldLev),
            ValidTime = EndTime + ShowTime,
            ActStage2 = ActStage#rr_act_stage{
                stage = Stage, valid_time = ValidTime
            },
            save_act_world_lev_stage(ActStage2),
            true;
        _ ->
            IsBroadcast
    end,
    init_act_world_lev_stage(T, Now, IsBroadcast2).


%% ---------------------------------------------------------------------------
%% @doc 检查活动世界等级阶段数据是否要更新
-spec check_act_world_lev_stage(ActList, Now) -> ok when
    ActList     :: #rr_act_info{},
    Now         :: integer().
%% ---------------------------------------------------------------------------
check_act_world_lev_stage(ActList, Now) ->
    NowOpenDay = util:get_open_day(),
    ActStageList = check_act_world_lev_stage_do(ActList, Now, NowOpenDay, []),
    save_act_world_lev_stage(ActStageList).

check_act_world_lev_stage_do([], _Now, _NowOpenDay, ActStageList) ->
    ActStageList;
check_act_world_lev_stage_do([Act | T], Now, NowOpenDay, ActStageList) ->
    #rr_act_info{act_id = ActId, end_time = EndTime} = Act,
    ActStageList2 = case data_race_rank:get_race_rank_act(ActId) of
        #race_rank_act{
            open_day = OpenDay, show_time = ShowTime
        } when NowOpenDay >= OpenDay ->
            case get_act_world_lev_stage(ActId) of
                ActStage = #rr_act_stage{
                    valid_time = ValidTime
                } when Now >= ValidTime -> %% 过期
                    WorldLev = util:get_world_lv(),
                    Stage = data_race_rank:get_world_lev_stage(WorldLev),
                    NewValidTime = EndTime + ShowTime,
                    ActStage2 = ActStage#rr_act_stage{
                        stage = Stage, valid_time = NewValidTime
                    },
                    [ActStage2 | ActStageList];
                ActStage = #rr_act_stage{} ->
                    ValidTime = EndTime + ShowTime,
                    ActStage2 = ActStage#rr_act_stage{valid_time = ValidTime},
                    [ActStage2 | ActStageList]
            end;
        _ ->
            ActStageList
    end,
    check_act_world_lev_stage_do(T, Now, NowOpenDay, ActStageList2).



%% ---------------------------------------------------------------------------
%% @doc 活动世界等级阶段数据
-spec get_act_world_lev_stage(ActId) -> Stage when
    ActId       :: integer(),
    Stage       :: #rr_act_stage{}.
%% ---------------------------------------------------------------------------
get_act_world_lev_stage(ActId) ->
    case game_env:get(rr_act_world_lev_stage) of
        StageList when is_list(StageList) ->
            case lists:keyfind(ActId, #rr_act_stage.act_id, StageList) of
                ActStage = #rr_act_stage{} -> ActStage;
                _ -> #rr_act_stage{act_id = ActId}
            end;
        _ ->
            #rr_act_stage{act_id = ActId}
    end.

%% ---------------------------------------------------------------------------
%% @doc 活动世界等级阶段数据
-spec save_act_world_lev_stage(ActStage) -> ok when
    ActStage    :: #rr_act_stage{} | [#rr_act_stage{}].
%% ---------------------------------------------------------------------------
save_act_world_lev_stage(ActStage = #rr_act_stage{act_id = ActId}) ->
    case game_env:get(rr_act_world_lev_stage) of
        StageList when is_list(StageList) ->
            StageList2 = lists:keystore(
                ActId, #rr_act_stage.act_id, StageList, ActStage
            ),
            game_env:save(rr_act_world_lev_stage, StageList2);
        _ ->
            StageList2 = [ActStage],
            game_env:save(rr_act_world_lev_stage, StageList2)
    end,
    ok;
save_act_world_lev_stage(ActStageList) when is_list(ActStageList) ->
    game_env:save(rr_act_world_lev_stage, ActStageList),
    ok.

%% ---------------------------------------------------------------------------
%% @doc 发送所有的正在开启中的竞榜活动信息
-spec send_opening_act_info(State, Sid) -> ok when
    State       :: #race_rank_act_info{},
    Sid         :: pid().
%% ---------------------------------------------------------------------------
send_opening_act_info(State, Sid) ->
    BinData = pack_acts_info(State),
    lib_server_send:send_to_sid(Sid, BinData),
    ok.

%% 打包所有活动数据
pack_acts_info(State) ->
    #race_rank_act_info{opening = OpeningList} = State,
    OpenDay = util:get_open_day(),
    ProData = pack_act_info(OpeningList, OpenDay, []),
    {ok, BinData} = pt_333:write(33300, [ProData]),
    BinData.

pack_act_info([], _NowOpenDay, DataList) ->
    DataList;
pack_act_info([Act | T], OpenDay, DataList) ->
    #rr_act_info{act_id = ActId, end_time = EndTime} = Act,
    DataList2 = case data_race_rank:get_race_rank_act(ActId) of
        #race_rank_act{
            open_day = [MinD,MaxD], show_time = ShowTime
        } when MinD=<OpenDay andalso OpenDay=<MaxD ->
            Stage = race_rank:get_act_world_lev_stage(ActId),
            ActInfo = {ActId, EndTime, EndTime + ShowTime, Stage},
            [ActInfo | DataList];
        _ ->
            DataList
    end,
    pack_act_info(T, OpenDay, DataList2).


%% ---------------------------------------------------------------------------
%% @doc 广播所有的正在开启中的竞榜活动信息
-spec broadcast_acts_info(State) -> ok when
    State       :: #race_rank_act_info{}.
%% ---------------------------------------------------------------------------
broadcast_acts_info(State) ->
    BinData = pack_acts_info(State),
    lib_server_send:send_to_all(BinData).

%%筛选正在开放的许愿活动
filter_wish_act(OpeningList,Type)->
    filter_wish_act(OpeningList,Type,[]).
filter_wish_act([],_,OpeningList)->
    OpeningList;
filter_wish_act([H|T],Type,OpeningList)->
    #rr_act_info{act_id = ActId} = H,
    case data_race_rank:get_race_rank_act(ActId) of
        #race_rank_act{type=Type}->
            filter_wish_act(T,Type,[ActId|OpeningList]);
        _->
            filter_wish_act(T,Type,OpeningList)
    end.


% broadcast_server_score(ActID,Stage,Score,NScore)->
%     AwardList = data_race_rank:get_times_award_list(ActID,Stage),
%     broadcast_server_score(ActID,Stage,Score,NScore,AwardList).
% broadcast_server_score(_ActID,_Stage,_Score,_NScore,[])->ok;
% broadcast_server_score(ActID,Stage,Score,NScore,[H|T])->
%     case data_race_rank:get_race_rank_times_award(ActID,Stage,H) of
%         #race_rank_times_award{server_score=Need}->
%             case Score=<Need andalso NScore>=Need of
%                 true->
%                     {ok,Bin} = pt_333:write(33307,[ActID,NScore]),
%                     lib_server_send:send_to_all(Bin);
%                 _->
%                     broadcast_server_score(ActID,Stage,Score,NScore,T)
%             end;
%         _->
%             broadcast_server_score(ActID,Stage,Score,NScore,T)
%     end.

% load_server_scores()->
%     DbList = db:get_all(?SQL_SERVER_SCORE_GET),
%     [{ActId,Score,Time}||[ActId,Score,Time]<-DbList].

% save_server_score(ActId,Score,Time)->
%     SQL = io_lib:format(?SQL_SERVER_SCORE_REPLACE, [ActId,Score,Time]),
%     db:execute(SQL).
