%%%-------------------------------------------------------------------
%%% @author liushl
%%% @doc
%%%     跳一跳游戏
%%2、玩家点击匹配匹配玩家参与游戏
%%3、匹配达到至少3人后，系统将会将玩家拉入对抗场景
%%4、在对抗场景，玩家进行依次轮流跳一跳玩法，未落在格子上的，将会损失一条生命值，生命值归零、超时、中途退出，都会判定失败
%%5、最终根据生存积分进行排名
%%  游戏开启和奖励配置由定制活动管理
%%% @end
%%%-------------------------------------------------------------------
-module(jump_act_center).
-author("liushl").

-behaviour(gen_server).

-include("jump_act.hrl").
-include("custom_act.hrl").
-include("def_module.hrl").
-include("predefine.hrl").
-include("common.hrl").

%% 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([
    jump_act_begin/1                %% 定制活动开始
    , jump_act_end/1                %% 定制活动结束

    , update_role_info/3            %% 更新玩家信息
    , ask_act_info/5                %% 请求活动状态
    , match_start/3                 %% 开始匹配
    , cancel_match/2                %% 取消匹配
    , role_enter/3                  %% 进入场景
    , ready_jump/3                  %% 跳跃状态
    , jump/7                        %% 跳跃
    , jump_end/3                    %% 跳跃完成
    , leave/2                       %% 主动离开
    , offline/2                     %% 玩家离线
    , re_login/2                    %% 玩家重登

    , cancel_role_match/1           %% 取消玩家的匹配
    , broadcast_act_state/3         %% 传闻广播活动状态
    , broadcast_act_state_do/3

    , gm_begin/1
    , gm_end/0
    , gm_get_room_data/2
    , gm_match_success/0

    , apply_cast/1
    , apply_info/1
]).

-export([handle/3]).

%%%===================================================================
%%% API
%%%===================================================================
%% 定制活动开始
jump_act_begin(ActInfo) ->
    apply_cast({custom_act_begin, ActInfo}).

%% 定制活动结束
jump_act_end(ActInfo) ->
    apply_cast({custom_act_end, ActInfo}).

