%% ------------------------------------
%% module       :deserted_boss_sup.erl
%% author       :hh
%% time         :21/01/19
%% desc         :混乱星域公共管理
%% ------------------------------------

-module(deserted_boss_sup).

-behaviour (gen_server).

-include ("deserted_boss.hrl").
-include ("common.hrl").

-export ([
    start_link/0,
    cast_sup/1,
    add_svrs/1,
    recalc_zone/1,
    handle_merge/3,
    node_connect/2,
    zone_fac_apply_cast/5,
    zone_fac_apply_cast/3,
    apply_cast/1
    ]).

-export ([
    local_deserted_close/0    
    ]).

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

-ifdef(DEV_SERVER).
-define(DELAY_INIT,      120000).
-else.
-define(DELAY_INIT,      600000).
-endif.

-define(DELAY_RECOVER,   30000).
-define(ZONE_INIT,       1).

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

cast_sup(Msg) ->
    gen_server:cast(?MODULE, Msg).

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

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

handle_merge(NowServerId, NowServerNum, MergeServers) ->
    gen_server:cast(?MODULE, {handle_merge, NowServerId, NowServerNum, MergeServers}).

node_connect(ServerId, OpenTime) ->
    gen_server:cast(?MODULE, {node_connect, ServerId, OpenTime}).

zone_fac_apply_cast(ZoneId, FacId, Module, Fun, Arg) ->
    zone_fac_apply_cast(ZoneId, FacId, {Module, Fun, Arg}).

zone_fac_apply_cast(ZoneId, FacId, {Module, Fun, Arg}) ->
    gen_server:cast(?MODULE, {zone_fac_apply_cast, ZoneId, FacId, {Module, Fun, Arg}}).

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

