%%% -------------------------------------------------------
%%% @author huangyongxing@yeah.net
%%% @doc
%%% 跨服公共接口
%%% @end
%%% @since 2017-03-03
%%% -------------------------------------------------------
-module(clusters_api).
-author("huangyongxing@yeah.net").
-export([
        send_to_uid/3
        ,send_to_uid/2
        ,get_server_info/1
        ,get_all_server_num/0
        ,check_node_status/1
        ,cast_node_restart/1
        ,async_apply_call/3
        ,async_apply_call_reply/3
        ,rpc_call_do_mfa/2
        ,rpc_cast_do_mfa/2
        ,add_node_db/1
        ,del_node_db/1
        ,center_connected/1
        ,center_disconnected/1
        ,node_connected/1
        ,node_disconnected/1
        ,role_node_disconnected/1
        ,is_connected_center/0              %% 查询游戏服是否和跨服中心连接
        ,get_cluster_type/0
        ,is_center/0
    ]).
-include("clusters.hrl").
-include("common.hrl").
-include("predefine.hrl").
-include("server.hrl").
-include("server_env.hrl").
-include("def_event.hrl").

%% -------------------------------------------
%% 一般接口
%% -------------------------------------------

%% 向指定节点角色发送二进制协议数据 (兼容旧项目接口)
send_to_uid(Node, Id, Bin) ->
    rpc:cast(Node, lib_server_send, send_to_uid, [Id, Bin]).

%% 注意，仅供跨服中心使用
send_to_uid(RoleId, Bin)->
    ServId = mod_player_create:get_serid_by_id(RoleId),
    clusters_center:apply_cast(ServId, lib_server_send, send_to_uid, [RoleId, Bin]).

%% 跨服中心获取游戏服信息
get_server_info(ServerId) ->
    case ets:lookup(?SERVER_INFO, ServerId) of
        [ServerInfo] -> ServerInfo;
        _ -> #server_info{}
    end.

%% 跨服中心获取所有游戏服数量
get_all_server_num() ->
    ets:info(?SERVER_INFO, size).

%% -------------------------------------------
%% 后台专用
%% -------------------------------------------
%% 检查跨服状态(后台使用)
check_node_status(Node) ->
    case rpc:call(Node, clusters_node, get_process_pid, [], 5000) of
        Pid when is_pid(Pid) -> 1;
        _ -> 0
    end.

%% 通知重启(后台使用)
cast_node_restart(Node) ->
    case rpc:call(Node, init, restart, [], 20000) of
        ok -> 1;
        _  -> 0
    end.
%% -------------------------------------------

%% -------------------------------------------
%% 异步处理call请求，并返回结果
%% 可用于跨服中心向游戏服发出请求，也可用于游戏服向跨服中心发出请求
%%
%% 使用方法：
%% 在调用的节点，以gen_server:call/2形式向管理进程发出Request
%% 处理进程收到Request后，调用async_apply_call/3向远程节点发出Request
%% 远程节点处理完Request后，通知到原调用节点
%% 原调用节点调用gen_server:reply/2给调用进程返回消息
%% -------------------------------------------

%% 异步处理同步的call请求
async_apply_call(NodeToCall, From, MFA) ->
    rpc:cast(NodeToCall, ?MODULE, async_apply_call_reply, [node(), From, MFA]).

%% 异步处理结果返回给调用节点
async_apply_call_reply(NodeToReply, From, MFA) ->
    Result = util:do_mfa(MFA),
    rpc:cast(NodeToReply, gen_server, reply, [From, Result]).

%% -------------------------------------------

%% call远程节点执行mfa
rpc_call_do_mfa(Node, {Module, Func, Args}) ->
    rpc:call(Node, Module, Func, Args, 3000);
rpc_call_do_mfa(Node, MFA = {Func, _Args}) when is_function(Func) ->
    rpc:call(Node, util, do_mfa, [MFA], 3000);
rpc_call_do_mfa(Node, Func) when is_function(Func) ->
    rpc:call(Node, util, do_mfa, [Func], 3000);
rpc_call_do_mfa(_, _) -> error.

%% cast到远程节点执行mfa
rpc_cast_do_mfa(Node, {Module, Func, Args}) ->
    rpc:cast(Node, Module, Func, Args);
rpc_cast_do_mfa(Node, MFA = {Func, _Args}) when is_function(Func) ->
    rpc:cast(Node, util, do_mfa, [MFA]);
rpc_cast_do_mfa(Node, Func) when is_function(Func, 0) ->
    rpc:cast(Node, util, do_mfa, [Func]);
rpc_cast_do_mfa(_, _) -> error.