%% 更新玩家信息
update_role_info(RoleId, Key, Value) ->
    case lib_custom_act_util:get_open_subtype_list(?CUSTOM_ACT_TYPE_JUMP_ACT) of
        [#act_info{key = {_, SubType}}] ->
            clusters_node:apply_cast(?MODULE, apply_info, [{common, SubType, ?JUMP_ACT_EVENT_MATCH, {'UPDATE_ROLE', RoleId, Key, Value}}]);
        _ ->
            ignore
    end.

%% 请求活动状态
ask_act_info(SubType, Sid, Count, LifeCount, IsMatching) ->
    clusters_node:apply_cast(?MODULE, apply_info, [{common, SubType, ?JUMP_ACT_EVENT_COMMON, {ask_act_info, Sid, Count, LifeCount, IsMatching}}]).

%% 开始匹配
match_start(SubType, RoleInfo, Pattern) ->
    clusters_node:apply_cast(?MODULE, apply_info, [{common, SubType, ?JUMP_ACT_EVENT_MATCH, {'MATCH_ACCEPT', RoleInfo, Pattern}}]).

%% 取消匹配
cancel_match(SubType, RoleId) ->
    clusters_node:apply_cast(?MODULE, apply_info, [{common, SubType, ?JUMP_ACT_EVENT_MATCH, {'CANCEL_MATCH', RoleId}}]).

%% 进入场景
role_enter(RoomId, RoleId, Sid) ->
    case lib_custom_act_util:get_open_subtype_list(?CUSTOM_ACT_TYPE_JUMP_ACT) of
        [#act_info{key = {_, SubType}}] ->
            clusters_node:apply_cast(?MODULE, apply_info, [{common, SubType, ?JUMP_ACT_EVENT_PLAY, {'ROLE_ENTER', RoomId, RoleId, Sid}}]);
        _ ->
            ignore
    end.

%% 跳跃状态
ready_jump(SubType, RoomId, RoleId) ->
    clusters_node:apply_cast(?MODULE, apply_info, [{common, SubType, ?JUMP_ACT_EVENT_PLAY, {'READY_JUMP', RoomId, RoleId}}]).

%% 跳跃
jump(SubType, RoomId, RoleId, TargetX, TargetY, Status, Time) ->
    clusters_node:apply_cast(?MODULE, apply_info, [{common, SubType, ?JUMP_ACT_EVENT_PLAY, {'JUMP', RoomId, RoleId, TargetX, TargetY, Status, Time}}]).

%% 跳跃完成
jump_end(SubType, RoomId, RoleId) ->
    clusters_node:apply_cast(?MODULE, apply_info, [{common, SubType, ?JUMP_ACT_EVENT_PLAY, {'JUMP_END', RoomId, RoleId}}]).

%% 主动离开
leave(RoomId, RoleId) ->
    case lib_custom_act_util:get_open_subtype_list(?CUSTOM_ACT_TYPE_JUMP_ACT) of
        [#act_info{key = {_, SubType}}] ->
            clusters_node:apply_cast(?MODULE, apply_info, [{common, SubType, ?JUMP_ACT_EVENT_PLAY, {'LEAVE', RoomId, RoleId}}]);
        _ ->
            ignore
    end.

%% 玩家离线
offline(RoomId, RoleId) ->
    case lib_custom_act_util:get_open_subtype_list(?CUSTOM_ACT_TYPE_JUMP_ACT) of
        [#act_info{key = {_, SubType}}] ->
            clusters_node:apply_cast(?MODULE, apply_info, [{common, SubType, ?JUMP_ACT_EVENT_PLAY, {'OFFLINE', RoomId, RoleId}}]);
        _ ->
            ignore
    end.

%% 玩家重登
re_login(SubType, RoleId) ->
    clusters_node:apply_cast(?MODULE, apply_info, [{common, SubType, ?JUMP_ACT_EVENT_PLAY, {'RE_LOGIN', RoleId}}]).

gm_begin(LastTime) ->
    case lib_custom_act_util:get_open_subtype_list(?CUSTOM_ACT_TYPE_JUMP_ACT) of
        [#act_info{key = {_, SubType}}] ->
            clusters_node:apply_cast(?MODULE, apply_info, [{common, SubType, ?JUMP_ACT_EVENT_COMMON, {act_begin, LastTime}}]);
        _ ->
            ignore
    end.
gm_end() ->
    case lib_custom_act_util:get_open_subtype_list(?CUSTOM_ACT_TYPE_JUMP_ACT) of
        [#act_info{key = {_, SubType}}] ->
            clusters_node:apply_cast(?MODULE, apply_info, [{common, SubType, ?JUMP_ACT_EVENT_COMMON, act_end}]);
        _ ->
            ignore
    end.

gm_get_room_data(SubType, RoomId) ->
    gen_server:call(?MODULE, {gm_get_room_data, SubType, RoomId}).
gm_match_success() ->
    case lib_custom_act_util:get_open_subtype_list(?CUSTOM_ACT_TYPE_JUMP_ACT) of
        [#act_info{key = {_, SubType}}] ->
            clusters_node:apply_cast(?MODULE, apply_info, [{common, SubType, ?JUMP_ACT_EVENT_MATCH, 'MATCH_OVERDUE'}]);
        _ ->
            ignore
    end.

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 :: #jump_act_center{}} | {ok, State :: #jump_act_center{}, timeout() | hibernate} |
    {stop, Reason :: term()} | ignore).
init([]) ->
    erlang:process_flag(trap_exit, true),
    %% 判断是否有正在开启的活动
    case lib_custom_act_api:get_open_custom_act_infos(?CUSTOM_ACT_TYPE_JUMP_ACT) of
        [_|_] = List ->
            [jump_act_begin(ActInfo) || ActInfo <- List];
        _ ->
            ok
    end,
    {ok, #jump_act_center{}}.

get_handler(?JUMP_ACT_EVENT_COMMON) -> ?MODULE;
get_handler(?JUMP_ACT_EVENT_MATCH) -> jump_act_match;
get_handler(?JUMP_ACT_EVENT_PLAY)  -> jump_act_play.
%%%===================================================================
%%% Logic 
%%%===================================================================
do_handle_call({gm_get_room_data, SubType, RoomId}, _From, State) ->
    #jump_act_center{act_data = ActData} = State,
    Res =
    case maps:find(SubType, ActData) of
        {ok, ActInfo} ->
            case maps:find(RoomId, ActInfo#jump_act_act.rooms) of
                {ok, Room} ->
                    Room;
                error ->
                    no_room
            end;
        error ->
            no_type
    end,
    {reply, Res, State};
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({custom_act_begin, Act}, State)->
    #jump_act_center{act_data = ActData} = State,
    #act_info{key = {_Type, SubType}} = Act,
    case maps:is_key(SubType, ActData) of
        true  ->
            {noreply, State};
        false ->
            %% 准备开始活动
            ActInfo = check_begin(#jump_act_act{sub_type = SubType}),
            NewActData = ActData#{SubType => ActInfo},
            {noreply, State#jump_act_center{act_data = NewActData}}
    end;

%% 定制活动结束
do_handle_cast({custom_act_end, Act}, State) ->
    #jump_act_center{act_data = ActData} = State,
    #act_info{key = {_Type, SubType}} = Act,
    case maps:find(SubType, ActData) of
        {ok, ActInfo} ->
            stop_act(ActInfo),
            NewActData = maps:remove(SubType, ActData),
            case NewActData of
                #{} ->
                    {noreply, #jump_act_center{}};
                _ ->
                    {noreply, State#jump_act_center{act_data = NewActData}}
            end;
        error ->
            {noreply, State}
    end;

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({common, SubType, Event, Msg}, State) ->
    #jump_act_center{
        act_data = ActData
    } = State,
    case maps:find(SubType, ActData) of
        {ok, ActInfo} ->
            case get_handler(Event) of
                undefined ->
                    {noreply, State};
                Handler ->
                    case Handler:handle(Msg, State, ActInfo) of
                        {NewState, NewActInfo} when
                            is_record(NewState, jump_act_center)
                                andalso is_record(NewActInfo, jump_act_act) ->
                            case NewActInfo of
                                ActInfo ->
                                    %% 未修改活动数据
                                    {noreply, NewState};
                                _ ->
                                    NewActData = ActData#{SubType => NewActInfo},
                                    {noreply, NewState#jump_act_center{act_data = NewActData}}
                            end;
                        _ ->
                            %% 忽略返回
                            {noreply, State}
                    end
            end;
        error ->
            %% 活动未开
            {noreply, State}
    end;

%% 检查关闭活动场景
do_handle_info({check_close, Count}, State) ->
    ActData = State#jump_act_center.act_data,
    {Active, AllEnd} = maps:fold(
        fun
            (_SubType, #jump_act_act{end_time = EndTime}, {_Force, _Flag}) when EndTime > 0 ->
                {true, false};
            (_SubType, #jump_act_act{rooms = Rooms}, {Force, Flag}) ->
            case Rooms == #{} of
                true  ->
                    {Force, Flag};
                false ->
                    {Force, false}
            end
        end, {false, true}, ActData
    ),
    SceneId = ?JUMP_ACT_SCENE,
    Res =
    if
        Active ->
            %% 活动进行中，不清理
            false;
        AllEnd ->
            %% 所有活动结束，清理;
            true;
        true ->
            %% 还有人正在游戏，等所有人都结束再关闭场景
            case Count >= 100 of
                true  ->
                    %% 判断100次了还有数据，强制清理
                    Arg = custom_act_jump_act:out_arg(),
                    [
                        lib_scene:kick_players_out_scene(SceneId, PoolId, Arg)
                        || PoolId <- scene_mgr:get_scene_pool_ids(SceneId)
                    ],
                    ?INFO("force stop game", []),
                    true;
                false ->
                    erlang:send_after(300000, self(), {check_close, Count + 1}),
                    false
            end
    end,
    case Res of
        true  ->
            mod_scene_agent:close_scene(SceneId),
            ResetData = maps:map(
                fun(Key, _Value) -> #jump_act_act{sub_type = Key} end, ActData
            ),
            {noreply, #jump_act_center{act_data = ResetData}};
        false ->
            {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}.

%% 循环检查开始玩法
handle(check, State, ActInfo) ->
    {State, check_begin(ActInfo)};
%% 单场活动开始
handle({act_begin, LastTime}, State, ActInfo)
    when ActInfo#jump_act_act.end_time == 0 ->
    SubType = ActInfo#jump_act_act.sub_type,
    util:cancel_timer(ActInfo#jump_act_act.ref_state),
    EndTime = utime:unixtime() + LastTime,
    Msg = {common, SubType, ?JUMP_ACT_EVENT_COMMON, act_end},
    RefState  = erlang:send_after(LastTime * 1000, self(), Msg),
    broadcast_act_state(SubType, 41, EndTime),
    NewActInfo = ActInfo#jump_act_act{
        ref_state = RefState,
        end_time  = EndTime
    },
    {State, NewActInfo};
%% 单场活动结束
handle(act_end, State, ActInfo) ->
    ActInfo1 = stop_act(ActInfo),
    %% 准备下一场
    ActInfo2 = check_begin(ActInfo1),
    %% 判断是否需要关闭活动场景
    self() ! {check_close, 1},
    {State, ActInfo2};
%% 查看活动数据
handle({ask_act_info, Sid, Count, LifeCount, IsMatching}, _State, ActInfo) ->
    #jump_act_act{end_time = EndTime} = ActInfo,
    {ok, Bin} = pt_459:write(45901, [Count, EndTime, LifeCount]),
    lib_server_send:send_to_sid(Sid, Bin),
    case IsMatching of
        true  ->
            MatchBin = jump_act_match:patch_match_info(ActInfo),
            lib_server_send:send_to_sid(Sid, MatchBin);
        false ->
            ok
    end,
    ok;
handle(_Msg, _State, _ActInfo) ->
    ok.

%% 准备下一场活动/计算下一场的场次和时间
check_begin(ActInfo) ->
    Type = ?CUSTOM_ACT_TYPE_JUMP_ACT,
    SubType = ActInfo#jump_act_act.sub_type,
    case lib_custom_act_util:get_act_cfg_info(Type, SubType) of
        #custom_act_cfg{condition = Condition} ->
            case lists:keyfind(open_time, 1, Condition) of
                {open_time, OpenTime} ->
                    case lists:keyfind(last_time, 1, Condition) of
                        {last_time, LastTime} ->
                            Now = utime:unixtime(),
                            {Date, {_H, M, S}} = utime:localtime(),
                            SortTime = lists:keysort(1, OpenTime),
                            case calc_time(SortTime, SortTime, LastTime, Now, Date) of
                                {check, CheckTime} ->
                                    %% 如果活动没开始，半小时检查一次配置
                                    HalfHour =
                                        case M >= 30 of
                                            true  -> (60 - M - 1) * 60 + (60 - S);
                                            false -> (30 - M - 1) * 60 + (60 - S)
                                        end,
                                    DiffTime = CheckTime - Now,
                                    NextTime  =
                                        case DiffTime > HalfHour of
                                            true  ->
                                                HalfHour;
                                            false ->
                                                DiffTime
                                        end,
                                    Msg = {common, SubType, ?JUMP_ACT_EVENT_COMMON, check},
                                    RefState = erlang:send_after(NextTime * 1000, self(), Msg),
                                    ActInfo#jump_act_act{
                                        ref_state = RefState
                                    };
                                {act_begin, EndTime} ->
                                    %% 活动期间，直接玩就行
                                    Msg = {common, SubType, ?JUMP_ACT_EVENT_COMMON, {act_begin, EndTime - Now}},
                                    self() ! Msg,
                                    ActInfo#jump_act_act{end_time  = 0};
                                _ ->
                                    ActInfo
                            end;
                        _ ->
                            ActInfo
                    end;
                _ ->
                    ActInfo
            end;
        _ ->
            ActInfo
    end.

calc_time([], [{Hour, Min}|_], _, _Now, Date) ->
    %% 第二天
    Tomorrow = utime:get_before_day(Date, 1, 1),
    NextTime = utime:unixtime({Tomorrow, {Hour, Min, 0}}),
    {check, NextTime};
calc_time([{Hour, Min}|T], OpenTime, LastTime, Now, Date) ->
    BeginTime = utime:unixtime({Date, {Hour, Min, 0}}),
    EndTime   = BeginTime + LastTime,
    if
        Now < BeginTime ->
            {check, BeginTime};
        Now < EndTime ->
            %% 判断的时候处于活动期间内
            {act_begin, EndTime};
        true ->
            calc_time(T, OpenTime, LastTime, Now, Date)
    end;
calc_time(_, _, _, _, _) -> {false, 0}.

%% 结束活动
stop_act(ActInfo) ->
    util:cancel_timer(ActInfo#jump_act_act.ref_state),
    util:cancel_timer(ActInfo#jump_act_act.match_ref),
    cancel_role_match(ActInfo#jump_act_act.match_list),
    broadcast_act_state(ActInfo#jump_act_act.sub_type, 42, 0),
    #jump_act_act{
        sub_type = ActInfo#jump_act_act.sub_type,
        rooms = ActInfo#jump_act_act.rooms
    }.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling call messages
%%
%% @end
%%--------------------------------------------------------------------
-spec(handle_call(Request :: term(), From :: {pid(), Tag :: term()},
    State :: #jump_act_center{}) ->
    {reply, Reply :: term(), NewState :: #jump_act_center{}} |
    {reply, Reply :: term(), NewState :: #jump_act_center{}, timeout() | hibernate} |
    {noreply, NewState :: #jump_act_center{}} |
    {noreply, NewState :: #jump_act_center{}, timeout() | hibernate} |
    {stop, Reason :: term(), Reply :: term(), NewState :: #jump_act_center{}} |
    {stop, Reason :: term(), NewState :: #jump_act_center{}}).
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 :: #jump_act_center{}) ->
    {noreply, NewState :: #jump_act_center{}} |
    {noreply, NewState :: #jump_act_center{}, timeout() | hibernate} |
    {stop, Reason :: term(), NewState :: #jump_act_center{}}).
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 :: #jump_act_center{}) ->
    {noreply, NewState :: #jump_act_center{}} |
    {noreply, NewState :: #jump_act_center{}, timeout() | hibernate} |
    {stop, Reason :: term(), NewState :: #jump_act_center{}}).
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 :: #jump_act_center{}) ->
    term()).
terminate(_Reason, _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 :: #jump_act_center{},
    Extra :: term()) ->
    {ok, NewState :: #jump_act_center{}} | {error, Reason :: term()}).
code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

%%%===================================================================
%%% Internal functions
%%%===================================================================
%% 取消玩家的匹配:主动取消/下线/活动结束
cancel_role_match(Role) when is_record(Role, jump_act_role) ->
    custom_act_jump_act:cancel_match_callback(Role#jump_act_role.node, Role#jump_act_role.role_id);
cancel_role_match(RoleList) ->
    [
        cancel_role_match(Role) || Role <- RoleList
    ].

%% 传闻广播活动状态
broadcast_act_state(SubType, TvType, Time) ->
    clusters_center:apply_to_all_node(?MODULE, broadcast_act_state_do, [SubType, TvType, Time]).
broadcast_act_state_do(SubType, TvType, Time) ->
    case lib_custom_act_api:is_open_act(?CUSTOM_ACT_TYPE_JUMP_ACT, SubType) of
        true ->
            {ok, Bin} = pt_459:write(45900, [Time]),
            lib_server_send:send_to_all(Bin),
            lib_chat:send_TV({all}, ?MOD_AC_CUSTOM, TvType, []);
        _ ->
            ok
    end.