%%%------------------------------------
%%% @Module  : mod_chat_agent
%%% @Author  : xyao
%%% @Created : 2012.07.25
%%% @Description: 聊天管理
%%%------------------------------------
-module(mod_chat_agent).
-behaviour(gen_server).
-export([start_link/0,
        get_online_num/0,
        send_msg/1,
        insert/1,
        lookup/1,
        match/3,
        match/2,
        update/2,
        delete/1,
        get_online_player/0,    %% 获得在线玩家
        handle_event/2,
        handle_event/1,
        repair_online_num/0,    %% 修复在线人数统计
        get_can_team_role/8,
        send_msg_to_kf/3,
        update_rank_info/1
    ]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).

-include("unite.hrl").
-include("chat.hrl").
-include("figure.hrl").
-include("rec_event.hrl").
-include("def_event.hrl").
-include("server.hrl").
-include("common.hrl").

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

get_online_num()->
    gen_server:call(?MODULE, {get_online_num, all}).

get_online_player() ->
    gen_server:call(?MODULE, {get_online_player}).

repair_online_num()->
    gen_server:cast(?MODULE, {repair_online_num}).

send_msg_to_kf(SendType, RoleId, Data) ->
    gen_server:cast(?MODULE, {send_msg_to_kf, SendType, RoleId, Data}).

%% 更新top10排行榜信息
update_rank_info(RankInfo) ->
    gen_server:cast(?MODULE, {update_rank_info, RankInfo}).
%% ----------------------------------------------------------------------
%% @doc 发送不同类型的消息
-spec send_msg(MsgTypeValueList) -> ok when
    MsgTypeValueList :: list().     %% 类型值组合的元组列表，元组类型
%% {uid, Id, Bin} | {all, Bin} | {all, tv, Bin} | {all, unmask, Bin}
%% {all, MinLv, MaxLv, Bin} | {open_call, Bin} |
%% {scene, SceneId, Bin} | {scene, SceneId, CopyId, Bin} | {guild, GuildId, Bin}
%% {realm, Realm, Bin} | {new_realm, NewRealm, Bin} | {team, TeamId, Bin} | {group, Group, Bin}
%% {career, Career, Bin}
%% ----------------------------------------------------------------------
send_msg(MsgTypeValueList) ->
    gen_server:cast(?MODULE, {send_msg, MsgTypeValueList}).

%% 插入数据
insert(EtsUnite) ->
    gen_server:cast(?MODULE, {insert, EtsUnite}).

%% ----------------------------------------------------------------------
%% @doc 更新操作
-spec update(Id, AttrKeyValueList) -> ok when
    Id               :: integer(),  %% 玩家id
    AttrKeyValueList :: list().     %% 即 [{Key1, Value1}, {..}],
%%  Key = name|gm|vip|scene|copy_id|team_id
%%        |sex|realm|career|guild_id|guild_name
%%        |guild_position|group|lv|image|appointment
%%        |combat_power|talk_accept|picture|picture_ver
%% ----------------------------------------------------------------------
update(Id, AttrKeyValueList) ->
    gen_server:cast(?MODULE, {update, Id, AttrKeyValueList}).

%% 删除数据
delete(Id) ->
    gen_server:cast(?MODULE, {delete, Id}).

%% 查找数据
lookup(Id) ->
    gen_server:call(?MODULE, {lookup, Id}).

%% 多条件查找
%%　返回值由使用者自己定义
match(Type, Info, ResultForm) ->
    gen_server:call(?MODULE, {match, Type, Info, ResultForm}).

match(Type, Info) ->
    gen_server:call(?MODULE, {match, Type, Info, all}).

