%%% -------------------------------------------------------
%%% @author huangyongxing@yeah.net
%%% @doc
%%% 场景进入排队管理
%%% @end
%%% -------------------------------------------------------
-module(scene_enter_queue).

-behaviour(gen_server).

%% API
-export([
        start_link/0
        ,enter/2
        ,cancel_enter/2
    ]).

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

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

-define(SERVER, {global, ?MODULE}).                 % 服务进程的引用

-define(GAME_HANDLE_INTERVAL,   500).               % 游戏节点排队处理间隔（秒）
-define(CENTER_HANDLE_INTERVAL, 500).               % 跨服中心节点排队处理间隔（秒）
-define(GAME_HANDLE_EACH_TIME,   30).               % 游戏节点排队每次处理人数
-define(CENTER_HANDLE_EACH_TIME, 30).               % 跨服中心节点排队每次处理人数

-record(state, {
        cluster_type                                % 游戏节点/跨服中心节点
        ,timer                                      % 定时器
        ,queue                                      % 排队队列
    }).

%%%===================================================================
%%% API
%%%===================================================================

start_link() ->
    gen_server:start_link(?SERVER, ?MODULE, [], []).

%% 进入排队
enter(EnterSceneId, {RoleId, RolePid, SceneId, ScenePoolId, CopyId, X, Y, KeyValueList}) ->
    IsCenterScene = lib_scene:is_clusters_scene(EnterSceneId),
    IsCenterNode  = clusters_api:is_center(),
    case IsCenterNode of
        true  ->    % 在跨服中心节点调用
            ServerId = mod_id_create:get_serid_by_id(RoleId),
            Node = clusters_center:find_node(ServerId),
            case IsCenterScene of
                true  ->    % 进入跨服场景
                    gen_server:cast(?SERVER, {join, Node, RoleId, RolePid, SceneId, ScenePoolId, CopyId, X, Y, KeyValueList});
                false ->    % 进入游戏服节点的场景
                    % clusters_center:apply_cast(Node, {
                    %         gen_server,
                    %         cast,
                    %         [{join, undefined, RoleId, RolePid, SceneId, ScenePoolId, CopyId, X, Y, KeyValueList}]
                    %     })
                    %% 不应该出现这种情况的调用，有可能用错了
                    error
            end;
        false ->    % 在游戏服节点调用
            case IsCenterScene of
                true  ->    % 进入跨服中心节点的场景
                    Node = clusters_node:get_node(),
                    clusters_node:apply_cast(
                        gen_server, cast, [?SERVER, {join, Node, RoleId, RolePid, SceneId, ScenePoolId, CopyId, X, Y, KeyValueList}]
                    );
                false ->    % 进入游戏服场景
                    gen_server:cast(?SERVER, {join, undefined, RoleId, RolePid, SceneId, ScenePoolId, CopyId, X, Y, KeyValueList})
            end
    end.

%% 取消排队
cancel_enter(EnterSceneId, RoleId) ->
    case lib_scene:is_clusters_scene(EnterSceneId) of
        true ->
            clusters_node:apply_cast(
                gen_server, cast, [?SERVER, {cancel_enter, RoleId}]
            );
        false ->
            gen_server:cast(?SERVER, {cancel_enter, RoleId})
    end.

%%%===================================================================
%%% gen_server callbacks
%%%===================================================================

