%%% -------------------------------------------------------
%%% @author huangyongxing@yeah.net
%%% @doc
%%% 游戏初始连接后登录验证及创角等管理
%%% @end
%%% -------------------------------------------------------
-module(pp_login).
-export([
        handle/3
        ,check_heart_time/2
        ,validate_name/1
    ]).
-include("common.hrl").
-include("server.hrl").
-include("unite.hrl").
-include("predefine.hrl").
-include("figure.hrl").
-include("conn.hrl").
-include("login.hrl").
-include("def_fun.hrl").

%% 自定义用户数据（保存在#conn.other_data）
-record(account, {
        accid  = 0          % 账户accid
        ,accname = ""       % 账户accname
        ,ids = []           % 记录帐号id列表
    }).

%% 帐号登陆验证
handle(10000, Conn = #conn{socket = Socket, account = Account}, Data) when Account =:= <<>> ->
    ?PRINT("10000 ~p~n", [Data]),
    try
        case is_bad_pass(Data) of
            true ->
                [AccId, AccName | _] = Data,
                %% 取选择最小人数的职业来返回
                Career = 1,           % urand:rand(1, 2),
                RoleList = lib_login:get_role_list(AccId, AccName),
                Now = utime:unixtime(ms),
                {ok, BinData} = pt_100:write(10000, [Career, Now, RoleList, util:get_real_open_time()]),
                lib_server_send:send_one(Socket, BinData),
                Ids = [Id||[_, #figure{id = Id}|_ ] <- RoleList],
                NewConn = Conn#conn{account = unicode:characters_to_binary(AccName), other_data = #account{accid = AccId, accname = AccName, ids = Ids}},
                {ok, NewConn};
            false ->
                {ok, Conn}
        end
    catch
        ?EXCEPTION(_Class, _Reason, _Stack) ->
            ?DEBUG("Login Error:~w, Reason:{~p,~p}~n  ~p~n", [Data, _Class, _Reason, ?GET_STACK(_Stack)]),
            {ok, Conn}
    end;

%%% 创建角色
handle(10003, Conn = #conn{account = Account, other_data = OtherData, socket = Socket, ip = IP},
    [Realm, Career, Sex, Name, Source, IsDefault, IsSimulator, IsChangeName, TaGuestId]) when is_binary(Name), Account =/= <<>> ->
    #account{accid = Accid, accname = Accname} = OtherData,
    case check_ip_limit(IP) of
        true ->
            case validate_name(Name) of  %% 角色名合法性检测
                {false, Msg} ->
                    {ok, BinData} = pt_100:write(10003, [Msg, 0]),
                    lib_server_send:send_one(Socket, BinData);
                true ->
                    case catch lib_login:create_role(Accid, Accname, Name, Realm, Career, Sex, IP, Source, IsDefault) of
                        0 ->
                            %% 角色创建失败
                            {ok, BinData} = pt_100:write(10003, [0, 0]),
                            lib_server_send:send_one(Socket, BinData);
                        Id when is_integer(Id) ->
                            %% 创建角色成功
                            {ok, BinData} = pt_100:write(10003, [1, Id]),
                            lib_server_send:send_one(Socket, BinData),
                            IsChooseCareer = ?IF(IsDefault == 0, 0, 1),
                            %% 创角事件上报
                            ta_agent_fire:create_role([Id, IP, Account, Name, IsSimulator, IsChooseCareer, IsChangeName, Source, Career, TaGuestId]),
                            NewConn = Conn#conn{other_data = OtherData#account{ids = [Id | OtherData#account.ids ]}},
                            {ok, NewConn};
                        Error ->
                            %% 角色创建失败
                            {ok, BinData} = pt_100:write(10003, [0, 0]),
                            lib_server_send:send_one(Socket, BinData),
                            ?ERR("pp_login 10003 create_role error accid(~w), accname(~ts), ~p", [Accid, Accname, Error]),
                            ok
                    end
            end;
        false ->
            {ok, BinData} = pt_100:write(10003, [2, 0]),
            lib_server_send:send_one(Socket, BinData)
    end;

%% 角色登录
handle(10004, Conn = #conn{account = Account, socket = Socket, other_data = OtherData, ip = Ip},
    [Id, _ServerName, Time, Ticket, NetworkType, LoginSource, TaGuestId]) when Account =/= <<>> ->
    #account{accname = AccName, accid = AccId, ids = Ids} = OtherData,
    % ?INFO_TTY("10004 ~p~n", [[Id, Time, Ticket]]),
    case lists:member(Id, Ids) of
        false ->
            {ok, BinData} = pt_590:write(59004, 9),
            lib_server_send:send_one(Socket, BinData),
            ok;
        true ->
            case util:check_char_encrypt(Id, Time, Ticket) of
                true ->
                    case mod_ban:check(Id, Ip) of
                        passed -> %% 通过验证
                            LoginParams = #login_params{
                                id = Id, accid = AccId, accname = AccName, ip = Ip,
                                socket = Socket, ids = Ids, network_type = NetworkType,
                                login_source = LoginSource, ta_guest_id = TaGuestId
                            },
                            case mod_login:login(LoginParams) of
                                {error, MLR} ->
                                    %% 告诉玩家登陆失败
                                    {ok, BinData} = pt_590:write(59004, MLR),
                                    lib_server_send:send_one(Socket, BinData),
                                    {ok, Conn};
                                {ok, Pid} ->
                                    %%告诉玩家登陆成功
                                    {ok, BinData} = pt_100:write(10004, 1),
                                    lib_server_send:send_one(Socket, BinData),
                                    %% 完成后进入游戏逻辑处理
                                    % io:format("Time:~p,RoleLogin:~p~n",[utime:unixtime(),Id]),
                                    {ok, Conn#conn{pid_msg_handler = Pid}}
                            end;
                        login_more ->
                            {ok, BinData} = pt_590:write(59004, 2),
                            lib_server_send:send_one(Socket, BinData),
                            {ok, Conn};
                        forbidall -> %% 所有账号登陆都被禁止（服务器正在维护中）
                            {ok, BinData} = pt_590:write(59004, 10),
                            lib_server_send:send_one(Socket, BinData),
                            {ok, Conn};
                        forbidip ->  %% IP被封
                            {ok, BinData} = pt_590:write(59004, 3),
                            lib_server_send:send_one(Socket, BinData),
                            {ok, Conn};
                        _ -> %% 未定义
                            {ok, BinData} = pt_590:write(59004, 9),
                            lib_server_send:send_one(Socket, BinData),
                            {ok, Conn}
                    end;
                false ->
                    {ok, BinData} = pt_590:write(59004, 9),
                    lib_server_send:send_one(Socket, BinData),
                    {ok, Conn}
            end
    end;

%% 进入游戏后心跳包
handle(10006, Conn = #conn{socket = Socket, last_heartbeat = LastHeartbeat, bad_heartbeat = BadHeartbeat}, _R) ->
    NowTime = utime:unixtime(ms),
    NewConn =
    case NowTime - LastHeartbeat < 4800 of
        true when BadHeartbeat >= 9 ->      % 10次快速心跳，踢下线
            {ok, BinData2} = pt_590:write(59004, 4),
            lib_server_send:send_one(Socket, BinData2),
            self() ! close_conn,
            Conn#conn{last_heartbeat = NowTime, bad_heartbeat = BadHeartbeat + 1};
        true ->
            Conn#conn{last_heartbeat = NowTime, bad_heartbeat = BadHeartbeat + 1};
        false ->
            NewBadHeartbeat = max(BadHeartbeat - 1, 0),
            Conn#conn{last_heartbeat = NowTime, bad_heartbeat = NewBadHeartbeat}
    end,
    {ok, BinData} = pt_100:write(10006, []),
    lib_server_send:send_one(Socket, BinData),
    {ok, NewConn};


handle(_Cmd, _Status, _Data) ->
    %?ERR("CMD ~w~n", [{_Cmd, _Status, _Data}]),
    {error, <<"pp_login no match">>}.

%%% ------------ 私有函数 --------------
%% 通行证验证
is_bad_pass([Accid, Accname, Tstamp, TK]) ->
    TICKET = config:get_ticket(),
    Hex = util:md5(lists:concat([Accid, Accname, Tstamp, TICKET])),
    TimeDiff = utime:unixtime() - Tstamp,
    %% token生成机器（注册系统）的时间不能超过服务器10分钟
    %% token生成时间不能超过12小时（等前端改好后，后续再缩短）
    (TimeDiff >= -600 andalso TimeDiff < 43200 andalso Hex =:= TK) orelse
    config:get_server_debug().

%% 角色名合法性检测
validate_name(Name) ->
    case lib_word:validate_role_name(Name) of
        true ->
            true;
        {false, width} ->
            {false, 5};
        {false, invalid_utf8} ->
            {false, 4};
        {false, role_exist} ->
            {false, 3};
        {false, keyword} ->
            {false, 7};
        _ ->
            {false, 0}
    end.

%% 检查心跳包发送的频率
check_heart_time(NowTime, LimTime) ->
    case get("pp_base_heartbeat_last_time") of
        undefined->
            put("pp_base_heartbeat_last_time", {0, 0}),
            false;
        {T, _} ->
            NowTime - T > LimTime
    end.

%% 检查IP注册量限制数量50,需要添加请直接在代码里修改
%% return true 通过,false 不通过
check_ip_limit(Ip)->
    TICKET = config:get_ticket(),
    case TICKET =:= ?INTERNAL_TICKET of
        true  -> true;
        false ->
            case mod_ban:check_bai(Ip) of
                true -> true;
                _ ->
                    SQL  = io_lib:format("SELECT COUNT(*) FROM player_login WHERE reg_ip = '~ts'", [util:ip2bin(Ip)]),
                    case db:get_one(SQL) of
                        null ->
                            true;
                        Times ->
                            case Times >= 5000 of
                                true  -> false;
                                false -> true
                            end
                    end
            end
    end.
