%%% -------------------------------------------------------
%%% @author huangyongxing@yeah.net
%%% @doc
%%% 虚拟客户端管理器
%%% @end
%%% -------------------------------------------------------
-module(cli_simu_mgr).

-behaviour(gen_server).

%% API
-export([
        start_link/0
        ,pressure_test/1
        ,client/1
        ,batch_start/1
        ,batch_start/2
        ,set_scene/0
        ,set_scene/1
    ]).

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

-define(SERVER, ?MODULE).

-include("cli_simu.hrl").

-record(state, {
        host = undefined
        ,port = 0
        ,tid = undefined
        ,accname_id_max = 0
    }).

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

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

%% 压力测试（指定人数进行压力测试）
-spec pressure_test(N :: integer()) -> ok.
pressure_test(N) when N > 0 andalso N < 3000 ->
    batch_start(N),
    SleepTime = (N div 10) * 200 + 8000,
    %% 等待几秒，等登录完成，统一设置场景
    timer:apply_after(SleepTime, ?MODULE, set_scene, []),
    ok;
pressure_test(_) ->
    ?INFO_TTY("error N, N must between 1 and 3000", []),
    ok.

client(AccName) when is_list(AccName) orelse is_binary(AccName) ->
    gen_server:cast(?SERVER, {start_client, AccName}).

batch_start(add, Count) ->
    gen_server:cast(?SERVER, {batch_start, {add, Count}});
batch_start(IdMin, IdMax) ->
    gen_server:cast(?SERVER, {batch_start, [IdMin, IdMax]}).

batch_start(ClientCount) ->
    gen_server:cast(?SERVER, {batch_start, ClientCount}).

%% 设置场景
-spec set_scene() -> ok.
set_scene() ->
    SetSceneList =
    case application:get_env(cli_simu, scene) of
        {ok, [_|_] = SceneList} ->
            SceneList;
        _ ->
            [
                {{1001, 14519, 32331, 500}, 100}
            ]
    end,
    set_scene(SetSceneList).

%% 设置场景
%% 里面的SceneList与app文件中的scene是一致的
%% 会根据实际的模拟客户端人数，依次按场景列表的人数来设置；如果还有剩余人数，重新按场景列表的人数来设置
-spec set_scene(SceneList) -> ok when
    SceneList :: [SceneInfo],
    SceneInfo :: {ScenePosInfo, RoleCount},
    ScenePosInfo :: {SceneId, X, Y, Offset} | {SceneId, X, Y} | SceneId,
    SceneId :: integer(), X :: integer(), Y :: integer(), Offset :: integer(),
    RoleCount :: integer().
set_scene(SceneList) ->
    RolePids = cli_role_ets:get_client_pids(),
    lists:foldl(fun(Pid, {Count, AccSceneList, Order}) ->
                {NewCount, NewAcc} =
                case AccSceneList of
                    [{ScenePosInfo, MaxCount} | _Rest] when Count < MaxCount ->
                        Pid ! {set_scene, ScenePosInfo},
                        {Count + 1, AccSceneList};
                    [{ScenePosInfo, Count} | Rest] ->
                        Pid ! {set_scene, ScenePosInfo},
                        {1, Rest};
                    [] ->
                        [{ScenePosInfo, _} | _] = SceneList,
                        Pid ! {set_scene, ScenePosInfo},
                        {2, SceneList}
                end,
                NewOrder = Order + 1,
                case NewOrder rem 10 =:= 0 of
                    true  -> timer:sleep(200);
                    false -> ignore
                end,
                {NewCount, NewAcc, NewOrder}
        end, {1, SceneList, 1}, RolePids),
    ok.

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

init([]) ->
    process_flag(trap_exit, true),
    Tid = cli_role_ets:init_role_tab(),
	{ok, Host} = application:get_env(host),
	{ok, Port} = application:get_env(port),
    ?INFO_TTY("default server host:port [~s:~w]", [Host, Port]),
    {ok, #state{host = Host, port = Port, tid = Tid}}.

handle_call(Request, From, State) ->
    try
        do_handle_call(Request, From, State)
    catch
        ?EXCEPTION(_ErrClass, _ErrReason, _Stacktrace) ->
            ?ERR("call:~w, ~w,~w,~p", [Request, _ErrClass, _ErrReason, ?GET_STACK(_Stacktrace)])
    end.

handle_cast(Msg, State) ->
    try
        do_handle_cast(Msg, State)
    catch
        ?EXCEPTION(_ErrClass, _ErrReason, _Stacktrace) ->
            ?ERR("cast:~w, ~w,~w,~p", [Msg, _ErrClass, _ErrReason, ?GET_STACK(_Stacktrace)])
    end.

handle_info(Info, State) ->
    try
        do_handle_info(Info, State)
    catch
        ?EXCEPTION(_ErrClass, _ErrReason, _Stacktrace) ->
            ?ERR("info:~w, ~w,~w,~p", [Info, _ErrClass, _ErrReason, ?GET_STACK(_Stacktrace)])
    end.

terminate(_Reason, _State) ->
    ok.

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

%%%===================================================================
%%% handle_info/handle_call/handle_cast Internal functions
%%%===================================================================

do_handle_call(_Req, _From, State) ->
    {reply, ok, State}.

do_handle_cast({start_client, AccName}, State = #state{host = Host, port = Port}) ->
    start_client(AccName, Host, Port),
    {noreply, State};

%% 指定区间段开启账号
do_handle_cast({batch_start, [IdMin, IdMax]}, State = #state{host = Host, port = Port, accname_id_max = OldMax}) ->
    IdMaxTmp = start_clients(IdMin, IdMax, Host, Port),
    NewMax = max(IdMaxTmp, OldMax),
    {noreply, State#state{accname_id_max = NewMax}};

do_handle_cast({batch_start, {add, AddCount}}, State = #state{host = Host, port = Port, accname_id_max = OldMax}) ->
    IdMaxTmp = start_clients(OldMax + 1, OldMax + AddCount, Host, Port),
    {noreply, State#state{accname_id_max = IdMaxTmp}};

do_handle_cast({batch_start, ClientCount}, State = #state{host = Host, port = Port, accname_id_max = OldMax}) ->
    IdMaxTmp = start_clients(1, ClientCount, Host, Port),
    NewMax = max(IdMaxTmp, OldMax),
    {noreply, State#state{accname_id_max = NewMax}};

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

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

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

start_client(AccName, Host, Port) ->
    supervisor:start_child(cli_role_sup, [AccName, Host, Port]).

start_clients(Id, IdMax, Host, Port) when Id =< IdMax ->
    AccName = gen_accname(Id),
    start_client(AccName, Host, Port),
    case Id rem 10 =:= 0 of
        true  -> timer:sleep(200);
        false -> ignore
    end,
    start_clients(Id + 1, IdMax, Host, Port);
start_clients(_Id, IdMax, _Host, _Port) ->
    IdMax.

gen_accname(AccNameId) ->
    AccNamePre = "test",
    lists:concat([AccNamePre, AccNameId]).