init([]) ->
    %% 预留足够时间等待分区信息加载
    Ref  = erlang:send_after(?DELAY_INIT, self(), {boss_zone_init}),
    Maps = maps:put(?ZONE_INIT, Ref, #{}),
    {ok, #deserted_sup_state{state_map = Maps}}.

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

handle_cast(Msg, State) ->
    try do_handle_cast(Msg, State) of
    Reply -> Reply
    catch ?EXCEPTION(Class, Reason, StackTrace) ->
        Error = {Class, Reason, ?GET_STACK(StackTrace)},
        ?ERR("deserted_boss_sup handle_cast msg:[~p] error:~p ~n", [Msg, Error]),
        {noreply, State}
    end.

handle_info(Msg, State) ->
    try do_handle_info(Msg, State) of
    Reply -> Reply
    catch ?EXCEPTION(Class, Reason, StackTrace) ->
        Error = {Class, Reason, ?GET_STACK(StackTrace)},
        ?ERR("deserted_boss_sup handle_info msg:[~p] error:~p ~n", [Msg, Error]),
        {noreply, State}
    end.

terminate(_Reason, State) ->
    #deserted_sup_state{zone_faction_pids = ZoneFacPids} = State,
    if _Reason =/= normal ->
            maps:map(fun({ZoneId, FacId}, _Pid) -> 
                deserted_boss_mgr:cast(ZoneId, FacId, {monitor_error_exit})
            end, ZoneFacPids);
        true ->
            skip
    end,
    ok.

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

do_handle_cast({zone_fac_apply_cast, ZoneId, FacId, {Module, Fun, Arg}}, State) ->
    #deserted_sup_state{zone_faction_svrs_map = ZoneFacSvrMaps} = State,
    FacSvrMaps = maps:get(ZoneId, ZoneFacSvrMaps, #{}),
    SvrList    = maps:get(FacId, FacSvrMaps, []),
    MFARet     = util:valid_mfa({Module, Fun, Arg}),
    if  
        not MFARet -> skip;
        true ->
            lists:map(fun(SvrId) -> 
                clusters_center:apply_cast(SvrId, Module, Fun, Arg) 
            end, SvrList)
    end,
    {noreply, State};

%% 有新的游戏服正式注册时，将产生新的独立子分区（初阶分区为1区1服）
%% Map :: #{cz_state => CZState, add_servers => AddServers}
%% AddServers :: [{ServerId, ZoneId, Faction}, ...]
do_handle_cast({add_svrs, Map}, State) ->
    #{add_servers := AddServers} = Map,
    #deserted_sup_state{
        zone_faction_svrs_map = ZoneFacSvrMaps
    } = State,
    NewZoneFacSvrMaps =
    lists:foldl(fun({ServerId, ZoneId, Faction}, TempZoneFacSvrMaps) -> 
        FacSvrMaps = maps:get(ZoneId, TempZoneFacSvrMaps, #{}),
        SvrList    = maps:get(Faction, FacSvrMaps, []),
        case lists:member(ServerId, SvrList) of
            false ->
                NewSvrList = [ServerId|SvrList],
                NewFacSvrsMap = maps:put(Faction, NewSvrList, FacSvrMaps),
                maps:put(ZoneId, NewFacSvrsMap, TempZoneFacSvrMaps);
            _ -> TempZoneFacSvrMaps
        end
    end, ZoneFacSvrMaps, AddServers),
    NewState = State#deserted_sup_state{zone_faction_svrs_map = NewZoneFacSvrMaps},
    {noreply, NewState};

% 动态分区调整
% CBMap = #{
%     ,svr_zone_map => NewSvrZoneMap
%     ,zone_map => ZoneMap
%     ,faction_maps => FactionMaps
% },
% ZoneMap       #{OldZoneId  => NewZoneId}
% FactionMaps   #{OldZoneId  => FactionMap :: #{OldFaction => NewFaction}}
% （ZoneMap中，OldZoneId 等于 NewZoneId 说明该分区为主分区，未发生zone_id变化）
do_handle_cast({recalc_zone, ChangeMap}, State) ->
    #{
        zone_map := ChangeZoneMap,
        faction_maps := ChangeFacMap,
        svr_zone_map := SvrZoneMap,
        stage := Stage
    } = ChangeMap,
    #deserted_sup_state{
        zone_faction_svrs_map = ZoneFacSvrMaps,
        zone_faction_pids = ZoneFacPids,
        node_time_info = NodeOpenTimes
    } = State,
    %% 涉及分区阵营的常规数据变动处理
    ChangeZoneFacs       = calc_zone_fac_change_data(ChangeZoneMap, ChangeFacMap),
    ChangeSvrZoneFacs    = calc_svr_zone_fac_change_data(ChangeZoneFacs, ZoneFacSvrMaps),
    ClsSvrs              = calc_svr_boss_status(SvrZoneMap, ChangeZoneMap, Stage),
    NewZoneFacSvrMaps    = recalc_zone_fac_svrs_map(ChangeZoneFacs, ZoneFacSvrMaps),
    NewZoneFacPids       = recalc_zone_fac_pids(ChangeZoneFacs, ZoneFacPids),
    NewState = State#deserted_sup_state{
        zone_faction_svrs_map = NewZoneFacSvrMaps,
        zone_faction_pids = NewZoneFacPids
    },
    %% 1.关闭被合并的分区阵营工作进程
    %% 2.切换本服玩法到跨服玩法
    %% 3.检查主阵营变动工作进程是否正常工作,若无则尝试启动
    recalc_deserted_boss_worker(ChangeSvrZoneFacs, ClsSvrs, NewZoneFacSvrMaps, NodeOpenTimes),
    {noreply, NewState};

do_handle_cast({handle_merge, NowServerId, NowServerNum, MergeServers}, State) ->
    #deserted_sup_state{
        zone_faction_svrs_map = ZoneFacSvrMaps, %% 分区阵营对应游戏服列表
        node_time_info = _NodeOpenTimes
    } = State,
    NewZoneFacSvrMaps = calc_zone_faction_svr(ZoneFacSvrMaps, NowServerId, MergeServers),
    NewState = State#deserted_sup_state{zone_faction_svrs_map = NewZoneFacSvrMaps},
    ?ERR("deserted_boss merge ~p to master ~p master_server_num ~p", [MergeServers, NowServerId, NowServerNum]),
    {noreply, NewState};