%% 添加节点
add_node_db(#game_node{node = Node, server_id = ServerId}) ->
    db:execute(io_lib:format(?SQL_REPLACE_CLUSTERS_NODE, [Node, ServerId])).

%% 删除节点
del_node_db(#game_node{server_id = ServerId}) ->
    db:execute(io_lib:format(?SQL_DELETE_CLUSTERS_NODE, [ServerId])).

%% -------------------------------------------
%% 节点连通后的回调处理
%% -------------------------------------------

%% 跨服中心连接到游戏服节点后回调（在跨服中心执行）
-spec center_connected(GameNode :: #game_node{}) -> ok.
center_connected(GameNode) ->
    #game_node{
        node = Node, server_id = ServerId, open_time = OpenTime
        ,merge_server_ids = _MergeServerIds
    } = GameNode,
    cross_zone_api:center_connected(GameNode),
    mod_top_pk_rank_center:apply_cast({'sync_rank_info', Node}),
    mod_void_fam:sync_act_status(Node),
    mod_kf_1vn_auction:sync_auction_state(Node, 1),
    mod_kf_1vn:sync_state(Node),
    race_rank_act:apply_for_opening_list(Node),
    mod_kf_supplication:sync_broadcast(Node),
    lib_custom_act_kf:sync_to_local_node(Node),
    mod_cgwar_ctrl:sync_cur_state(Node),
    %beauty_contest_center:node_connect(ServerId),
    deserted_boss_sup:node_connect(ServerId, OpenTime),
    cross_point_fight_center:node_connect(ServerId),
    mod_pet_support_center:node_connect(Node),
    ok.

%% 跨服中心断开与游戏服的连接时回调（跨服中心执行）
-spec center_disconnected(GameNode :: #game_node{}) -> ok.
center_disconnected(GameNode) ->
    cross_zone_api:center_disconnected(GameNode),
    ok.

%% 游戏服节点连接上跨服中心后回调处理（在游戏服执行）
%% CenterNode : 跨服中心节点名
-spec node_connected(CenterNode :: node()) -> ok.
node_connected(_CenterNode) ->
    %% 写入跨服连接状态数据
    IsConnectedCenter = #server_env{name = is_connected_center, value = true},
    ets:insert(?SERVER_ENV, IsConnectedCenter),
    %% 其他系统功能接口回调
    merge:check_merge(),
    lib_player_event:brocast_dispatch(?EVENT_CENTER_CONNECTED),
    ok.

%% 游戏服节点断开与跨服中心的连接时回调处理（在游戏服执行）
%% CenterNode : 跨服中心节点名
-spec node_disconnected(CenterNode :: node()) -> ok.
node_disconnected(_CenterNode) ->
    %% 删除跨服连接状态数据
    ets:delete(?SERVER_ENV, is_connected_center),
    %% 跨服分区断开连接管理
    czone_fire:disconnected(),
    %% 通知所有玩家节点断开与跨服的连接，由玩家进程选择该角色需要做的处理
    Pids = lib_player:get_online_pids(),
    lists:foreach(fun(Pid) ->
                lib_player:apply_cast(Pid, ?APPLY_CAST_STATUS, ?MODULE, role_node_disconnected, [])
        end, Pids),
    lib_player_event:brocast_dispatch(?EVENT_CENTER_DISCONNECT),
    ok.

%% 断开连接时，角色进程回调接口
role_node_disconnected(Player = #player_status{
        scene = SceneId
    }) ->
    case lib_scene:is_clusters_scene(SceneId) of
        true  ->
            %% 在跨服场景，需要将玩家切换到游戏服，状态锁强制打断
            NewPlayer = lib_scene:node_disconnect_kickout(Player),
            %% 如有其他需要角色处理的操作，在这里添加回调接口调用
            {ok, NewPlayer};
        false ->
            ok
    end.

%% ---------------------------------------------------------------------------
%% @doc 查询游戏服是否和跨服中心连接。
%% false是没连接；true是连接
-spec is_connected_center() -> false | true.
%% ---------------------------------------------------------------------------
is_connected_center() ->
    case ets:lookup(?SERVER_ENV, is_connected_center) of
        [] -> false;
        [#server_env{value = Value}]  -> Value;
        _ -> false
    end.

%% -------------------------------------------
%% 其他
%% -------------------------------------------

%% 节点在集群中的类型： 跨服中心/游戏服
-spec get_cluster_type() -> ClusterType :: integer().
get_cluster_type() ->
    case config:get_cls_type() of
        1 -> ?CLUSTER_CENTER;
        _ -> ?CLUSTER_NODE
    end.

%% 是否跨服中心节点
-spec is_center() -> boolean().
is_center() ->
    config:get_cls_type() =:= 1.

