%%%------------------------------------
%%% module  : mod_server
%%% @author  : xyao
%%% email   : jiexiaowen@gmail.com
%%% Created : 2011.06.14
%%% description: 角色处理
%%%------------------------------------
-module(mod_server).
-behaviour(gen_server).
-export([
        start/1
        ,start_link/1
        ,re_login/6
        ,stop/1
        ,cast_stop/2
        ,server_stop/1
        ,online_flag/1
        ,do_return_value/2
        ,stop_socket/2
    ]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
%% only for async fake protocol (server internal call)
-export([
        after_proto/3
    ]).
-include("server.hrl").
-include("common.hrl").
-include("attr.hrl").
-include("team.hrl").
-include("figure.hrl").
-include("predefine.hrl").
-include("scene.hrl").
-include("def_event.hrl").
-include("errcode.hrl").

%% 开始
start(Args) ->
    SpawnOpts = [
        {spawn_opt, [{fullsweep_after, 128}]}
    ],
    gen_server:start(?MODULE, Args, SpawnOpts).

start_link(Args) ->
    SpawnOpts = [
        {spawn_opt, [{fullsweep_after, 128}]}
    ],
    gen_server:start_link(?MODULE, Args, SpawnOpts).

init(Args) ->
    %% process_flag(priority, max),
    PS = mod_login:server_login(Args),
    % util:send_after([], 60*1000, self(), check_memory),
    % 上线上传防沉迷数据
    % mod_prevent_addiction:upload_prevent_addiction_data(PS, 1),
    {ok, PS}.

re_login(Pid, Socket, Ip, ConnPid, LoginSource, TaGuestId) ->
    gen_server:call(Pid, {re_login, Socket, Ip, ConnPid, LoginSource, TaGuestId}).

%% 停止本游戏进程
stop(Pid) ->
    catch gen:call(Pid, '$gen_call', stop).

%% 特殊的异步停止玩家进程操作，通常不要使用
%% （一般使用stop/1，阻塞执行，避免并发调用很多进程退出，IO负载过高）
%% 主要由场景使用，避免阻塞场景运转（场景负载较高）
cast_stop(Pid, LogoutType) ->
    gen_server:cast(Pid, {stop, LogoutType}).

stop_socket(Pid, Socket)->
    catch gen:call(Pid, '$gen_call', {stop, Socket}).

%% 停服踢出玩家

%% 服务器停止
server_stop(Pid) ->
    catch gen:call(Pid, '$gen_call', server_stop).

%% 获取玩家的离线状态
online_flag(Pid)->
    gen_server:call(Pid, 'online_flag').

%% 游戏进程死掉修改状态(非正常掉线)
terminate(_Reason, _Status) ->
    handle_offline(_Reason, _Status),
    ok.

code_change(_oldvsn, Status, _extra) ->
    {ok, Status}.

-ifdef(DEV_SERVER).
-define(REDS_DIFF,   1200000).
-define(MEM_MAX,    15000000).
-define(MEM_DIFF,   12000000).
handle_call(Req, From, Status) ->
    dev_tool:func_checker(
        undefined,
        fun(ReqX, FromX, StatusX) ->
                sub_handle_call(ReqX, FromX, StatusX)
        end,
        [Req, From, Status],
        [{reds_diff, ?REDS_DIFF}, {mem_max, ?MEM_MAX}, {mem_diff, ?MEM_DIFF}]
    ).
handle_cast(Msg, Status) ->
    RedsDiffLimit =
    case Msg of
        {'SOCKET_EVENT', _FromPid, _ProtoMod = pt_111, _Mod = pp_gm, _Cmd, _Bin} ->
            %% GM命令，给予30倍的执行时间
            30 * ?REDS_DIFF;
        {apply_cast, _CastType, lib_player_event, dispatch, _Args} ->
            %% 由于事件派发包含很多不同模块的操作，个别情形下reductions非常大，
            %% 无法给出合理的评估，所以这里不记录，
            %% 在lib_player_event:dispatch/3接口中针对每个事件回调单独做记录
            false;
        _ ->
            ?REDS_DIFF
    end,
    case RedsDiffLimit of
        false ->
            sub_handle_cast(Msg, Status);
        _ ->
            dev_tool:func_checker(
                undefined,
                fun(MsgX, StatusX) ->
                        sub_handle_cast(MsgX, StatusX)
                end,
                [Msg, Status],
                [{reds_diff, RedsDiffLimit}, {mem_max, ?MEM_MAX}, {mem_diff, ?MEM_DIFF}]
            )
    end.
handle_info(Info, Status) ->
    dev_tool:func_checker(
        undefined,
        fun(InfoX, StatusX) ->
                sub_handle_info(InfoX, StatusX)
        end,
        [Info, Status],
        [{reds_diff, ?REDS_DIFF}, {mem_max, ?MEM_MAX}, {mem_diff, ?MEM_DIFF}]
    ).
-else.
handle_call(Req, From, Status) ->
    sub_handle_call(Req, From, Status).
handle_cast(Msg, Status) ->
    sub_handle_cast(Msg, Status).
handle_info(Info, Status) ->
    sub_handle_info(Info, Status).
-endif.

sub_handle_cast({'SOCKET_EVENT', FromPid, ProtoMod, Mod, Cmd, Bin}, Status) ->
    ProtoRet = protocol_routing:do_routing(ProtoMod, Mod, Cmd, Bin, Status),
    {HandleRet, Player1} = after_proto(Cmd, Status, ProtoRet),
    lib_player:log_operation_combat_power(Status, Player1, Cmd div 100, Cmd, ""),
    %lib_misc:print_in(Cmd,Bin,Status),
    FromPid ! {read_next, HandleRet},
    Player2 = lib_player:check_log_behavior(Player1, Cmd),
    {noreply, Player2};

%% 停止游戏进程
sub_handle_cast({stop, LogoutType}, Status) ->
    case catch mod_login:logout(Status, LogoutType) of
        {'EXIT', _Reason} ->
            catch ?ERR("mod_server_logout ERROR== LINE:~p Reason=~p~n", [?LINE, _Reason]);
        _ -> skip
    end,
    {stop, normal, Status};

%% handle_cast信息处理
sub_handle_cast(Event, Status) ->
    misc:monitor_pid(handle_cast, Event),
    mod_server_cast:handle_cast(Event, Status).

%% 玩家在线的状态
sub_handle_call('online_flag', _From, Status) ->
    {reply, Status#player_status.online, Status};

%% 重新登陆游戏进程
sub_handle_call({re_login, Socket, Ip, ConnPid, LoginSource, TaGuestId}, _From, Status) ->
    NewStatus = mod_login:re_login(Status, Socket, Ip, ConnPid, LoginSource, TaGuestId),
    {reply, true, NewStatus};

%% 停止游戏进程
sub_handle_call(stop, _From, Status) ->
    case catch mod_login:logout(Status, ?LOGOUT_LOG_NORMAL) of
        {'EXIT', _Reason} ->
            catch ?ERR("mod_server_login:logout ERROR== LINE:~p Reason=~p~n", [?LINE, _Reason]);
        _ -> skip
    end,
    {stop, normal, ok, Status};

%% 停服停止游戏进程
sub_handle_call(server_stop, _From, Status) ->
    case catch mod_login:logout(Status, ?LOGOUT_LOG_SERVER_STOP) of
        {'EXIT', _Reason} ->
            catch ?ERR("mod_server_login:logout ERROR== LINE:~p Reason=~p~n", [?LINE, _Reason]);
        _ -> skip
    end,
    {stop, normal, ok, Status};

%% 特殊处理stop
sub_handle_call({stop, Socket}, _From, Status) ->
    #player_status{socket = OldSocket} = Status,
    if
        Socket =:= OldSocket ->
            case catch mod_login:logout(Status, ?LOGOUT_LOG_NORMAL) of
                {'EXIT', _Reason} ->
                    catch ?ERR("mod_server_login:logout ERROR== LINE:~p Reason=~p~n", [?LINE, _Reason]);
                _ -> skip
            end,
            {stop, normal, Status};
        true ->
            {reply, ok, Status}
    end;

%% handle_call信息处理
sub_handle_call(Event, From, Status) ->
    misc:monitor_pid(handle_call, Event),
    mod_server_call:handle_call(Event, From, Status).

sub_handle_info({'EXIT', _, normal}=Info, Status) ->
    mod_server_info:handle_info(Info, Status);

%% Socket进程关闭
%% (注：客户端主动或网络导致的SOCKET断开首先执行此处，但是服务端报错进程异常中断不是走这里)
sub_handle_info({disconnect, Socket, From}, Status = #player_status{}) ->
    case sub_handle_call({'stop', Socket}, From, Status) of
        Stop = {stop, _, _} ->
            Stop;
        {reply, _, NewStatus} ->
            {noreply, NewStatus};
        Other ->
            Other
    end;

sub_handle_info(Info, Status) ->
    misc:monitor_pid(handle_info, Info),
    mod_server_info:handle_info(Info, Status).

%% 检测协议处理返回值，对数据进行适当加工
%% after_proto/3 -> {HandleRet :: ok | error, NewPlayer :: #player_status{}}.
after_proto(_Cmd, _Player, {ok, NewPlayer}) when is_record(NewPlayer, player_status) ->
    {ok, NewPlayer};
after_proto(_Cmd, _Player, {true, NewPlayer}) when is_record(NewPlayer, player_status) ->
    {ok, NewPlayer};
after_proto(_Cmd, _Player, {ok, Ret, NewPlayer}) when is_record(NewPlayer, player_status) ->
    do_return_value(Ret, NewPlayer),
    {ok, NewPlayer};
after_proto(_Cmd, Player, {ok, _NewPlayer}) ->
    catch ?ERR("badrecord: cmd:~p~n~p", [_Cmd, _NewPlayer]),
    {error, Player};
after_proto(_Cmd, Player, {ok, V, _NewPlayer}) ->
    catch ?ERR("syn badrecord: cmd:~p:state:~p~n~p", [_Cmd, V, _NewPlayer]),
    {error, Player};
after_proto(_Cmd, Player, ok) ->
    {ok, Player};
after_proto(_Cmd, Player, skip) ->
    {ok, Player};
after_proto(_Cmd, Player, ignore) ->
    {ok, Player};
%% 最后是发协议数据给前端；太多此类操作，就忽略掉吧
after_proto(_Cmd, Player, {send_data, _BinData}) ->
    {ok, Player};
after_proto(_Cmd, Player, unmatch_cmd) -> %% 读取协议出错
    {error, Player};
after_proto(_Cmd, Player, _Error) ->      %% 返回值格式错误
    catch ?DEBUG("error result of cmd[~w]:~n~p", [_Cmd, _Error]),
    {error, Player}.

%%
%% ------------------------私有函数------------------------
%%

%% 处理路由返回值
do_return_value(Value, Status) ->
    case Value of
        %% 更新战斗属性
        battle_attr ->
            mod_scene_agent:update(Status, [{battle_attr, Status#player_status.battle_attr}]);
        %% ...
        _ ->
            skip
    end.

%% 玩家进程退出
%% 退出所需要操作都写这里
handle_offline(normal, _Status) ->
    ok;
handle_offline(Reason, Status) ->
    PlayerId = Status#player_status.id,
    catch ?ERR("player error logout reason:~p,~n~p~n", [PlayerId, Reason]),
    %% 登出流程
    case catch mod_login:logout(Status, ?LOGOUT_LOG_ERROR) of
        {'EXIT', _Reason} ->
            catch ?ERR("mod_server_login:logout ERROR== LINE:~p Id=~p, Reason=~p~n", [?LINE, PlayerId, _Reason]);
        _ ->
            skip
    end,
    ok.