handle_event(PS, #event_callback{type_id = ?EVENT_LV_UP}) ->
    %% 更新公共线的等级信息
    mod_chat_agent:update(PS#player_status.id, [{lv, PS#player_status.figure#figure.lv}]),
    {ok, PS};
%% 处理回调事件
handle_event(PS, #event_callback{type_id = ?EVENT_COMBAT_POWER}) ->
    mod_chat_agent:update(PS#player_status.id, [{power, PS#player_status.combat_power}]),
    {ok, PS};
handle_event(PS, #event_callback{}) ->
    {ok, PS}.

handle_event(#event_callback{type_id = ?EVENT_PICTURE_CHANGE, data = EventData}) ->
    #callback_picture{role_id = RoleId, picture = Picture, picture_ver = PictureVer} = EventData,
    mod_chat_agent:update(RoleId, [{picture, Picture}, {picture_ver, PictureVer}]),
    ok;
handle_event(#event_callback{}) ->
    ok.

get_can_team_role(RoleId, Sex, ActivityId, SubTypeId, Position, Type, Scene, IdList) ->
    gen_server:cast(?MODULE, {get_can_team_role, RoleId, Sex, ActivityId, SubTypeId, Position, Type, Scene, IdList}).


init([]) ->
    process_flag(trap_exit, true),
    erlang:send_after(3000, self(), {init_rank_info}), %% 三秒之后将排行榜部分数据加载进来
    {ok, []}.

%% 返回真是在线玩家和离线挂机玩家人数 {OnlineNum, OnhookNum}
handle_call({get_online_num, all}, _From, State) ->
    case get(online_num) of
        undefined -> OnlineNum = 0;
        OnlineNum -> skip
    end,
    case get(onhook_online_num) of
        undefined -> OnhookNum = 0;
        OnhookNum -> skip
    end,
    {reply, {OnlineNum, OnhookNum}, State};

%% 获得在线玩家
handle_call({get_online_player}, _From, State) ->
    Data = get(),
    Reply = [Player || {_, Player} <- Data, is_record(Player, ets_unite)],
    {reply, Reply, State};

%% 查找数据
handle_call({lookup, Id}, _From, State) ->
    Data1 = case get(Id) of
        undefined ->
            [];
        Data ->
            [Data]
    end,
    {reply, Data1, State};

%% 多条件查找
%%  返回值由使用者自己定义
handle_call({match, Type, Info, ResultForm}, _From, State) ->
    Data = do_match(Type, Info, ResultForm),
    {reply, Data, State};

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

%% 插入聊天进程
handle_cast({insert, EtsUnite}, State) ->
    case get(EtsUnite#ets_unite.id) of
        undefined ->
            put(EtsUnite#ets_unite.id, EtsUnite),
            %% 记录在线人数
            increasement_online_num();
        #ets_unite{online = Online} ->
            if
                Online == ?ONLINE_OFF_HOSTING ->
                    %% 离线挂机人数-1,在线人数+1
                    change_online_onhook(-1),
                    increasement_online_num();
                true -> %% 其他情况,默认应该是不加在线人数
                    skip
            end,
            put(EtsUnite#ets_unite.id, EtsUnite)
    end,
    {noreply, State};

%% 更新进程字典
handle_cast({update, Id, AttrKeyValueList}, State) ->
    case get(Id) of
        undefined ->
            skip;
        Data ->
            NewData = do_update(AttrKeyValueList, Data, Id),
            put(Id, NewData)
    end,
    {noreply, State};

%% 玩家下线操作时触发
handle_cast({delete, Id}, State) ->
    case get(Id) of
        undefined -> skip;
        EtsUnite ->
            erase(Id),
            if
                %% 判断是否已经触发过减在线人数
                EtsUnite#ets_unite.online == ?ONLINE_OFF_HOSTING -> %% 离线挂机人数-1
                    change_online_onhook(-1);
                true -> %% 其他情况清理
                    reduce_online_num()
            end
    end,
    {noreply, State};

%% 发送不同类型的消息
handle_cast({send_msg, MsgTypeValueList}, State) ->
    do_send_msg(MsgTypeValueList),
    {noreply, State};

%% 修复在线人数
handle_cast({repair_online_num}, State)->
%%    Data = get(),
%%    RoleInfo = [Player || {_, Player} <- Data, is_record(Player, ets_unite)],
%%    F = fun(#ets_unite{online = Online}, {OnlineNum, OnhookNum})->
%%            if
%%                Online == ?ONLINE_OFF_ONHOOK -> {OnlineNum, OnhookNum+1};
%%                true -> {OnlineNum+1, OnhookNum}
%%            end
%%    end,
%%    {OnlineCount, OnlineOnhookCount} = lists:foldl(F, {0, 0}, RoleInfo),
%%    put(online_num, OnlineCount),
%%    put(onhook_online_num, OnlineOnhookCount),

%% 引用旧项目从数据库拿数据
%% 更新聊天中的在线标识
    F = fun([Id], OnlineFlag) ->
            case get(Id) of
                undefined -> skip;
                EtsUnite -> put(EtsUnite#ets_unite.id, EtsUnite#ets_unite{online = OnlineFlag})
            end,
            OnlineFlag
    end,
    %% 查询数据
    SeSQL = <<"select id from `player_login` where `online_flag`=1">>,
    case db:get_all(SeSQL) of
        OnlineList when is_list(OnlineList)->
            lists:foldl(F, 1, OnlineList),
            OnlineCount = length(OnlineList),
            put(online_num, OnlineCount);
        _ -> skip
    end,
    SeSQL1 = <<"select id from `player_login` where `online_flag`=2">>,
    case db:get_all(SeSQL1) of
        OnlineOnhookList when is_list(OnlineOnhookList)->
            lists:foldl(F, 2, OnlineOnhookList),
            OnlineOnhookCount = length(OnlineOnhookList),
            put(onhook_online_num, OnlineOnhookCount);
        _ -> skip
    end,
    {noreply, State};


%% 获得可加入队伍的玩家
%% 1：附近玩家（每次请求只会发N条过来） 2：帮派成员（全量列表） 3：好友（全量列表）
handle_cast({get_can_team_role, RoleId, _Sex, ActivityId, SubTypeId, Position, Type, Scene, IdList}, State) ->
    if
        Type =:= 1 ->
            AllRoles = get(),
            {NewPosition, List} = get_team_nearby_role(RoleId, Scene, AllRoles, max(1, Position));
        true  ->
            F = fun(Id, Temp) ->
                case get(Id) of
                    #ets_unite{online = ?ONLINE_ON, team_id = 0} ->
                        Figure = lib_role:get_role_figure(Id),
                        [{Figure, 0, 0}|Temp];
                    _ -> 
                        Temp
                end 
            end,
            NewPosition = Position,
            List = lists:foldl(F, [], IdList)
    end,
    {ok, BinData} = pt_240:write(24053, [List, ActivityId, SubTypeId, NewPosition, Type]),
    lib_server_send:send_to_uid(RoleId, BinData),
    {noreply, State};

%% 给单个玩家发送消息
handle_cast({send_msg_to_kf, SendType, RoleId, Data}, State) ->
    case get(rank_info) of
        [LvRank, CombatRank] ->
            skip;
        _ ->
            LvRank = [], CombatRank = []
    end,
    case lists:keyfind(RoleId, 1, LvRank) of
        {RoleId, RoleLvRank} -> skip;
        _ -> RoleLvRank = 0
    end,
    case lists:keyfind(RoleId, 1, CombatRank) of
        {RoleId, RoleCombatRank} -> skip;
        _ -> RoleCombatRank = 0
    end,
    case Data of
        [Channel, SerId, SerNum, ServerName, Id, Figure, MsgSend, NewArgs, ArgsResult, NowTime] ->
            {ok, BinData} = pt_110:write(11001, [Channel, SerId, SerNum, ServerName, Id, Figure, MsgSend, NewArgs, ArgsResult, NowTime, RoleLvRank, RoleCombatRank]);
        [Channel, SerId, SerNum, ServerName, PlayerList, ClientVer, ClientAutoId, ReceiveId, FinVoiceText, VoiceMsgTime, NowTime] ->
            {ok, BinData} = pt_110:write(11003, [Channel, SerId, SerNum, ServerName, PlayerList, ClientVer, ClientAutoId, ReceiveId, FinVoiceText, VoiceMsgTime, NowTime, RoleLvRank, RoleCombatRank]);
        _ -> Channel = 0, BinData = 0
    end,
    case SendType of
        all when BinData =/= 0 -> % 给跨服玩家推送信息
            ServerId = config:get_server_id(),
            clusters_node:apply_cast(lib_chat, do_send_clusters_msg, [{{channel, Channel}, ServerId, BinData}]);
        self when BinData =/= 0 -> % 只发给自己
            lib_server_send:send_to_uid(RoleId, BinData);
        _ -> ?ERR("handle_cast data_error:~w", [Data])
    end,
    {noreply, State};

%% 同步top10的等级和战力榜信息
handle_cast({update_rank_info, RankInfo}, State) ->
    put(rank_info,RankInfo),
    {noreply, State};

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

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

terminate(_Reason, _State) ->
    ok.

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

%% 内部函数:查询
do_match(Type, Info, ResultForm) ->
    %% 获取所有的数据
    Data = get(),
    %% 查找
    case Type of
        match_name ->   %% 根据名字查找玩家信息
            [PlayerName] = Info,
            F = fun
                ({_K, #ets_unite{figure = Figure}}) ->
                    Figure#figure.name =:= PlayerName;
                (_) ->
                    false
            end,
            D = lists:filter(F, Data),
            case D of
                [{_, Player}] -> [util:record_return_form(Player, ResultForm)];
                _ -> []
            end
    end.

increasement_online_num() ->
    case get(online_num) of
        undefined -> put(online_num, 1);
        Num -> put(online_num, Num+1)
    end.

reduce_online_num() ->
    case get(online_num) of
        undefined -> skip;
        Num -> put(online_num, max(0, Num-1))
    end.

%% 记录离线挂机人数
change_online_onhook(N)->
    case get(onhook_online_num) of
        undefined -> put(onhook_online_num, max(0, N));
        Num -> put(onhook_online_num, max(Num+N, 0))
    end.

%% 更新数据
do_update([], Data, _Id) -> Data;
do_update([T|L], #ets_unite{figure = Figure}=Data, Id) ->
    case T of
        {lv, Lv} -> NewData = Data#ets_unite{figure = Figure#figure{lv = Lv} };
        {picture, Picture} -> NewData = Data#ets_unite{figure = Figure#figure{picture = Picture} };
        {picture_ver, PictureVer} -> NewData = Data#ets_unite{figure = Figure#figure{picture_ver = PictureVer} };
        {guild_id, GuildId} -> NewData = Data#ets_unite{figure = Figure#figure{guild_id = GuildId}};
        {guild_name, GuildName} -> NewData = Data#ets_unite{figure = Figure#figure{guild_name = GuildName}};
        {scene, SceneId} -> NewData = Data#ets_unite{scene = SceneId};
        {scene_pool_id, ScenePoolId} -> NewData = Data#ets_unite{scene_pool_id = ScenePoolId};
        {copy_id, CopyId} -> NewData = Data#ets_unite{copy_id = CopyId};
        {online, Online} -> NewData = Data#ets_unite{online = Online};
        {online_flag, Online} ->
            if
                Data#ets_unite.online == Online -> NewData = Data;
                true ->
                    if
                        Online == ?ONLINE_OFF_HOSTING ->
                            %% 在线人数-1
                            reduce_online_num(),
                            %% 离线挂机人数+1
                            change_online_onhook(1),
                            %% 玩家这里的在线情况只保留0和1的情况
                            NewData = Data#ets_unite{online = Online};
                        true -> %% 其他情况就只标记玩家下线
                            NewData = Data#ets_unite{online = Online}
                    end
            end;
        {online_flag, Online, OldOnline} ->
            %% 玩家重连的情况下判断:玩家是否进入了离线挂机情况
            if
                Online == ?ONLINE_ON andalso OldOnline == ?ONLINE_OFF_HOSTING ->
                    %% 在线人数+1
                    increasement_online_num(),
                    %% 离线挂机人数-1
                    change_online_onhook(-1),
                    NewData =  Data#ets_unite{online = Online};
                true ->
                    NewData = Data#ets_unite{online = Online}
            end;
        {sid, Sid} ->
            NewData = Data#ets_unite{sid = Sid};
        {team_id, TeamId} ->
            NewData = Data#ets_unite{team_id = TeamId};
        {power, Power} ->
            NewData = Data#ets_unite{figure = Figure#figure{power = Power} };
%%        {ip, Ip} ->
%%            NewData = Data#ets_unite{ip = Ip};
        _  -> NewData = Data
    end,
    do_update(L, NewData, Id).


do_send_msg([]) -> ok;
do_send_msg([H|T]) ->
    case H of
        {all, Bin} ->
            Data = get(),
            F = fun(Player) ->
                    lib_server_send:send_to_sid(Player#ets_unite.sid, Bin)
            end,
            [F(Player) || {_, Player} <- Data, is_record(Player, ets_unite)];
        %% 广播
        {all, tv, Bin} ->
            Data = get(),
            F = fun(Player) ->
                    lib_server_send:send_to_sid(Player#ets_unite.sid, Bin)
            end,
            [F(Player) || {_, Player} <- Data, is_record(Player, ets_unite)];
        %% 无视玩家的消息屏蔽设置发送数据
        {all, unmask, Bin} ->
            Data = get(),
            [lib_server_send:send_to_sid(Player#ets_unite.sid, Bin) || {_, Player} <- Data, is_record(Player, ets_unite)];
        %% 等级段发送
        {all_lv, {MinLv, MaxLv}, Bin} ->
            Data = get(),
            [lib_server_send:send_to_sid(Player#ets_unite.sid, Bin) ||
                {_, Player} <- Data, Player#ets_unite.figure#figure.lv >= MinLv, Player#ets_unite.figure#figure.lv =< MaxLv];
        {lv_and_open_day, {MinLv, ConfigDay}, Bin} ->
            Data = get(),
            Openday = util:get_open_day(),
            if Openday < ConfigDay -> 
                    ok;
                true ->
                    [lib_server_send:send_to_sid(Player#ets_unite.sid, Bin) ||
                        {_, Player} <- Data, Player#ets_unite.figure#figure.lv >= MinLv]
            end;
        %% 单个排除
        {all_exclude, RoleIds, Bin} when is_integer(RoleIds)->
            Data = get(),
            [lib_server_send:send_to_sid(Player#ets_unite.sid, Bin) || {_, Player} <- Data, Player#ets_unite.id /= RoleIds];
        %% 多人排除
        {all_exclude, RoleIds, Bin} ->
            Data = get(),
            [lib_server_send:send_to_sid(Player#ets_unite.sid, Bin) || {_, Player} <- Data, lists:member(Player#ets_unite.id, RoleIds)];
        {scene, SceneId, Bin} ->
            Data = get(),
            [lib_server_send:send_to_sid(Player#ets_unite.sid, Bin) || {_, Player} <- Data, Player#ets_unite.scene == SceneId];
        {scene, SceneId, PoolId, Bin} ->
            Data = get(),
            [lib_server_send:send_to_sid(Player#ets_unite.sid, Bin)|| {_, Player = #ets_unite{scene = SceneIdX, scene_pool_id = PoolIdX}} <- Data,
                SceneIdX =:= SceneId andalso PoolIdX =:= PoolId];
%%        {scene, SceneId, CopyId, Bin} ->
%%            Data = get(),
%%            [lib_server_send:send_to_sid(Player#ets_unite.sid, Bin) || {_, Player} <- Data, Player#ets_unite.scene == SceneId, Player#ets_unite.copy_id == CopyId];
        {scene, SceneId, PoolId, CopyId, Bin} ->
            Data = get(),
            [lib_server_send:send_to_sid(Player#ets_unite.sid, Bin)|| {_, Player = #ets_unite{scene = SceneIdX, scene_pool_id = PoolIdX, copy_id = CopyIdX}} <- Data,
                SceneIdX =:= SceneId andalso PoolIdX =:= PoolId andalso CopyIdX =:= CopyId];
        {guild, GuildId, Bin} ->
            Data = get(),
            [lib_server_send:send_to_sid(Player#ets_unite.sid, Bin) || {_, Player} <- Data, Player#ets_unite.figure#figure.guild_id == GuildId];
        {realm, Realm, Bin} ->
            Data = get(),
            [lib_server_send:send_to_sid(Player#ets_unite.sid, Bin) || {_, Player} <- Data, Player#ets_unite.figure#figure.realm == Realm];
        {team, TeamId, Bin} ->
            Data = get(),
            [lib_server_send:send_to_sid(Player#ets_unite.sid, Bin) || {_, Player} <- Data, Player#ets_unite.team_id == TeamId];
        {group, Group, Bin} ->
            Data = get(),
            [lib_server_send:send_to_sid(Player#ets_unite.sid, Bin) || {_, Player} <- Data, Player#ets_unite.group == Group];
        {career, Career, Bin} ->
            Data = get(),
            [lib_server_send:send_to_sid(Player#ets_unite.sid, Bin) || {_, Player} <- Data, Player#ets_unite.figure#figure.career == Career];
        {all_guild, Bin} ->
            Data = get(),
            [lib_server_send:send_to_sid(Player#ets_unite.sid, Bin) || {_, Player} <- Data, Player#ets_unite.figure#figure.guild_id > 0];
        {specify_uids, UIds, Bin} ->
            Data = get(),
            [lib_server_send:send_to_sid(Player#ets_unite.sid, Bin) || {_, Player} <- Data, lists:member(Player#ets_unite.id, UIds)];
        _ -> ok
    end,
    do_send_msg(T).





%% 选择同场景的在线玩家
get_team_nearby_role(RoleId, Scene, AllRoles, Position) ->
    {Count, RoleList} = get_can_team_role(RoleId, Scene, AllRoles, 0, []),
    SelectNum = 5,
    {NewPosition, SelectList} = case Count =< SelectNum of
        true -> %% 人数很少
            {Position, RoleList};
        false ->
            RoleList2 = lists:keysort(#ets_unite.id, RoleList),
            case Count >= (Position + SelectNum) of
                true -> 
                    {Position + SelectNum, lists:sublist(RoleList2, Position, SelectNum)};
                false ->
                    StartPos = max(1, Position - SelectNum),
                    {Position, lists:sublist(RoleList2, StartPos, SelectNum)}
            end
    end,
    %% 不处理战力更新回调了,这里的操作肯定比战力变化回调操作少
    % SelectList2 = [{Figure, 0, 0} || #ets_unite{figure = Figure} <- SelectList],
    SelectList2 = [begin Figure = lib_role:get_role_figure(RoleIdX), {Figure, 0, 0} end || #ets_unite{id = RoleIdX} <- SelectList],
    {NewPosition, SelectList2}.
    
get_can_team_role(_RoleId, _Scene, [], Count, List) -> 
    {Count, List};
get_can_team_role(RoleId, Scene, [H | AllRoles], Count, List) ->
    {NewCount, NewList} = case H of
        {
            RoleId2, Role = #ets_unite{online = ?ONLINE_ON, team_id = 0, scene = Scene}
        } when RoleId =/= RoleId2 ->
            {Count + 1, [Role | List]};
        _ ->
            {Count, List}
    end,
    get_can_team_role(RoleId, Scene, AllRoles, NewCount, NewList).
