%%% -------------------------------------------------------
%%% @author huangyongxing@yeah.net
%%% @doc
%%% 模拟的客户端
%%% @end
%%% -------------------------------------------------------
-module(cli_role_proc).

-behaviour(gen_server).

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

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

-include("cli_simu.hrl").


%% 心跳间隔
-define(HEART_INTERVAL, 8000).

%% 是否关闭INFO_TTY打印
% -ifdef(INFO_TTY).
% -undef(INFO_TTY).
% -define(INFO_TTY(F, A), ok).
% -define(INFO_TTY(F), ok).
% -endif.

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

start_link(AccName, Host, Port) ->
    AccId = 1,
    RegisterName = list_to_atom(util:make_sure_list(AccName)),
    gen_server:start_link({local, RegisterName}, ?MODULE, [AccId, AccName, Host, Port], []).

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

init([AccId, AccName, Host, Port]) ->
    process_flag(trap_exit, true),
    case cli_simu_conn:conn(Host, Port) of
        {ok, Socket} ->
            put(socket, Socket),
            CliRolePid = self(),
            case cli_simu_conn:start_link(CliRolePid, Socket) of
                {ok, ConnPid} ->
                    BinAccount = util:make_sure_binary(AccName),
                    case gen_tcp:controlling_process(Socket, ConnPid) of
                        ok ->
                            %% 发送握手数据包
                            % FirstPackage = first_package(),
                            % gen_tcp:send(Socket, FirstPackage),
                            CliRole = #cli_role{pid = CliRolePid
                                , account = BinAccount
                                , acc_id = AccId
                                , conn_pid = ConnPid
                                , socket = Socket
                            },
                            %% 触发账号登录验证
                            cli_login:auth(AccId, AccName),
                            ConnPid ! get_package,
                            %% 开始心跳
                            erlang:start_timer(?HEART_INTERVAL, self(), timer_heartbeat),
                            {ok, CliRole};
                        {error, Reason} ->
                            gen_tcp:close(Socket),
                            {stop, Reason}
                    end;
                _Error ->
                    gen_tcp:close(Socket),
                    throw(_Error),
                    {stop, <<"spawn receiver failure!">>}
            end;
        {error, Err} ->
            {stop, Err}
    end.

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)]),
            {reply, error, State}
    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)]),
            {noreply, State}
    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)]),
            {noreply, State}
    end.

terminate(_Reason, _State = #cli_role{id = RoleId}) ->
    RoleId > 0 andalso ets:delete(?CLI_ROLE, RoleId),
    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(_Msg, State) ->
    {noreply, State}.

%% 心跳处理
do_handle_info({timeout, _Timer, timer_heartbeat}, State) ->
    util:cancel_timer(get(timer_heartbeat)),
    Timer = erlang:start_timer(?HEART_INTERVAL, self(), timer_heartbeat),
    put(timer_heartbeat, Timer),
    cli_simu_proto:pack_send(10006, []),
    %% 心跳时顺便检查是否死亡状态（避免非正常情况未复活）
    % ?INFO_TTY("[~w] send heartbeat 10006", [State#cli_role.id]),
    case State of
        CliRole = #cli_role{id = RoleId, hp = Hp} when RoleId > 0 andalso Hp =< 0 ->
            cli_battle:do_revive(CliRole, 3);
        _ ->
            ignore
    end,
    {noreply, State};

%% 收取到协议通信数据，调用接口进行处理
do_handle_info({recv_packet, Cmd, Data}, State = #cli_role{conn_pid = ConnPid}) ->
    Ret =
    case cli_simu_proto:proto_handler(Cmd) of
        {ok, Handler} ->
            Result =
            case Handler:handle(Cmd, State, Data) of
                ok -> ok;
                ignore -> ok;
                {ok, NewState = #cli_role{}} -> NewState;
                NewState = #cli_role{} -> NewState;
                _ -> ok
            end,
            case Result of
                State ->
                    {noreply, State};
                #cli_role{} ->
                    ets:insert(?CLI_ROLE, Result),
                    {noreply, Result};
                _ ->
                    {noreply, State}
            end;
        {error, Error} ->
            ?ERR("Handler error:~p", [Error]),
            {noreply, State}
    end,
    ConnPid ! get_package,
    Ret;

%% 传送到指定场景区域（使用GM命令切换，启动测试时初始设置场景使用）
do_handle_info({set_scene, SceneInfo}, State = #cli_role{id = _RoleId, scene_id = _SceneId, socket = Socket}) ->
    ?INFO_TTY("[~w] now scene:~w, gm set scene:~w", [_RoleId, _SceneId, SceneInfo]), 
    GmTokens =
    case SceneInfo of
        {SceneId, X, Y, Offset} when is_integer(SceneId) andalso Offset > 0 ->
            NewX = X + urand:rand(-Offset, Offset),
            NewY = Y + urand:rand(-Offset, Offset),
            ["scene", SceneId, NewX, NewY];
        {SceneId, X, Y} when is_integer(SceneId) andalso is_integer(X) andalso is_integer(Y) ->
            ["scene", SceneId, X, Y];
        SceneId when is_integer(SceneId) ->
            ["scene", SceneId];
        {SceneId, random} when is_integer(SceneId) ->
            case data_scene:get(SceneId) of
                #ets_scene{width = Width, height = Height} ->
                    ["scene", urand:rand(1, Width), urand:rand(1, Height)];
                _ ->
                    ignore
            end;
        _ -> ignore
    end,
    case GmTokens of
        ignore -> ignore;
        _ ->
            cli_gm:send_gm(Socket, GmTokens),
            cli_scene:async_load_scene()
    end,
    {noreply, State};

%% 定期自动行为触发
do_handle_info({timeout, _Timer, auto_do}, State) ->
    OldTimer = get({timer, auto_do}),
    util:cancel_timer(OldTimer),
    Timer = erlang:start_timer(urand:rand(300, 500), self(), auto_do),
    put({timer, auto_do}, Timer),
    case cli_simu_auto:auto_do(State) of
        NewState = #cli_role{} ->
            {noreply, NewState};
        _ ->
            {noreply, State}
    end;

%% 通过GM命令检查获取装备
do_handle_info({timeout, _Timer, get_equips}, State = #cli_role{socket = Socket}) ->
    cli_gm:get_equips(Socket),
    {noreply, State};

%% 请求自定义GM命令
do_handle_info({gm, GmStr}, State = #cli_role{socket = Socket}) ->
    cli_gm:send_gm(Socket, GmStr),
    {noreply, State};

do_handle_info({'EXIT', Pid, Why}, State = #cli_role{conn_pid = Pid}) ->
    {stop, Why, State};

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

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

% %% 首个握手数据包
% first_package() ->
%     String = <<"jmfuz-game-MIqkY">>,
%     Bin = pt:write_string(String),
%     {ok, BinData} = pack(1000, Bin),
%     BinData.