do_handle_cast({apply_cast, {Module, Fun, Args}}, State) ->
    case apply(Module, Fun, [State|Args]) of
        NewState = #deserted_sup_state{} ->
            {noreply, NewState};
        {ok, NewState = #deserted_sup_state{}} ->
            {noreply, NewState};
        _ ->
            {noreply, State}
    end;

%% 更新节点开服时间数据
do_handle_cast({node_connect, ServerId, OpenTime}, State) ->
    #deserted_sup_state{node_time_info = NodeOpenTimes} = State,
    case lists:keyfind(ServerId, 1, NodeOpenTimes) of
        {_, _} ->
            {noreply, State};
        _ ->
            NewNodeOpenTimes = lists:keystore(ServerId, 1, NodeOpenTimes, {ServerId, OpenTime}),
            NewState = State#deserted_sup_state{node_time_info = NewNodeOpenTimes},
            {noreply, NewState}
    end;

do_handle_cast(_Msg, State) ->
    ?ERR("unkown msg ~p", [_Msg]),
    {noreply, State}.

do_handle_info({boss_zone_init}, State) ->
    #deserted_sup_state{state_map = StateMap} = State,
    ZoneFacSvrMaps = czone:get_faction_map(zone_faction_svrs_map),
    ZoneStages     = czone:get_zone_stages(),
    NodeOpenTimes  = clusters_center:get_nodes_open_time(),
    Ref = maps:get(?ZONE_INIT, StateMap, undefined),
    util:cancel_timer(Ref),
    NewStateMap = maps:remove(?ZONE_INIT, StateMap),
    NewState = State#deserted_sup_state{
        zone_faction_svrs_map = ZoneFacSvrMaps, 
        node_time_info = NodeOpenTimes,
        state_map = NewStateMap
    },
    NewState1 = init_zone_fac_deserted_boss(NewState, ZoneStages),
    {noreply, NewState1};

do_handle_info({init_deserted_boss_worker, ZoneId, FacId}, State) ->
    #deserted_sup_state{
        zone_faction_svrs_map = ZoneFacSvrMaps,
        zone_faction_pids = ZoneFacPids, 
        recover_map = RecoverMap, 
        monitor_map = MonitorMap,
        node_time_info = NodeOpenTimes
    } = State,
    util:cancel_timer(maps:get({ZoneId, FacId}, RecoverMap, undefined)),
    NewRecoverMap = maps:remove({ZoneId, FacId}, RecoverMap),
    OpenTime = calc_tar_zone_fac_open_time(ZoneFacSvrMaps, ZoneId, FacId, NodeOpenTimes),
    {NewZoneFacPids, NewMonitorMap} =
    case deserted_boss_mgr:start([ZoneId, FacId, OpenTime]) of
        {ok, Pid} ->
            MonitorRef = erlang:monitor(process, Pid),
            {
            maps:put({ZoneId, FacId}, Pid, ZoneFacPids),
            maps:put(MonitorRef, {ZoneId, FacId, Pid}, MonitorMap)
            };
        {error, {already_started, _Pid}} ->
            {ZoneFacPids, MonitorMap};
        {error, Reason} ->
            ?ERR("init deserted boss {zone_id, fac_id}:~p start ~n error:~p", [{ZoneId, FacId}, Reason]),
            {ZoneFacPids, MonitorMap}
    end,
    NewState = State#deserted_sup_state{
        zone_faction_pids = NewZoneFacPids,
        recover_map = NewRecoverMap,
        monitor_map = NewMonitorMap
    },
    {noreply, NewState};


do_handle_info({'DOWN', MonitorRef, _Type, _Obj, _Reason}, State) ->
    #deserted_sup_state{monitor_map = MonitorMap, recover_map = RecoverMap} = State,
    % erlang:demonitor(MonitorRef),
    Ret = maps:get(MonitorRef, MonitorMap, undefined),
    NewState = 
    case Ret of
        {ZoneId, FacId, _Pid} ->
            ?ERR("deserted boss {zone_id, fac_id}:~p worker:[~p] ~n shutdown:~p", [{ZoneId, FacId}, _Pid, _Reason]),
            if _Reason =/= normal ->
                    %% 30秒后尝试重新开启工作进程
                    Ref = erlang:send_after(?DELAY_RECOVER, self(), {init_deserted_boss_worker, ZoneId, FacId}),
                    NewRecoverMap = maps:put({ZoneId, FacId}, Ref, RecoverMap),
                    NewMonitorMap = maps:remove(MonitorRef, MonitorMap),
                    State#deserted_sup_state{monitor_map = NewMonitorMap, recover_map = NewRecoverMap};
                true ->
                    NewMonitorMap = maps:remove(MonitorRef, MonitorMap),
                    State#deserted_sup_state{monitor_map = NewMonitorMap}
            end;
        _ ->
            State
    end,
    {noreply, NewState};

do_handle_info(_Msg, State) ->
    ?ERR("unkown msg ~p", [_Msg]),
    {noreply, State}.