init([]) ->
    ClusterType = clusters_api:get_cluster_type(),
    InitQueue = queue:new(),
    {ok, #state{cluster_type = ClusterType, timer = undefined, queue = InitQueue}}.

handle_call(_Request, _From, State) ->
    Reply = ok,
    {reply, Reply, State}.

handle_cast({join, Node, RoleId, RolePid, SceneId, ScenePoolId, CopyId, X, Y, KeyValueList},
    State = #state{cluster_type = ClusterType, queue = EnterQueue, timer = OldTimer}) ->
    RoleData = {Node, RoleId, RolePid, SceneId, ScenePoolId, CopyId, X, Y, KeyValueList},
    NewQueue =
    case keymember(RoleId, 2, EnterQueue) of
        true  -> EnterQueue;
        false -> queue:in(RoleData, EnterQueue)
    end,
    NewTimer =
    case is_reference(OldTimer) of
        true  -> OldTimer;
        false -> start_timer(ClusterType)
    end,
    {noreply, State#state{timer = NewTimer, queue = NewQueue}};

handle_cast({cancel_enter, RoleId}, State = #state{queue = EnterQueue, timer = OldTimer}) ->
    case keymember(RoleId, 2, EnterQueue) of
        true  ->
            NewQueue = keyremove(RoleId, 2, EnterQueue),
            NewTimer =
            case queue:is_empty(NewQueue) of
                true  ->
                    util:cancel_timer(OldTimer),
                    undefined;
                false ->
                    OldTimer
            end,
            {noreply, State#state{timer = NewTimer, queue = NewQueue}};
        false ->
            {noreply, State}
    end;

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

%% 触发排队处理
handle_info({timeout, Timer, <<>>}, State = #state{cluster_type = ClusterType, timer = Timer, queue = EnterQueue}) ->
    util:cancel_timer(Timer),
    HandleCount = get_handle_count(ClusterType),
    RemainQueue = handle_enter_queue(ClusterType, EnterQueue, 1, HandleCount),
    NewTimer = case queue:is_empty(RemainQueue) of
        true  -> undefined;
        false -> start_timer(ClusterType)
    end,
    NewState = State#state{timer = NewTimer, queue = RemainQueue},
    {noreply, NewState};

handle_info(_Info, State) ->
    {noreply, State}.

terminate(_Reason, _State) ->
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

%%%===================================================================
%%% Internal functions
%%%===================================================================

%% 获取处理队列的超时间隔
get_timeout(?CLUSTER_CENTER) -> ?CENTER_HANDLE_INTERVAL;
get_timeout(_) -> ?GAME_HANDLE_INTERVAL.

%% 获取处理队列时一次处理多少人数
get_handle_count(?CLUSTER_CENTER) -> ?CENTER_HANDLE_EACH_TIME;
get_handle_count(_) -> ?GAME_HANDLE_EACH_TIME.

%% 启动队列处理定时器
start_timer(ClusterType) ->
    Timeout = get_timeout(ClusterType),
    erlang:start_timer(Timeout, self(), <<>>).

handle_enter_queue(ClusterType, EnterQueue, Order, MaxCount) when Order =< MaxCount ->
    case queue:out(EnterQueue) of
        {{value, {Node, _PlayerId, Pid, SceneId, ScenePoolId, CopyId, X, Y, KeyValueList}}, RemainQueue} ->
            try
                case ClusterType of
                    ?CLUSTER_CENTER ->
                        clusters_center:apply_cast(Node, lib_scene, change_player_scene, [Pid, SceneId, ScenePoolId, CopyId, X, Y, KeyValueList]);
                    _ ->
                        lib_scene:change_player_scene(Pid, SceneId, ScenePoolId, CopyId, X, Y, KeyValueList)
                end
                catch _ErrClass:_ErrReason ->
                    % ?ERR("handle enter queue error: ~w,~w", [_ErrClass, _ErrReason]),
                    ignore
            end,
            handle_enter_queue(ClusterType, RemainQueue, Order + 1, MaxCount);
        {empty, _} ->
            EnterQueue
    end;
handle_enter_queue(_ClusterType, EnterQueue, _, _) ->
    EnterQueue.

%% 检查是否已经是成员
%% 查看queue的结构可知，Queue = {RearList,FrontList}
keymember(Key, Nth, {RearList, FrontList}) ->
    lists:keymember(Key, Nth, RearList) orelse lists:keymember(Key, Nth, FrontList).

%% 按nth位置Key值，从队列中移除数据
keyremove(Key, Nth, Queue = {RearList, FrontList}) ->
    case lists:keydelete(Key, Nth, RearList) of
        RearList ->
            case lists:keydelete(Key, Nth, FrontList) of
                FrontList ->
                    Queue;
                NewFrontList ->
                    {RearList, NewFrontList}
            end;
        NewRearList ->
            {NewRearList, FrontList}
    end.