init_zone_fac_deserted_boss(State, ZoneStages) ->
    #deserted_sup_state{
        zone_faction_svrs_map = ZoneFacSvrMaps,
        zone_faction_pids = ZoneFacPids,
        monitor_map = MonitorMap,
        node_time_info = NodeOpenTimes
    } = State,
    ZoneFacs = calc_zone_fac_svr_open(ZoneFacSvrMaps, NodeOpenTimes, ZoneStages),
    {NewZoneFacPids, NewMonitorMap} =
    lists:foldl(fun({ZoneId, Facs}, {TempZoneFacPids, TempMonitorMap}) ->
        init_zone_fac_deserted_boss_do(ZoneId, Facs, TempZoneFacPids, TempMonitorMap)
    end, {ZoneFacPids, MonitorMap}, ZoneFacs),
    State#deserted_sup_state{
        zone_faction_pids = NewZoneFacPids,
        monitor_map = NewMonitorMap
    }.

init_zone_fac_deserted_boss_do(ZoneId, Facs, ZoneFacPids, MonitorMap) ->
    lists:foldl(fun({FacId, FacSvrOpens}, {TempZoneFacPids, TempMonitorMap}) ->
        WorkPid = deserted_boss_mgr:get_worker_by_name(ZoneId, FacId),
        PidRet = is_pid(WorkPid) andalso misc:is_process_alive(WorkPid),
        if PidRet ->
                ?ERR("worker:{~p, ~p} have existed", [ZoneId, FacId]),
                {TempZoneFacPids, TempMonitorMap};
            true ->
                OpenTime = get_latest_open_time(FacSvrOpens),
                case deserted_boss_mgr:start([ZoneId, FacId, OpenTime]) of
                    {ok, Pid} ->
                        MonitorRef = erlang:monitor(process, Pid),
                        {
                        maps:put({ZoneId, FacId}, Pid, TempZoneFacPids),
                        maps:put(MonitorRef, {ZoneId, FacId, Pid}, TempMonitorMap)
                        };
                    {error, {already_started, _Pid}} ->
                        {TempZoneFacPids, TempMonitorMap};
                    {error, Reason} ->
                        ?ERR("deserted boss {zone_id, fac_id}:~p ~n init error:~p", [{ZoneId, FacId}, Reason]),
                        {TempZoneFacPids, TempMonitorMap}
                end
        end
    end, {ZoneFacPids, MonitorMap}, Facs).

%% 获取分区所有阵营
%% 过滤低于阶段3的分区
calc_zone_fac_svr_open(ZoneFacSvrMaps, NodeOpenTimes, ZoneStages) ->
    maps:fold(fun(ZoneId, V, Acc) ->
        case lists:keyfind(ZoneId, 1, ZoneStages) of
            {_, Stage} when Stage >= 3 ->
                FacSvrOpens = calc_zone_fac_svr_open_1(maps:to_list(V), NodeOpenTimes, []),
                [{ZoneId, FacSvrOpens}|Acc];
            _ -> Acc
        end
    end, [], ZoneFacSvrMaps).


calc_zone_fac_svr_open_1([], _NodeOpenTimes, Acc) ->
    Acc;
calc_zone_fac_svr_open_1([{FacId, Svrs}|Rest], NodeOpenTimes, Acc) ->
    FacSvrOpens = lists:map(fun(SvrId) -> 
        case lists:keyfind(SvrId, 1, NodeOpenTimes) of
            {_, OpenTime} -> {SvrId, OpenTime};
            _             -> {SvrId, 0}
        end
    end, Svrs),
    calc_zone_fac_svr_open_1(Rest, NodeOpenTimes, [{FacId, FacSvrOpens}|Acc]).


get_latest_open_time(FacSvrOpens) ->
    case lists:reverse(lists:keysort(2, FacSvrOpens)) of
        [{_, OpenTime}|_] -> OpenTime;
        _                 -> utime:unixtime()
    end.

calc_tar_zone_fac_open_time(ZoneFacSvrMaps, ZoneId, FacId, NodeOpenTimes) ->
    Now = utime:unixtime(),
    FacSvrMaps = maps:get(ZoneId, ZoneFacSvrMaps, #{}),
    FacSvrs = maps:get(FacId, FacSvrMaps, []),
    FacSvrOpenTimes =
    lists:map(fun(SvrId) -> 
        case lists:keyfind(SvrId, 1, NodeOpenTimes) of
            {_, OpenTime} -> {SvrId, OpenTime};
            _             -> {SvrId, Now}
        end
    end, FacSvrs),
    case lists:reverse(lists:keysort(2, FacSvrOpenTimes)) of
        [{_, OpenTime}|_] -> OpenTime;
        _   -> OpenTime = Now
    end,
    OpenTime.

% ZoneMap       #{OldZoneId  => NewZoneId}
% FactionMaps   #{OldZoneId  => FactionMap :: #{OldFaction => NewFaction}}
% return [{{旧分区id,旧阵营},{新分区id,新阵营id}},...]
calc_zone_fac_change_data(ChangeZoneMap, ChangeFacMap) ->
    maps:fold(fun(OldZoneId, NewZoneId, Acc) -> 
        OldChangeFacMap = maps:get(OldZoneId, ChangeFacMap, #{}),
        maps:fold(fun(OldFaction, NewFaction, TempAcc) -> 
            [{{OldZoneId, OldFaction}, {NewZoneId, NewFaction}}|TempAcc]
        end, Acc, OldChangeFacMap)
    end, [], ChangeZoneMap).

calc_svr_zone_fac_change_data(ChangeZoneFacs, ZoneFacSvrMaps) ->
    lists:foldl(fun({{OldZoneId, OldFacId}, {NewZoneId, NewFacId}}, Acc) -> 
        ZoneFacSvrs = maps:get(OldZoneId, ZoneFacSvrMaps, #{}),
        FacSvrs = maps:get(OldFacId, ZoneFacSvrs, []),
        [{{OldZoneId, OldFacId}, {NewZoneId, NewFacId}, FacSvrs}|Acc]
    end, [], ChangeZoneFacs).

%% 计算本次变动达到跨服玩法标准的游戏服
calc_svr_boss_status(SvrZoneMap, ChangeZoneMap, Stage) ->
    AllZoneSvrs =
    lists:foldl(fun({SvrId, ZoneId}, Acc) ->
        NewZoneSvrs =
        case lists:keyfind(ZoneId, 1, Acc) of
            {_, ZoneSvrs} ->
                case lists:member(SvrId, ZoneSvrs) of
                    true -> ZoneSvrs;
                    _    -> [SvrId|ZoneSvrs]
                end;
            _ -> [SvrId]
        end,
        lists:keystore(ZoneId, 1, Acc, {ZoneId, NewZoneSvrs})
    end, [], maps:to_list(SvrZoneMap)),

    ChangeZoneList = maps:to_list(ChangeZoneMap),
    if Stage < 3 ->
            [];
        true ->
            FilZoneSvrs = 
            lists:filter(fun({ZoneIdX, _ZoneSvrsX}) ->
                AnyRet = lists:any(fun({_OldZoneId, NewZoneId}) -> 
                    NewZoneId == ZoneIdX
                end, ChangeZoneList),
                AnyRet
            end, AllZoneSvrs),
            if  FilZoneSvrs =/= [] ->
                    lists:append([ZoneSvrsX1 || {_ZoneIdX1, ZoneSvrsX1} <- FilZoneSvrs]);
                true ->
                    []
            end
    end.

%% ChangeZoneFacs :: [{{旧分区id,旧阵营},{新分区id,新阵营id}},...]
%% ZoneFacSvrMaps :: #{ZoneId => FSvrsMap}, FSvrsMap :: #{Faction => ServerIds}
recalc_zone_fac_svrs_map(ChangeZoneFacs, ZoneFacSvrMaps) ->
    ZoneFacSvrMaps1 =
    lists:foldl(fun({{OldZoneId, OldFacId}, {NewZoneId, NewFacId}}, TempZoneFacSvrMaps) -> 
        OldZoneFacSvrs = maps:get(OldZoneId, TempZoneFacSvrMaps, #{}),
        NewZoneFacSvrs = maps:get(NewZoneId, TempZoneFacSvrMaps, #{}),
        OldFacSvrs  = maps:get(OldFacId, OldZoneFacSvrs, []),
        NewFacSvrs  = maps:get(NewFacId, NewZoneFacSvrs, []),
        NewFacSvrs1 = lists:append(OldFacSvrs, NewFacSvrs),
        NewZoneFacSvrs1 = maps:put(NewFacId, NewFacSvrs1, NewZoneFacSvrs),
        TempZoneFacSvrMaps1 = maps:put(NewZoneId, NewZoneFacSvrs1, TempZoneFacSvrMaps),
        TempZoneFacSvrMaps1
    end, ZoneFacSvrMaps, ChangeZoneFacs),
    lists:foldl(fun({{OldZoneIdX, OldFacIdX}, _}, TempZoneFacSvrMapsX) -> 
        OldZoneFacSvrsX  = maps:get(OldZoneIdX, TempZoneFacSvrMapsX, #{}),
        OldZoneFacSvrsX1 = maps:remove(OldFacIdX, OldZoneFacSvrsX),
        maps:put(OldZoneIdX, OldZoneFacSvrsX1, TempZoneFacSvrMapsX)
    end, ZoneFacSvrMaps1, ChangeZoneFacs).

%% ChangeZoneFacs :: [{{旧分区id,旧阵营},{新分区id,新阵营id}},...]
recalc_zone_fac_pids(ChangeZoneFacs, ZoneFacPids) ->
    lists:foldl(fun({{OldZoneId, OldFacId}, {_NewZoneId, _NewFacId}}, TempZoneFacPids) ->
            NewTempZoneFacPids = maps:remove({OldZoneId, OldFacId}, TempZoneFacPids),
            NewTempZoneFacPids 
    end, ZoneFacPids, ChangeZoneFacs).

%% ChangeSvrZoneFacs :: [{{OldZoneId, OldFacId}, {NewZoneId, NewFacId}, FacSvrs}]
recalc_deserted_boss_worker(ChangeSvrZoneFacs, ClsSvrs, ZoneFacSvrMaps, NodeOpenTimes) ->
    lists:map(fun({{OldZoneId, OldFacId}, {NewZoneId, NewFacId}, FacSvrs}) ->
        %% 关闭被合并的阵营进程 
        deserted_boss_mgr:cast(OldZoneId, OldFacId, {deserted_boss_close_after_recalc}),
        %% 关闭达到跨服模式的游戏本地进程
        [clusters_center:apply_cast(ServId, {?MODULE, local_deserted_close, []}) || ServId <- FacSvrs, lists:member(ServId, ClsSvrs)],
        Pid    = deserted_boss_mgr:get_worker_by_name(NewZoneId, NewFacId),
        PidRet = is_pid(Pid) andalso misc:is_process_alive(Pid),
        if PidRet -> %% 检查阵营内游戏服的开服天数
                check_node_time_open(NewZoneId, NewFacId, ZoneFacSvrMaps, NodeOpenTimes);
            true  -> 
                erlang:send(self(), {init_deserted_boss_worker, NewZoneId, NewFacId})
        end
    end, ChangeSvrZoneFacs).

%% 阵营变动的游戏服执行
local_deserted_close() ->
    {Ret, _ZoneId, _FacId} = deserted_boss_mgr:check_cls_deserted_boss(),
    if
        Ret -> %% 若已达到参与跨服玩法则关闭本服进程
            deserted_boss_mgr:close_local_deserted_boss();
        true ->
            skip
    end.

%% 合服处理
%% ZoneFacSvrMaps :: #{ZoneId => FSvrsMap}, FSvrsMap :: #{Faction => ServerIds}
calc_zone_faction_svr(ZoneFacSvrMaps, _NowServerId, MergeServers) ->
    maps:fold(fun(ZoneId, FacSvrsMap, TempMap) -> 
        NewFacSvrsMap = maps:fold(fun(FacId, SvrIds, TempFacSvrsMap) -> 
            NewSvrIds = SvrIds -- MergeServers,
            maps:put(FacId, NewSvrIds, TempFacSvrsMap)
        end, FacSvrsMap, FacSvrsMap),
        maps:put(ZoneId, NewFacSvrsMap, TempMap)
    end, ZoneFacSvrMaps, ZoneFacSvrMaps).

check_node_time_open(ZoneId, FacId, ZoneFacSvrMaps, NodeOpenTimes) ->
    FacSvrMaps  = maps:get(ZoneId, ZoneFacSvrMaps, #{}),
    FacSvrs     = maps:get(FacId, FacSvrMaps, []),
    FacSvrOpens = lists:filtermap(fun(SvrId) -> 
        case lists:keyfind(SvrId, 1, NodeOpenTimes) of
            {_, NodeOpenTime} -> {true, {SvrId, NodeOpenTime}};
            _   -> false
        end
    end, FacSvrs),
    case lists:reverse(lists:keysort(2, FacSvrOpens)) of
        [{_, LatestTime}|_] ->
            deserted_boss_mgr:cast(ZoneId, FacId, {update_node_open_time, LatestTime});
        _ ->
            skip
    end.