%%% -------------------------------------------------------
%%% @author huangyongxing@yeah.net
%%% @doc
%%% 标准跨服分区
%%% 基于cross_zone开发，主要是为了利用原接口，避免重复开发。
%%% 所以要求对cross_zone有基本的认识，才方便了解此分区的管理
%%% -------------------------------------------------------
%%%
%%% 基本规则说明：
%%%
%%% 游戏服第二天在跨服进行注册（为保持未来稳定的分区合并顺序），
%%% 注册后，此时为0阶分区（分区中仅单个服）。
%%% 分区的进阶，需要达到世界等级、天数等限制，同时还需要有同阶分区。
%%% 没有对应的同阶分区不能合并进阶到下一阶分区，
%%% 要等待相邻的同阶分区的出现。
%%%
%%% 理论上，分区的服数是1、2、4、8、16、32、64、128 ... （即2^N）
%%%
%%% 实际上，可能因为合服或运营微调等原因略有变化，
%%% 但是整体来说分区合并过程是可预测的稳定结果。
%%%
%%% -------------------------------------------------------
%%%
%%% 关于分区合并时的主分区、副分区：
%%%
%%% ZoneId 与 Stage无关，Stage变化时，维持使用主分区的ZoneId
%%% 一般而言，主分区的阵营也不变，副分区的阵营会按条件并入主分区中
%%%
%%% -------------------------------------------------------
%%%
%%% 关于合并分区时，阵营的合并：
%%%
%%% 阵营的合并，是以整组阵营为单位，不是以服为单位。
%%% 合并依据是阵营活跃度 —— 对应服最近三天内登录过的战力前列的玩家总战力
%%%
%%% -------------------------------------------------------
%%%
%%% 关于设计的问题：
%%%
%%% 暂定设计不支持对游戏服所处分区进行调整
%%% 因为与ZoneId计算规则冲突，除非有不得不调整的理由，可能不会再改。
%%% 如果需要ZoneId可手工调整的，那么就要多一个数据，
%%% 记录是否已经有ZoneId，不能根据公式计算，不利于数据的转换计算
%%%
%%% -------------------------------------------------------
%%% TODO 合服管理
%%%      需要做合服分区数据更新回调、小分区合并回调等
%%% TODO 合跨服管理
%%%      合跨服需要考虑各个接口有无问题、规则要如何调整以达成合并
%%% @end
%%% @since 2020-07-10
%%% -------------------------------------------------------
-module(czone).
-behaviour(cross_zone).
%% API
-export([
        get_svr_zone_map/0              % 获取区服映射关系
        ,get_faction_map/1              % 获取阵营映射关系
        ,async_get_faction_map/2        % 异步获取阵营映射关系
        ,get_zone_id/1                  % 获取游戏服对应的ZoneId
        ,get_scheduled_time/1           % 获取计划分区时间
        ,get_zone_svr_local/1           % 获取游戏服对应的基础信息（对应游戏服节点缓存数据）
        ,get_zone_stage/1               % 获得对应分区的分区阶段
        ,get_zone_stages/0              % 获取所有分区及其对应的分区阶段
        ,get_zone_world_lv/1            % 获得对应分区的平均世界等级
        ,get_zone_servers/1             % 获取指定ZoneId对应的服务器数据
        ,zone_apply_cast/2              % 指定ZoneId执行
        ,zone_faction_apply_cast/3      % 指定ZoneId及FactionId（阵营）执行MFA
        ,set_faction_name/3             % 设置分区阵营名字
        ,get_faction_name/2             % 获取分区阵营名字
    ]).
%% 跨服分区基础功能API
-export([
        start_link/0
        ,add_reg_svr_list/1
        ,get_czone_info/1
        ,get_combine_advance/1
        ,get_rezone_progress/1
        ,get_rezone_notice/1
        ,update_svr_world_level/2
        ,check_update_svr_info/2
        ,calc_timer_secs/1
        ,calc_timer_secs/2
        ,to_czone_svr_local_list/1
        ,to_czone_svr_local/3
        ,set_zone_vitalitys/1
    ]).
%% cross_zone callbacks
-export([
        init/1
        ,handle_event/3
        ,add_node/4
    ]).
%% gm
-export([
        gm_timer/1
        ,gm_reset_zones/0
        ,gm_zone_n_times/1
        ,gm_read_timer/0
        ,gm_save_zones/0
    ]).
%% only for debug
% -compile(export_all).
-export([
        fix_server_ids/2
        ,is_mate_zone/3
        ,gen_pre_rezone/4
    ]).

-include("common.hrl").
-include("cross_zone.hrl").
-include("clusters.hrl").
-include("center_svr_reg.hrl").
-include("czone.hrl").

-define(SERVER, ?MODULE).

%%%===================================================================
%%% API
%%%===================================================================

%% -------------------------------------------------------
%% 分区各功能模块公共API
%% -------------------------------------------------------

-spec get_svr_zone_map() -> SvrZoneMap | Error when
    SvrZoneMap :: #{ServerId => ZoneId},
    ServerId :: non_neg_integer(), ZoneId :: non_neg_integer(),
    Error :: any().
%% 获取区服映射关系
get_svr_zone_map() ->
    cross_zone:get_server_zone_map(?SERVER).

-spec get_faction_map(Type) -> Reply | Error when
    Type :: svr_faction_map | zone_faction_svrs_map | all,
    Reply :: SvrFMap | ZoneFSvrsMap | #{
        svr_faction_map => SvrFMap,
        zone_faction_svrs_map => ZoneFSvrsMap
    } | undefined,
    SvrFMap :: #{ServerId => Faction},
    ZoneFSvrsMap :: #{ZoneId => FSvrsMap},
    FSvrsMap :: #{Faction => ServerIds},
    ServerIds :: [ServerId],
    ServerId :: non_neg_integer(), ZoneId :: non_neg_integer(),
    Error :: any().
%% 获取分区阵营映射关系
get_faction_map(Type) ->
    cross_zone:call(?SERVER, {get_faction_map, Type}).

async_get_faction_map(Type, CallbackMFA = {_M, _F, _A}) ->
    cross_zone:cast(?SERVER, {async_get_faction_map, Type, CallbackMFA}).

-spec get_zone_id(ServerId) -> ZoneId | error | OtherErr when
    ServerId :: non_neg_integer(), ZoneId :: non_neg_integer(),
    OtherErr :: any().
%% 获取游戏服对应分区的ZoneId
get_zone_id(ServerId) ->
    cross_zone:get_server_zone(?SERVER, ServerId).

-spec get_scheduled_time(ServerId) -> {NoticeState, ZoneId, ScheduledTime, NextStage} | Error when
    ServerId :: non_neg_integer(),
    NoticeState :: boolean(),
    ZoneId :: non_neg_integer(),
    ScheduledTime :: non_neg_integer(),
    NextStage :: non_neg_integer(),
    Error :: any().
%% 获取游戏服计划分区时间
%% NoticeState true表示当前已经到了通知倒计时阶段，false表示不在该阶段
get_scheduled_time(ServerId) ->
    cross_zone:call(?SERVER, {get_scheduled_time, ServerId}).

-spec get_zone_svr_local(ServerId) -> CZoneSvrLocal | error | OtherErr when
    ServerId :: non_neg_integer(),
    CZoneSvrLocal :: #czone_svr_local{},
    OtherErr :: any().
%% 获取游戏服对应的基础信息（对应游戏服节点缓存数据）
get_zone_svr_local(ServerId) ->
    cross_zone:call(?SERVER, {get_zone_svr_local, ServerId}).

%% 获得对应分区的分区阶段
-spec get_zone_stage(ZoneId) -> ZoneStage when
    ZoneId :: non_neg_integer(), ZoneStage :: non_neg_integer().
get_zone_stage(ZoneId) ->
    cross_zone:call(?SERVER, {get_zone_stage, ZoneId}).

%% 获取所有分区及其对应的分区阶段
-spec get_zone_stages() -> ZoneStages | {error, Error} when
    ZoneStages :: [{ZoneId, ZoneStage}],
    ZoneId :: non_neg_integer(), ZoneStage :: non_neg_integer(),
    Error :: any().
get_zone_stages() ->
    cross_zone:call(?SERVER, get_zone_stages).

%% 获得对应分区的平均世界等级
-spec get_zone_world_lv(ZoneId) -> WorldLvAvg | error | {error, Error} when
    ZoneId :: non_neg_integer(),
    WorldLvAvg :: non_neg_integer(),
    Error :: any().
get_zone_world_lv(ZoneId) ->
    cross_zone:call(?SERVER, {get_zone_world_lv, ZoneId}).

%% 获取指定ZoneId对应的服务器数据
get_zone_servers(ZoneId) ->
    cross_zone:get_zone_servers(?SERVER, ZoneId).

%% 指定ZoneId执行
-spec zone_apply_cast(ZoneId, MFA) -> ok | error when
    ZoneId :: non_neg_integer() | all,
    MFA :: {Mod :: atom(), Func :: atom(), Args} | {Func :: function(), Args},
    Args :: list().
zone_apply_cast(ZoneId, MFA) ->
    cross_zone:apply_to_zone(?SERVER, ZoneId, MFA).

%% 指定ZoneId及FactionId（阵营）执行MFA
-spec zone_faction_apply_cast(ZoneId, Faction, MFA) -> ok | error when
    ZoneId :: non_neg_integer(),
    Faction :: non_neg_integer(),
    MFA :: {Mod :: atom(), Func :: atom(), Args} | {Func :: function(), Args},
    Args :: list().
zone_faction_apply_cast(ZoneId, Faction, MFA) ->
    CheckMFARet = util:valid_mfa(MFA),
    case CheckMFARet of
        true  -> cross_zone:cast(?SERVER, {zone_faction_apply, ZoneId, Faction, MFA});
        false -> error
    end.

%% -------------------------------------------------------
%% 分区基础功能相关API
%% -------------------------------------------------------
start_link() ->
    %% 标准跨服分区
    cross_zone:start_link({local, ?SERVER}, ?MODULE, []).

%% 添加注册服列表
add_reg_svr_list(ReqtAddRegSvrList) ->
    %% 过滤掉无效数据（主要为控制GM操作问题）
    AddRegSvrList = [
        RegSvr || RegSvr  = #reg_svr{svr_cid = SvrCid} <- ReqtAddRegSvrList,
        SvrCid > 0
    ],
    cross_zone:cast(?SERVER, {add_reg_svr_list, AddRegSvrList}).

%% 获取跨服分区状态资料
get_czone_info(ServerId) ->
    cross_zone:call(?SERVER, {get_czone_info, ServerId}).

%% 获取跨服分区预告
get_combine_advance(ServerId) ->
    cross_zone:call(?SERVER, {get_combine_advance, ServerId}).

%% 获取跨服再分区进度
get_rezone_progress(ServerId) ->
    cross_zone:call(?SERVER, {get_rezone_progress, ServerId}).

%% 获取跨服再分区通知状态
get_rezone_notice(ServerId) ->
    cross_zone:call(?SERVER, {get_rezone_notice, ServerId}).

%% 更新世界等级
update_svr_world_level(ServerId, WorldLv) ->
    cross_zone:cast(?SERVER, {update_svr_world_level, ServerId, WorldLv}).

%% 注册时更新节点相关资料
check_update_svr_info(GameNodes, ServerIdAddList) ->
    ZonePid = whereis(?SERVER),
    case is_pid(ZonePid) of
        true  ->
            %% 找到相关新增服务器，通知分区服务节点更新资料
            lists:foreach(fun(ServerId) ->
                        case lists:keyfind(ServerId, #game_node.server_id, GameNodes) of
                            #game_node{} = GameNode ->
                                SimpleGameNode = GameNode#game_node{merge_server_ids = []},
                                %% 创建节点连接消息（暂无特别区别，不需要区分是否真实连接消息）
                                cross_zone:add_node(ZonePid, SimpleGameNode, normal);
                            _ -> ignore
                        end
                end, ServerIdAddList);
        false ->
            ignore
    end.

%% 设置分区各阵营活跃度信息
set_zone_vitalitys(ZoneVitalityList = [_|_]) ->
    cross_zone:cast(?SERVER, {vitality, ZoneVitalityList});
set_zone_vitalitys(_) ->
    ok.

%% 设置分区阵营名字
set_faction_name(ZoneId, Faction, FactionName) ->
    FactionNameStr = unicode:characters_to_binary(FactionName),
    cross_zone:cast(?SERVER, {set_faction_name, ZoneId, Faction, FactionNameStr}).

%% 获取分区阵营名字
%% 参数Faction == all时，返回 {ok, [{Faction, FactionNameBin}]} 格式的数据
%% 参数Faction为相应的阵营id时，返回 FactionNameBin或<<>>
-spec get_faction_name(ZoneId, Faction) -> {ok, FactionNames} | {error, Error} when
    ZoneId :: non_neg_integer(),
    Faction :: all | non_neg_integer(),
    FactionNames :: [FactionNameInfo],
    FactionNameInfo :: {Faction :: non_neg_integer(), FactionName :: binary()},
    Error :: any().
get_faction_name(ZoneId, Faction = all) ->
    case cross_zone:call(?SERVER, {get_faction_name, ZoneId, Faction}) of
        {ok, FactionNames} ->
            lists:foldl(fun(FactionX, NamesAcc) ->
                        case lists:keymember(FactionX, 1, NamesAcc) of
                            true  ->
                                NamesAcc;
                            false ->
                                FactionName = data_czone:faction_name(FactionX),
                                [{FactionX, FactionName} | NamesAcc]
                        end
                end,
                FactionNames, ?FACTION_LIST);
        Result ->
            Result
    end;
get_faction_name(ZoneId, Faction) when is_integer(Faction) ->
    case cross_zone:call(?SERVER, {get_faction_name, ZoneId, Faction}) of
        {ok, <<>>} ->
            {ok, data_czone:faction_name(Faction)};
        Result ->
            Result
    end.

%% 计算定时器时间 -> {NowTime, NextTimerTime, RemainSecs}
calc_timer_secs(Type) ->
    NowTime = utime:unixtime(),
    calc_timer_secs(Type, NowTime).

calc_timer_secs(Type, NowTime) ->
    Time =
    case Type of
        pre_timer       -> ?PRE_TIME;
        confirm_timer   -> ?CONFIRM_TIME;
        notice_timer    -> ?NOTICE_TIME;
        rezone_timer    -> ?REZONE_TIME;
        _               -> throw({error, {timer_type, Type}})
    end,
    {Date, _Time} = utime:unixtime_to_localtime(NowTime),
    TodayTimerTime = utime:unixtime({Date, Time}),
    NextTimerTime =
    case NowTime >= TodayTimerTime of
        true  ->
            NextDate = utime:get_before_day(Date, 1, 1),
            utime:unixtime({NextDate, Time});
        false ->
            TodayTimerTime
    end,
    % ?INFO(
    %     "calc_timer_secs: Type - ~w, NowTime - ~w, ~w~n"
    %     "TodayTimerTime : ~w, NextTimerTime : ~w~n"
    %     "RemainSecs: ~w",
    %     [
    %         Type, NowTime, utime:unixtime_to_localtime(NowTime),
    %         TodayTimerTime, NextTimerTime,
    %         NextTimerTime - NowTime
    %     ]),
    {NowTime, NextTimerTime, NextTimerTime - NowTime}.

-spec to_czone_svr_local_list(ZoneInfo) -> CZoneSvrLocalList when
    ZoneInfo :: #zone_info{},
    CZoneSvrLocalList :: [ {ServerId, CZoneSvrLocal} ],
    ServerId :: non_neg_integer(),
    CZoneSvrLocal :: #czone_svr_local{}.
%% 转换为游戏服节点使用的缓存数据
to_czone_svr_local_list(#zone_info{
        zone_id = ZoneId,
        zone_ext = #czone_info{stage = Stage},
        servers = ServerList
    }) ->
    [
        {ServerId, to_czone_svr_local(ZoneId, Stage, Faction)} ||
        #zone_svr{
            server_id = ServerId,
            svr_ext = #czone_svr{faction = Faction}
        } <- ServerList
    ];
to_czone_svr_local_list(_) ->
    [].

%% 组装为游戏服节点缓存的数据
to_czone_svr_local(ZoneId, Stage, #zone_svr{svr_ext = #czone_svr{faction = Faction}}) ->
    to_czone_svr_local(ZoneId, Stage, Faction);
to_czone_svr_local(ZoneId, Stage, Faction) ->
    #czone_svr_local{
        zone_id = ZoneId
        ,stage = Stage
        ,faction = Faction
    }.

%% gm timer（在游戏服节点调用）
%% 用于加速“预分区-确认-通知-重新分区”的过程)
gm_timer(GmTimerType) ->
    GmTimerTypeList = [
        {"pre", pre_timer}
        ,{"confirm", confirm_timer}
        ,{"notice", notice_timer}
        ,{"rezone", rezone_timer}
    ],
    case lists:keyfind(GmTimerType, 1, GmTimerTypeList) of
        {_, TimerType} ->
            Msg = {timeout, undefined, TimerType},
            clusters_node:apply_cast(erlang, send, [?SERVER, Msg]);
        false ->
            ignore
    end,
    ok.

%% gm初始跨服分区-清空分区，重新划分为0阶分区（在游戏服节点调用）
gm_reset_zones() ->
    %% 分区管理进程执行的函数
    FunCleanZones = fun(ZoneState) ->
            ZoneIds = cross_zone:proc_get_zone_ids(),
            cross_zone:proc_erase_zones(ZoneIds),
            db:execute("DELETE FROM `center_zones`"),
            %% GM命令各功能需要清空数据，可添加在此处进行管理
            #zone_state{
                zone_sys = #czone_sys{
                    pre_timer = PreTimer
                    ,confirm_timer = ConfirmTimer
                    ,notice_timer = NoticeTimer
                    ,rezone_timer = RezoneTimer
                }
            } = ZoneState,
            NewCZoneSys = #czone_sys{
                pre_timer = PreTimer
                ,confirm_timer = ConfirmTimer
                ,notice_timer = NoticeTimer
                ,rezone_timer = RezoneTimer
            },
            {ok, #zone_state{
                    zone_sys = NewCZoneSys,
                    module_state = #cz_state{}
                }
            }
    end,
    %% 通知执行清空分区命令
    clusters_node:apply_cast(
        cross_zone, apply_cast_with_data, [?SERVER, {FunCleanZones, []}]
    ),
    FunReg = fun() ->
            #svr_reg_proc_state{reg_svr_list = RegSvrList} = sys:get_state(center_svr_reg),
            czone:add_reg_svr_list(RegSvrList)
    end,
    %% 通知注册游戏服
    clusters_node:apply_cast({FunReg, []}).

%% 跨服分区进行N次分区
gm_zone_n_times(N) ->
    FunSetWorldLv = fun() ->
            %% 从数据库中加载跨服分区服的世界等级（用于直接db编辑不在线的服的世界等级进行加载）
            DbSvrBases  = db:get_all(?SQL_SELECT_ZONE_SVR_BASE),
            [
                czone:update_svr_world_level(ServerId, WorldLv) ||
                [ServerId, _F, WorldLv | _] <- DbSvrBases
            ],
            %% 通知连接着的各服上传世界等级
            clusters_center:apply_to_all_node(czone_api, upload_world_level, [])
    end,
    %% 进行分区流程
    FunZone = fun() ->
            % 预分区
            czone ! {timeout, undefined, pre_timer},
            % 确认
            czone ! {timeout, undefined, confirm_timer},
            % 分区通知
            czone ! {timeout, undefined, notice_timer},
            % 分区
            czone ! {timeout, undefined, rezone_timer}
    end,
    %% 汇总操作
    Fun = fun() ->
            FunSetWorldLv(),
            timer:sleep(200),
            AllStages = data_czone:all_stages(),
            MaxStage = case AllStages of
                [_|_] -> lists:max(AllStages);
                _ -> 1
            end,
            Times = erlang:min(erlang:max(1, N), MaxStage),
            [ begin FunZone(), timer:sleep(100) end || _ <- lists:seq(1, Times)],
            ok
    end,
    clusters_node:apply_cast({Fun, []}).

%% 用于打印timer信息，确认有无问题
gm_read_timer() ->
    ZS = cross_zone:gm_get_zone_sys(?SERVER),
    case ZS of
        #czone_sys{
            pre_timer = PreTimer,
            confirm_timer = ConfirmTimer,
            notice_timer = NoticeTimer,
            rezone_timer = RezoneTimer
        } ->
            ?INFO_TTY("~n"
                "\ttimer type    : Timer                  RemainMicroSecs~n"
                "\tpre_timer     : ~-22w ~w~n"
                "\tconfirm_timer : ~-22w ~w~n"
                "\tnotice_timer  : ~-22w ~w~n"
                "\trezone_timer  : ~-22w ~w~n",
                [
                    PreTimer, erlang:read_timer(PreTimer),
                    ConfirmTimer, erlang:read_timer(ConfirmTimer),
                    NoticeTimer, erlang:read_timer(NoticeTimer),
                    RezoneTimer, erlang:read_timer(RezoneTimer)
                ]
            );
        _Error ->
            ?INFO_TTY("error: ~p", [_Error])
    end.

%% 存储分区数据到center_zones，用于测试验证数据
gm_save_zones() ->
    F = fun(S) ->
            Zones = cross_zone:proc_get_zones(),
            db_save_center_zones(Zones),
            S
    end,
    sys:replace_state(?SERVER, F).

%%%===================================================================
%%% cross_zone callbacks
%%%===================================================================

init(_) ->
    self() ! init_fire,
    %% 加载已存储的分区数据
    ZoneInfos = load_czones(),
    %% 加载已存储的预分区数据
    {PreRezoneData, ConfirmRezoneData} = load_rezone_infos(),
    cross_zone:proc_put_zones(ZoneInfos),
    [SvrFMap, ZoneFSvrsMap] = generate_faction_map(ZoneInfos),
    %% 如果在停服维护期间，需要分区及确认的数据未执行处理，开服后不修正，等待下一次机会处理
    {NowTime, _, PreRemainTime} = calc_timer_secs(pre_timer),
    {_, _, ConfirmRemainTime}   = calc_timer_secs(confirm_timer, NowTime),
    {_, _, NoticeRemainTime}    = calc_timer_secs(notice_timer, NowTime),
    {_, _, RezoneRemainTime}    = calc_timer_secs(rezone_timer, NowTime),
    PreTimer    = set_timer(pre_timer, undefined, PreRemainTime),
    ConfirmTimer= set_timer(confirm_timer, undefined, ConfirmRemainTime),
    NoticeTimer = set_timer(notice_timer, undefined, NoticeRemainTime),
    RezoneTimer = set_timer(rezone_timer, undefined, RezoneRemainTime),
    %% 只有通知后还未分区，下一次通知时间才会大于分区时间，以此反推出是否已通知的状态
    NoticeState = NoticeRemainTime > RezoneRemainTime,
    CZoneSys = #czone_sys{
        pre_timer = PreTimer
        ,confirm_timer = ConfirmTimer
        ,notice_timer = NoticeTimer
        ,rezone_timer = RezoneTimer
        ,pre_rezone_data = PreRezoneData
        ,notice_state = NoticeState
        ,confirm_rezone_data = ConfirmRezoneData
        ,svr_faction_map = SvrFMap
        ,zone_faction_svrs_map = ZoneFSvrsMap
    },
    CZState = #cz_state{},
    ZoneType = ?MODULE,
    #zone_init_ret{
        zone_type       = ZoneType
        ,later_join     = true      % 必须设置为true，才能在节点连接时更新连接状态
        ,num_per_zone   = 0
        ,delay_time     = false
        ,init_zones     = ZoneInfos
        ,zone_sys       = CZoneSys
        ,module_state   = CZState
    }.

%% 添加新正式注册的游戏服，初始化相应分区数据
handle_event(cast, {add_reg_svr_list, AddRegSvrList}, ZoneState = #zone_state{
        svr_zone_map = SvrZoneMap
        ,max_zone_id = MaxZoneId
        ,zone_sys = CZoneSys
        ,module_state = CZState
    }) ->
    ?INFO("Add reg svr list:~w", [AddRegSvrList]),
    %% 计算分区数据更新
    {
        NewSvrZoneMap,
        ZoneUpdateList,
        ZoneSvrAddList
    } = add_reg_svr_to_zone(AddRegSvrList, SvrZoneMap, [], []),
    %% 存储分区数据到DB
    Result =
    try
        ok = db:transaction(
            fun() ->
                    db_save_center_zones(ZoneUpdateList),
                    db_save_svr_base_list(ZoneSvrAddList),
                    ok
            end
        )
    catch
        ?EXCEPTION(_Class, _Reason, _Stacktrace) ->
            ?ERR("add reg svr error:~w,~n  ~p", [{_Class, _Reason}, ?GET_STACK(_Stacktrace)]),
            %% 间隔一段时间尝试重新注册
            proc_lib:spawn(fun() ->
                        RandTime = urand:rand(5000, 15000),
                        timer:sleep(RandTime),
                        czone:add_reg_svr_list(AddRegSvrList)
                end),
            error
    end,
    case Result of
        ok ->
            %% 存储分区数据到进程
            cross_zone:proc_put_zones(ZoneUpdateList),
            %% 更新其他数据
            ZoneIds = cross_zone:proc_get_zone_ids(),
            NewMaxZoneId = case ZoneIds of
                [_|_] -> lists:max(ZoneIds);
                _ -> MaxZoneId
            end,
            %% 通知相关数据更新服务器上传世界等级数据
            proc_lib:spawn_link(fun() ->
                        notice_upload_world_level(ZoneSvrAddList)
                end),
            NewCZoneSys = czone_sys_faction_map_update_zone(CZoneSys, ZoneUpdateList),
            NewCZState =
            try
                AddServers = conv_add_svrs_param(ZoneSvrAddList, NewSvrZoneMap),
                #cz_state{} = czone_fire:add_svrs(CZState, AddServers)
            catch
                ?EXCEPTION(_Class2, _Reason2, _Stacktrace2) ->
                    ?ERR("czone_fire:add_svrs/2 return invalid:~w,~n  ~p", [
                            {_Class2, _Reason2}, ?GET_STACK(_Stacktrace2)
                        ]),
                    CZState
            end,
            %% 通知clusters_center进程执行回调（更新本进程的新增节点资料）
            ServerIdAddList = [ServerId || #zone_svr{server_id = ServerId} <- ZoneSvrAddList],
            clusters_center:proc_apply({?MODULE, check_update_svr_info, [ServerIdAddList]}),
            NewZoneState = ZoneState#zone_state{
                svr_zone_map = NewSvrZoneMap
                ,max_zone_id = NewMaxZoneId
                ,zone_sys = NewCZoneSys
                ,module_state = NewCZState
            },
            {ok, NewZoneState};
        _Err ->
            ok
    end;

%% 更新世界等级
handle_event(cast, {update_svr_world_level, ServerId, WorldLv}, #zone_state{
        svr_zone_map = SvrZoneMap
    }) ->
    case maps:get(ServerId, SvrZoneMap, undefined) of
        undefined -> ok;
        ZoneId ->
            case cross_zone:proc_get_zone(ZoneId) of
                #zone_info{
                    servers = ServerList,
                    zone_ext = CZoneInfo
                } = ZoneInfo ->
                    case cross_zone_api:find_server(ServerId, ServerList) of
                        #zone_svr{svr_ext = #czone_svr{world_level = WorldLv}} ->
                            % 无变化
                            ignore;
                        ZoneSvr = #zone_svr{svr_ext = CZoneSvr = #czone_svr{}} ->
                            NewCZoneSvr = CZoneSvr#czone_svr{world_level = WorldLv},
                            NewZoneSvr  = ZoneSvr#zone_svr{svr_ext = NewCZoneSvr},
                            ZoneInfoUpateSvr = cross_zone_api:store_to_zone_info(NewZoneSvr, ZoneInfo),
                            #zone_info{servers = NewServerList} = ZoneInfoUpateSvr,
                            NewWorldLvAvg = world_lv_avg(NewServerList),
                            NewCZoneInfo = CZoneInfo#czone_info{world_lv_avg = NewWorldLvAvg},
                            NewZoneInfo = ZoneInfoUpateSvr#zone_info{zone_ext = NewCZoneInfo},
                            %% 存储至数据库
                            db_save_svr_base_list([NewZoneSvr]),
                            cross_zone:proc_put_zone_only(NewZoneInfo);
                        _False ->
                            % 缺数据，不正常，忽略
                            ignore
                    end;
                false ->
                    ignore
            end
    end;

%% 设置各阵营活跃度信息
handle_event(cast, {vitality, ZoneVitalityList}, _State) ->
    proc_set_vitality(ZoneVitalityList),
    ok;

%% 设置分区阵营个性化名称
handle_event(cast, {set_faction_name, ZoneId, Faction, FactionNameBin}, _State) ->
    case cross_zone:proc_get_zone(ZoneId) of
        ZoneInfo = #zone_info{zone_ext = CZoneInfo = #czone_info{faction_names = FactionNames}} ->
            case lists:member(Faction, ?FACTION_LIST) of
                true  ->
                    NewFactionNames = [
                        {Faction, FactionNameBin} |
                        lists:keydelete(Faction, 1, FactionNames)
                    ],
                    NewCZoneInfo = CZoneInfo#czone_info{faction_names = NewFactionNames},
                    NewZoneInfo = ZoneInfo#zone_info{zone_ext = NewCZoneInfo},
                    cross_zone:proc_put_zone_only(NewZoneInfo),
                    db_save_center_zones([NewZoneInfo]);
                false ->
                    ignore
            end;
        _ -> ignore
    end,
    ok;

%% 异步获取阵营数据
handle_event(cast, {async_get_faction_map, Type, {M, F, A}}, State) ->
    {reply, Reply} = handle_event({call, undefined}, {get_faction_map, Type}, State),
    erlang:apply(M, F, [Reply | A]),
    ok;

%% 指定分区中，对应特定阵营的游戏服执行某个Function
handle_event(cast, {zone_faction_apply, ZoneId, Faction, MFA}, _State) ->
    case cross_zone:proc_get_zone(ZoneId) of
        #zone_info{servers = ServerList} ->
            lists:foreach(
                fun (#zone_svr{
                            node = Node, conn = true,
                            svr_ext = #czone_svr{faction = FactionX}
                        }
                    ) when FactionX =:= Faction ->
                        rpc:cast(Node, util, do_mfa, [MFA]);
                    (_) ->
                        ignore
                end,
                ServerList);
        _ ->
            ignore
    end,
    ok;

%% 获取分区阵营个性化名称
handle_event({call, _From}, {get_faction_name, ZoneId, Faction}, _ZoneState) ->
    case cross_zone:proc_get_zone(ZoneId) of
        #zone_info{zone_ext = #czone_info{faction_names = FactionNames}} ->
            case Faction of
                all ->
                    {reply, {ok, FactionNames}};
                _ ->
                    case lists:keyfind(Faction, 1, FactionNames) of
                        {_Faction, FactionNameBin} ->
                            {reply, {ok, FactionNameBin}};
                        _ ->
                            {reply, {ok, <<>>}}
                    end
            end;
        _ ->
            {reply, {error, invalid_zone}}
    end;

%% 获取分区信息
handle_event({call, _From}, {get_czone_info, ServerId}, #zone_state{
        svr_zone_map = SvrZoneMap
    }) ->
    case maps:get(ServerId, SvrZoneMap, undefined) of
        undefined ->
            {reply, unopened};
        ZoneId ->
            case cross_zone:proc_get_zone(ZoneId) of
                #zone_info{zone_ext = CZoneInfo, servers = ServerList} ->
                    #czone_info{stage = Stage, faction_names = FactionNames} = CZoneInfo,
                    FactionDataList = [
                        {Faction, ServerIdX} ||
                        #zone_svr{
                            server_id = ServerIdX
                            ,svr_ext = #czone_svr{faction = Faction}
                        } <- ServerList
                    ],
                    {reply, {ok, Stage, FactionNames, FactionDataList}};
                _False ->
                    {reply, error}
            end
    end;

%% 获取合区预告（即已确认将要执行的分区结果，对应confirm_rezone_data）
handle_event({call, _From}, {get_combine_advance, ServerId}, #zone_state{
        svr_zone_map = SvrZoneMap
        ,zone_sys = CZoneSys
    }) ->
    case maps:get(ServerId, SvrZoneMap, undefined) of
        undefined ->
            {reply, unopened};
        ZoneId ->
            #czone_sys{confirm_rezone_data = ConfirmRezoneData} = CZoneSys,
            RezoneInfo =
            case lists:keyfind(ZoneId, #rezone_info.zone_id, ConfirmRezoneData) of
                #rezone_info{} = RezoneInfoX ->
                    RezoneInfoX;
                false ->
                    case lists:keyfind(ZoneId, #rezone_info.zone_id_2, ConfirmRezoneData) of
                        #rezone_info{} = RezoneInfoX ->
                            RezoneInfoX;
                        false ->
                            false
                    end
            end,
            Reply =
            case RezoneInfo of
                false ->
                    {ok, 0, 0, [], []};
                _ ->
                    calc_rezone_advance(RezoneInfo)
            end,
            {reply, Reply}
    end;

%% 获取再分区进度数据
handle_event({call, _From}, {get_rezone_progress, ServerId}, #zone_state{
        svr_zone_map = SvrZoneMap
    }) ->
    case maps:get(ServerId, SvrZoneMap, undefined) of
        undefined ->
            {reply, unopened};
        ZoneId ->
            Reply = calc_rezone_progress(ZoneId),
            {reply, Reply}
    end;

%% 获取再分区通知状态
handle_event({call, _From}, {get_rezone_notice, ServerId}, #zone_state{
        svr_zone_map = SvrZoneMap
        ,zone_sys = CZoneSys
    }) ->
    case maps:get(ServerId, SvrZoneMap, undefined) of
        undefined ->
            {reply, unopened};
        ZoneId ->
            #czone_sys{
                confirm_rezone_data = ConfirmRezoneData
            } = CZoneSys,
            case find_rezone_info(ZoneId, ConfirmRezoneData) of
                #rezone_info{scheduled_time = ScheduledTime} ->
                    {reply, {ok, ScheduledTime}};
                _False ->
                    Data = {ok, 0},
                    {reply, Data}
            end
    end;

%% 获取分区阵营相关映射关系
handle_event({call, _From}, {get_faction_map, Type}, #zone_state{
        zone_sys = CZoneSys
    }) ->
    #czone_sys{
        svr_faction_map = SvrFMap,
        zone_faction_svrs_map = ZoneFSvrsMap
    } = CZoneSys,
    ReplyData =
    case Type of
        svr_faction_map -> SvrFMap;
        zone_faction_svrs_map -> ZoneFSvrsMap;
        all ->
            #{
                svr_faction_map => SvrFMap,
                zone_faction_svrs_map => ZoneFSvrsMap
            };
        _ ->
            undefined
    end,
    {reply, ReplyData};

handle_event({call, _From}, {get_scheduled_time, ServerId}, #zone_state{
        svr_zone_map = SvrZoneMap
        ,zone_sys = CZoneSys
    }) ->
    #czone_sys{
        confirm_rezone_data = ConfirmRezoneData
        ,notice_state = NoticeState
    } = CZoneSys,
    case maps:get(ServerId, SvrZoneMap, undefined) of
        undefined ->
            {reply, {NoticeState, undefined, 0, 0}};
        ZoneId ->
            case find_rezone_info(ZoneId, ConfirmRezoneData) of
                #rezone_info{scheduled_time = ScheduledTime, next_stage = NextStage} ->
                    {reply, {NoticeState, ZoneId, ScheduledTime, NextStage}};
                _ ->
                    {reply, {NoticeState, ZoneId, 0, 0}}
            end
    end;

handle_event({call, _From}, {get_zone_svr_local, ServerId}, #zone_state{
        svr_zone_map = SvrZoneMap
    }) ->
    case maps:get(ServerId, SvrZoneMap, undefined) of
        undefined ->
            {reply, undefined};
        ZoneId ->
            case cross_zone:proc_get_zone(ZoneId) of
                ZoneInfo = #zone_info{zone_id = ZoneId, zone_ext = #czone_info{stage = Stage}} ->
                    case cross_zone_api:find_server(ServerId, ZoneInfo) of
                        #zone_svr{} = ZoneSvr ->
                            CZoneSvrLocal = to_czone_svr_local(ZoneId, Stage, ZoneSvr),
                            {reply, CZoneSvrLocal};
                        _ ->
                            {reply, error}
                    end;
                _ ->
                    {reply, error}
            end
    end;

handle_event({call, _From}, {get_zone_stage, ZoneId}, _ZoneState) ->
    ZoneStage = proc_get_zone_stage(ZoneId),
    {reply, ZoneStage};

handle_event({call, _From}, get_zone_stages, _ZoneState) ->
    ZoneStages = proc_get_zone_stages(),
    {reply, ZoneStages};

handle_event({call, _From}, {get_zone_world_lv, ZoneId}, _ZoneState) ->
    case cross_zone:proc_get_zone(ZoneId) of
        #zone_info{zone_id = ZoneId, zone_ext = #czone_info{world_lv_avg = Lv}} ->
            {reply, Lv};
        _ ->
            {reply, error}
    end;

%% 分区流程中的定时器触发
handle_event(info, {timeout, _OldTimer, TimerType}, ZoneState = #zone_state{
        zone_sys = CZoneSys
    }) ->
    % ?INFO("msg timer: ~w", [{timeout, _OldTimer, TimerType}]),
    %% NOTE:
    %%   各操作写DB是否考虑优化改造为失败自动延后重试的方式（rezone本身已有）
    %%   不改造的情况下，失败就要等待明天下一次操作，问题也不大
    case TimerType of
        pre_timer ->
            #czone_sys{pre_timer = OldTimer} = CZoneSys,
            NewTimer = set_timer(TimerType, OldTimer),
            TmpCZoneSys = pre_rezone(CZoneSys, TimerType),
            NewCZoneSys = TmpCZoneSys#czone_sys{pre_timer = NewTimer},
            {ok, ZoneState#zone_state{zone_sys = NewCZoneSys}};
        confirm_timer ->
            #czone_sys{confirm_timer = OldTimer} = CZoneSys,
            NewTimer = set_timer(TimerType, OldTimer),
            TmpCZoneSys = confirm_rezone(CZoneSys),
            NewCZoneSys = TmpCZoneSys#czone_sys{confirm_timer = NewTimer},
            {ok, ZoneState#zone_state{zone_sys = NewCZoneSys}};
        notice_timer ->
            %% 1. 检查有无0/1阶分区，对低阶分区直接合并进行pre_rezone及confirm_rezone操作
            %% 2. 向相关服发送通知和做关闭入口之类的管理
            #czone_sys{notice_timer = OldTimer} = CZoneSys,
            NewTimer = set_timer(TimerType, OldTimer),
            TmpCZoneSys1 = pre_rezone(CZoneSys, TimerType),
            TmpCZoneSys2 = confirm_rezone(TmpCZoneSys1),
            notice_rezone(TmpCZoneSys2),
            NewCZoneSys = TmpCZoneSys2#czone_sys{
                notice_timer = NewTimer
                ,notice_state = true
            },
            {ok, ZoneState#zone_state{zone_sys = NewCZoneSys}};
        rezone_timer ->
            #czone_sys{rezone_timer = OldTimer} = CZoneSys,
            NewTimer = set_timer(TimerType, OldTimer),
            %% 为了错开重新分区时机及避免单次失败对整体影响，
            %% 通过额外消息执行真正的分区过程，每次只处理一个分区合并任务
            check_rezone_task(CZoneSys, 0),
            NewCZoneSys = CZoneSys#czone_sys{rezone_timer = NewTimer},
            NewZoneState = ZoneState#zone_state{zone_sys = NewCZoneSys},
            {ok, NewZoneState}
    end;

%% 执行重新分区任务（每次执行单个任务-两个子分区合并）
handle_event(info, {rezone, ErrCount}, ZoneState = #zone_state{
        svr_zone_map = SvrZoneMap
        ,zone_sys = CZoneSys = #czone_sys{confirm_rezone_data = [_|_]}
        ,module_state = CZState
    }) ->
    #czone_sys{
        confirm_rezone_data = ConfirmRezoneData
        ,svr_faction_map = SvrFMap
        ,zone_faction_svrs_map = ZoneFSvrsMap
        ,notice_state = OldNoticeState
    } = CZoneSys,
    #{
        error := IsErr
        ,svr_zone_map := NewSvrZoneMap
        ,svr_faction_map := NewSvrFMap
        ,zone_faction_svrs_map := NewZoneFSvrsMap
        ,zone_change_map := ZoneChangeMap
        ,faction_change_map := FactionChangeMaps
        ,confirm_rezone_data := NewConfirmRezoneData
        ,new_zone_info := NewZoneInfo
    } = rezone(SvrZoneMap, ConfirmRezoneData, SvrFMap, ZoneFSvrsMap),
    NoticeState =
    case NewConfirmRezoneData of
        [] -> false;
        _  -> OldNoticeState
    end,
    NewCZoneSys = CZoneSys#czone_sys{
        confirm_rezone_data = NewConfirmRezoneData
        ,svr_faction_map = NewSvrFMap
        ,zone_faction_svrs_map = NewZoneFSvrsMap
        ,notice_state = NoticeState
    },
    NewErrCount =
    case IsErr of
        true  -> ErrCount + 1;
        false -> 0
    end,
    check_rezone_task(NewCZoneSys, NewErrCount),
    NewCZState =
    case IsErr of
        true  -> CZState;
        false ->
            try
                #cz_state{} = czone_fire:rezone(CZState, NewSvrZoneMap, ZoneChangeMap, FactionChangeMaps, NewZoneInfo)
            catch
                ?EXCEPTION(_Class, _Reason, _Stacktrace) ->
                    ?ERR("czone_fire:rezone/4 return invalid:~w,~n  ~p", [
                            {_Class, _Reason}, ?GET_STACK(_Stacktrace)
                        ]),
                    CZState
            end
    end,
    %% 更新活跃度管理器数据
    case NewConfirmRezoneData of
        [] -> svr_vitality_center_mgr:set_zone_faction(NewZoneFSvrsMap);
        _  -> ignore
    end,
    NewZoneState = ZoneState#zone_state{
        svr_zone_map = NewSvrZoneMap
        ,zone_sys = NewCZoneSys
        ,module_state = NewCZState
    },
    {ok, NewZoneState};

%% 进程启动完成初步初始化之后触发，
%% 进行其他功能数据初始及回调管理
handle_event(info, init_fire, #zone_state{
        svr_zone_map = SvrZoneMap
        ,module_state = CZState
    }) ->
    %% 将分区中已存在的ServerId资料发送给center_svr_reg注册服管理进程，
    %% 以便检查有无注册触发分区失败的服，进行补全管理
    ServerIds = maps:keys(SvrZoneMap),
    center_svr_reg:check_zone_omit_svrs(ServerIds),
    %% 初始加载各功能系统业务数据
    try
        NewCZState = #cz_state{} = czone_fire:init(CZState, SvrZoneMap),
        {ok, NewCZState}
    catch
        ?EXCEPTION(_Class, _Reason, _Stacktrace) ->
            ?ERR("czone_fire:init/2 return invalid:~w,~n  ~p", [
                    {_Class, _Reason}, ?GET_STACK(_Stacktrace)
                ]),
            ok
    end;

handle_event(_, _, _) ->
    ok.

%% 节点连接
%% （在此种分区类型下，不会是新分区，等于是节点重连）
add_node(_IsNewZoneId, ZoneSvr, ZoneInfo, CZState) ->
    %% 回调更新数据
    NewCZState =
    try
        czone_fire:conn(CZState, ZoneInfo, ZoneSvr)
    catch
        ?EXCEPTION(_Class, _Reason, _Stacktrace) ->
            ?ERR("czone:add_node/4 return invalid:~w,~n  ~p", [
                    {_Class, _Reason}, ?GET_STACK(_Stacktrace)
                ]),
            CZState
    end,
    NewCZState.

%%%===================================================================
%%% Internal functions
%%%===================================================================

%% 初始加载分区数据
load_czones() ->
    DbZonesInfo = db:get_all(?SQL_SELECT_CZONES),
    DbSvrBases  = db:get_all(?SQL_SELECT_ZONE_SVR_BASE),
    DbSvrBaseAcc = [list_to_tuple(SvrBase) || SvrBase <- DbSvrBases],
    {ZoneInfos, _} = lists:foldl(
        fun(
                DbZoneInfo = [_ZoneId, _Stage, _ServerIdsBin, _FactionNamesBin],
                {PDZoneInfosAcc, SvrBaseAcc}
            ) ->
                {ZoneInfo, NewSvrBaseAcc} = db_to_zone_info(DbZoneInfo, SvrBaseAcc),
                NewPDZoneInfosAcc = [ZoneInfo | PDZoneInfosAcc],
                {NewPDZoneInfosAcc, NewSvrBaseAcc};
            (_, Acc) ->
                Acc
        end,
        {[], DbSvrBaseAcc},
        DbZonesInfo),
    ZoneInfos.

db_to_zone_info([ZoneId, Stage, ServerIdsBin, FactionNamesBin], SvrBase) ->
    ServerIds = case util:bitstring_to_term(ServerIdsBin) of
        [_|_] = Ids -> Ids;
        _ -> fix_server_ids(ZoneId, Stage)
    end,
    {ZoneSvrs, NewSvrBaseAcc} = lists:foldl(
        fun db_to_zone_svr/2,
        {[], SvrBase},
        ServerIds
    ),
    WorldLvAvg = world_lv_avg(ZoneSvrs),
    CZoneInfo = #czone_info{
        stage = Stage
        ,world_lv_avg = WorldLvAvg
        ,faction_names = read_fmt_faction_names(FactionNamesBin)
    },
    ZoneInfo = #zone_info{
        zone_id = ZoneId
        ,servers = ZoneSvrs
        ,zone_ext = CZoneInfo
    },
    {ZoneInfo, NewSvrBaseAcc}.

%% 处理节点数据生成处理
db_to_zone_svr(ServerId, {ZoneSvrList, SvrBaseAcc}) ->
    {CZoneSvr, NewSvrBaseAcc} =
    case lists:keytake(ServerId, 1, SvrBaseAcc) of
        {value, {_, Faction, WorldLevel}, RemainSvrBase} ->
            {
                #czone_svr{faction = Faction, world_level = WorldLevel},
                RemainSvrBase
            };
        _ ->
            %% FIXME：
            %% 出错了，正常不应该没有数据（db未保存成功），
            %% 是否需要再考虑特殊情况的补救措施？
            {
                #czone_svr{},
                SvrBaseAcc
            }
    end,
    %% 如果缺少数据，就不正常，应该要通知主服上传数据
    ServerNum = query_server_num(ServerId),
    ZoneSvr = #zone_svr{
        server_id = ServerId
        ,server_num = ServerNum
        ,conn = false
        ,node = undefined
        ,svr_ext = CZoneSvr
    },
    {[ZoneSvr | ZoneSvrList], NewSvrBaseAcc}.

%% load_rezone_infos() -> {PreRezoneData, ConfirmRezoneData}.
load_rezone_infos() ->
    DbRezoneInfos = db:get_all(?SQL_SELECT_REZONE_INFO),
    lists:foldl(
        fun db_to_rezone_info/2,
        {[], []},
        DbRezoneInfos
    ).

db_to_rezone_info(DbRezoneInfo, {PreRezoneData, ConfirmRezoneData}) ->
    [ZoneId, ZoneId2, NextStage, SvnFactionsBin, ScheduledTime, Confirm] = DbRezoneInfo,
    SvnFactions = case util:bitstring_to_term(SvnFactionsBin) of
        [_|_] = Result -> Result;
        _ ->
            %% 不正常，在db字段长度充足情况下，通常不可能，
            %% 如有问题，可尝试读取ZoneId与ZoneId2中的数据进行修复，
            %% 目前这里忽略处理
            []
    end,
    RezoneInfo = #rezone_info{
        zone_id = ZoneId
        ,zone_id_2 = ZoneId2
        ,next_stage = NextStage
        ,svr_factions = SvnFactions
        ,scheduled_time = ScheduledTime
    },
    case Confirm of
        1 -> {PreRezoneData, [RezoneInfo | ConfirmRezoneData]};
        _ -> {[RezoneInfo | PreRezoneData], ConfirmRezoneData}
    end.

%% 格式化从db中读取出来的faction_names二进制字符串
read_fmt_faction_names(<<>>) -> [];
read_fmt_faction_names(FactionNamesBin) ->
    %% 存储的是特殊格式化后字符串，不能用bitstring_to_term来转换
    String = unicode:characters_to_list(FactionNamesBin),
    case util:string_to_term(String) of
        [_|_] = List ->
            [
                {Faction, unicode:characters_to_binary(FactionName)} ||
                {Faction, FactionName} <- List
            ];
        _ -> []
    end.

%% 格式化faction_names数据，用于db写入
write_fmt_faction_names([_|_] = FactionNames) ->
    %% 处理成此类格式是为了db中可见
    %% 如果直接term_to_bitstring，需要扩大db字段长度
    %% （term_to_bitstring存储的是原始unicode码相关数据）
    BinStr = fmt_faction_names(FactionNames, <<>>),
    list_to_binary([<<"[">>, BinStr, <<"]">>]);
write_fmt_faction_names(_) ->
    <<"[]">>.

fmt_faction_names([{Faction, FactionName} | Others], Acc) ->
    Sep =
    case Acc of
        <<"">> -> <<>>;
        _ -> <<",">>
    end,
    NewAcc = <<"{", (integer_to_binary(Faction))/binary, ",\"", FactionName/binary, "\"}", Sep/binary, Acc/binary>>,
    fmt_faction_names(Others, NewAcc);
fmt_faction_names([_|Others], Acc) ->
    fmt_faction_names(Others, Acc);
fmt_faction_names([], Acc) ->
    Acc.

%% 注意：
%% 如果需要可编辑游戏服所在分区，则与此规则冲突，底层设计需要考虑调整
%% 以Stage和RegOrder计算出该分区最小的RegOrder，再来决定ZoneId
%% 与to_reg_order中的规则相对应
to_zone_id(CenterSvrId, Stage, MainRegOrder) ->
    {MinRegOrder, _MaxRegOrder} = calc_reg_orders(Stage, MainRegOrder),
    <<ZoneId:32>> = <<CenterSvrId:16, MinRegOrder:16>>,
    ZoneId.

%% 与to_zone_id中的规则相对应
to_reg_order(ZoneId) ->
    <<CenterSvrId:16, RegOrder:16>> = <<ZoneId:32>>,
    {CenterSvrId, RegOrder}.

%% zone_svr与zone_info是存储于进程字典的，
%% 分区的基本资料需要存储于此，其他资料可以选择存在进程字典，
%% 也可以选择存储在进程State的模块状态数据中（module_state）

%% 尝试从其他数据中修复还原对应的ServerId
%% NOTE:
%% 只有运营不能调整游戏服所处分区，才可以适用，
%% 如果对运营提供该功能，则此接口会有问题
fix_server_ids(ZoneId, Stage) ->
    {CenterSvrId, RegOrder} = to_reg_order(ZoneId),
    %% 根据对应的阶段，用数学方法计算出注册序号，
    %% 再找出对应序号在跨服中的游戏服id
    {MinRegOrder, MaxRegOrder} = calc_reg_orders(Stage, RegOrder),
    %% 基于现有设计，
    %% 可以从ets表中匹配出 MinRegOrder到MaxRegOrder 之间的数据
    %% 或者是从db中直接查询（方便）
    center_svr_reg:find_server_ids(CenterSvrId, MinRegOrder, MaxRegOrder).

%% 通过当前Stage，计算出RegOrder相应的区间范围
%% RegOrder应该落在区间[MinRegOrder, MaxRegOrder]中
calc_reg_orders(Stage, RegOrder) ->
    Pow = trunc(math:pow(2, Stage)),
    ZoneOrder = (RegOrder - 1) div Pow + 1,
    Min = (ZoneOrder - 1) * Pow + 1,
    Max = Min + Pow - 1,
    {Min, Max}.

%% 检查两个ZoneId是否相邻可合并分区的ZoneId
is_mate_zone(Stage, ZoneId1, ZoneId2) ->
    {CenterSvrId1, RegOrder1} = to_reg_order(ZoneId1),
    {CenterSvrId2, RegOrder2} = to_reg_order(ZoneId2),
    case CenterSvrId1 =:= CenterSvrId2 of
        true  ->
            NextStage = Stage + 1,
            calc_reg_orders(NextStage, RegOrder1) =:=
            calc_reg_orders(NextStage, RegOrder2);
        false ->
            false
    end.

%% 计算将要合并的ZoneId -> MateZoneId
%% 先计算出ZoneId对应的RegOrder
%% 根据Stage的2^N规则，计算出对应的另一个RegOrder
%% 再重新计算出MateZoneId
calc_next_stage_mate_zone_id(ZoneId, Stage) ->
    {CenterSvrId, RegOrder} = to_reg_order(ZoneId),
    {MinRegOrder, MaxRegOrder} = calc_reg_orders(Stage, RegOrder),
    {NextMinRegOrder, NextMaxRegOrder} = calc_reg_orders(Stage + 1, RegOrder),
    if
        MinRegOrder =:= NextMinRegOrder ->
            %% 最小值相等，说明另一个ZoneId对应的MaxRegOrder应该与NextMaxRegOrder相等
            %% 在不修改ZoneId计算规则情况下，可以用NextMaxRegOrder计算到ZoneId
            to_zone_id(CenterSvrId, Stage, NextMaxRegOrder);
        MaxRegOrder =:= NextMaxRegOrder ->
            %% 与上一情况相反
            to_zone_id(CenterSvrId, Stage, NextMinRegOrder);
        true ->
            %% 按照数学规则，正常情况是不可能走这个分支的
            to_zone_id(CenterSvrId, Stage, NextMinRegOrder)
    end.


%% 添加注册服资料到分区中
add_reg_svr_to_zone([RegSvr | RegSvrList], SvrZoneMap, ZoneUpdateAcc, SvrAddAcc) ->
    #reg_svr{
        server_id = ServerId
        ,svr_cid = SvrCid
    } = RegSvr,
    case maps:is_key(ServerId, SvrZoneMap) of
        true  ->
            %% 已经完成分区的服，忽略
            add_reg_svr_to_zone(RegSvrList, SvrZoneMap, ZoneUpdateAcc, SvrAddAcc);
        false when SvrCid > 0 ->
            {CenterSvrId, RegOrder} = center_svr_reg:svr_cid_to_reg_order(SvrCid),
            %% 刚加入到分区的时候，是单个服一个分区的，所以不需要计算，直接设置阵营
            Stage = 0,
            Faction = ?FACTION_1,
            ZoneId = to_zone_id(CenterSvrId, Stage, RegOrder),
            %% 初始数据，世界等级将在后面通知游戏服上传更新
            CZoneSvr = #czone_svr{ faction = Faction },
            %% ZoneSvr的数据有缺失，在全部注册服处理完后将异步更新资料
            ZoneSvr = #zone_svr{
                server_id = ServerId,
                server_num = query_server_num(ServerId),
                node = undefined,
                conn = false,
                svr_ext = CZoneSvr
            },
            CZoneInfo = #czone_info{
                stage = Stage,
                world_lv_avg = 0
            },
            ZoneInfo = #zone_info{
                zone_id = ZoneId,
                servers = [ZoneSvr],
                zone_ext = CZoneInfo
            },
            NewZoneUpdateAcc = [ZoneInfo | ZoneUpdateAcc],
            NewSvrAddAcc = [ZoneSvr | SvrAddAcc],
            NewSvrZoneMap = SvrZoneMap#{ServerId => ZoneId},
            add_reg_svr_to_zone(RegSvrList, NewSvrZoneMap, NewZoneUpdateAcc, NewSvrAddAcc);
        _False ->
            add_reg_svr_to_zone(RegSvrList, SvrZoneMap, ZoneUpdateAcc, SvrAddAcc)
    end;
add_reg_svr_to_zone([], SvrZoneMap, ZoneUpdateAcc, SvrAddAcc) ->
    {SvrZoneMap, ZoneUpdateAcc, SvrAddAcc}.

query_server_num(ServerId) ->
    case center_svr_reg:lookup_reg_svr(ServerId) of
        [#reg_svr{server_num = ServerNum}] -> ServerNum;
        _ -> 0
    end.

%% 根据[#zone_info{}]，保存到db中
db_save_center_zones(ZoneInfoList) ->
    % ?DEBUG_TTY("zoneinfo list :~p", [ZoneInfoList]),
    DataFmtFun = fun fmt_center_zone/1,
    SqlPrefix = ?SQL_REPLACE_CZONE_PREFIX,
    udb:save_data_list(ZoneInfoList, SqlPrefix, DataFmtFun).

%% 保存跨服分区单服基本信息
db_save_svr_base_list(ZoneSvrList) ->
    DataFmtFun = fun fmt_zone_svr_base/1,
    udb:save_data_list(ZoneSvrList, ?SQL_REPLACE_ZONE_SVR_BASE, DataFmtFun).

%% 保存跨服再分区资料
db_save_rezone_info(_, []) ->
    ignore;
db_save_rezone_info(IsConfirmed, RezoneInfoList) when IsConfirmed >= 0 andalso IsConfirmed =< 1 ->
    DataFmtFun = fun(Data) -> fmt_rezone_info(IsConfirmed, Data) end,
    SqlPrefix = ?SQL_REPLACE_REZONE_INFO_PREFIX,
    udb:save_data_list(RezoneInfoList, SqlPrefix, DataFmtFun).

fmt_center_zone(#zone_info{
        zone_id = ZoneId
        ,servers = ZoneSvrs
        ,zone_ext = #czone_info{stage = Stage, faction_names = FactionNames}
    }) ->
    ServerIds = [ServerId || #zone_svr{server_id = ServerId} <- ZoneSvrs],
    util:fmt_bin(<<"(~w,~w,'~ts','~ts')"/utf8>>,
        [ZoneId, Stage, util:term_to_bitstring(ServerIds), write_fmt_faction_names(FactionNames)]
    );
fmt_center_zone(_) ->
    <<>>.

fmt_zone_svr_base(#zone_svr{
        server_id = ServerId
        ,svr_ext = #czone_svr{
            faction = Faction
            ,world_level = WorldLevel
        }
    }) ->
    util:fmt_bin(<<"(~w,~w,~w)"/utf8>>, [ServerId, Faction, WorldLevel]);
fmt_zone_svr_base(_) ->
    <<>>.

fmt_rezone_info(IsConfirmed, #rezone_info{
        zone_id = ZoneId
        ,zone_id_2 = ZoneId2
        ,next_stage = NextStage
        ,svr_factions = SvnFactions
        ,scheduled_time = ScheduledTime
    }) ->
    SvnFactionsBinStr = util:term_to_bitstring(SvnFactions),
    util:fmt_bin(<<"(~w,~w,~w,'~ts',~w,~w)"/utf8>>, [
            ZoneId, ZoneId2, NextStage, SvnFactionsBinStr, ScheduledTime, IsConfirmed
        ]).

db_delete_czone(ZoneId) ->
    Sql = util:fmt_bin(?SQL_DEL_CZONE, [ZoneId]),
    db:execute(Sql).

db_delete_rezone_info(ZoneId) ->
    Sql = util:fmt_bin(?SQL_DEL_REZONE_INFO, [ZoneId]),
    db:execute(Sql).

%% -------------------------------------------------------
%% 活跃度数据存取
%% -------------------------------------------------------
proc_set_vitality([{ZoneId, FVitalityList} | ZFVitalityList]) ->
    put({vitality, ZoneId}, FVitalityList),
    proc_set_vitality(ZFVitalityList);
proc_set_vitality([]) ->
    ok.

%% 获取阵营活跃数据（用于分区时，阵营组合判断）
proc_get_vitality(ZoneId) ->
    case get({vitality, ZoneId}) of
        [_|_] = FVitalityList -> FVitalityList;
        _ -> []
    end.
%% -------------------------------------------------------

%% 分区预告数据
%% 新的ZoneId将为#rezone_info.zone_id
calc_rezone_advance(RezoneInfo) ->
    #rezone_info{
        zone_id = _ZoneId1
        ,zone_id_2 = _ZoneId2
        ,next_stage = NextStage
        ,svr_factions = NewSvnFactions
        ,scheduled_time = ScheduledTime
    } = RezoneInfo,
    NewFactionNames = [],
    {ok, ScheduledTime, NextStage, NewSvnFactions, NewFactionNames}.

%% 预分区
%% 通过筛选出符合条件的ZoneId，按小分区到大分区的顺序进行合并，
%% 可以先按Stage从小到大的顺序排位，再往上检查
%%
%% 需要注意：
%% pre_rezone_data中数据，
%% 分为两类，一类有计划分区时间，一类没有。
%% 没有分区计划时间的（可能是运营/GM提前编辑），
%% 也需要判断是否达成目标，确定最终的计划分区时间
pre_rezone(CZoneSys, TimerType) ->
    #czone_sys{
        pre_rezone_data = PreRezoneData
        ,confirm_rezone_data = ConfirmRezoneData
    } = CZoneSys,
    %% 可以改为拿到zone_ids，一个个按顺序检查，节省内存开销
    %% （不过意义并不大，其他地方会用到更多内存，且如此吧）
    ZoneInfoList = cross_zone:proc_get_zones(),
    NewPreRezoneData = gen_pre_rezone(ZoneInfoList, PreRezoneData, ConfirmRezoneData, TimerType),
    case TimerType of
        notice_timer  ->
            %% 通知阶段的pre_rezone，处理完数据后会进入确认阶段，还会写最新数据
            ignore;
        _PreTimer ->
            %% 数据存储到DB
            db_save_rezone_info(0, NewPreRezoneData)
    end,
    CZoneSys#czone_sys{
        pre_rezone_data = NewPreRezoneData
    }.

%% 确认分区
%% 存储在pre_rezone_data中的数据，
%% 如果分区合并数据中有计划分区时间，则移动到确认结果集
confirm_rezone(CZoneSys) ->
    #czone_sys{
        pre_rezone_data = PreRezoneData
        ,confirm_rezone_data = ConfirmRezoneData
    } = CZoneSys,
    {_, NextScheduledTime, _Remain} = calc_timer_secs(rezone_timer),
    {
        ConfirmAdd,
        PreRezoneDataRemain
    } = lists:partition(fun(RezoneInfo) ->
                case RezoneInfo of
                    #rezone_info{
                        scheduled_time = ScheduledTime
                    } when ScheduledTime > 0 andalso ScheduledTime =< NextScheduledTime ->
                        true;
                    _ ->
                        false
                end
        end, PreRezoneData),
    NewConfirmRezoneData = ConfirmRezoneData ++ ConfirmAdd,
    %% 数据存储
    db_save_rezone_info(1, ConfirmAdd),
    CZoneSys#czone_sys{
        pre_rezone_data = PreRezoneDataRemain
        ,confirm_rezone_data = NewConfirmRezoneData
    }.

%% 通知将要进行分区
%% 从confirm_rezone_data中取出数据，向相关服发送通知
%% 由对应玩法在回调中做提前关闭入口之类的管理
notice_rezone(CZoneSys) ->
    #czone_sys{
        confirm_rezone_data = ConfirmRezoneData
    } = CZoneSys,
    [
        begin
                czone_fire:notice_rezone(ZoneId1, ScheduledTime),
                czone_fire:notice_rezone(ZoneId2, ScheduledTime),
                ok
        end ||
        #rezone_info{
            zone_id = ZoneId1
            ,zone_id_2 = ZoneId2
            ,scheduled_time = ScheduledTime
        } <- ConfirmRezoneData
    ],
    ok.

%% -------------------------------------------------------
%% 分区正式进行调整相关数据处理
%% -------------------------------------------------------

%% 调整分区
%% rezone(SvrZoneMap, CZoneSys) -> #{}.
rezone(SvrZoneMap, [RezoneInfo | OtherRezoneInfos], SvrFMap, ZoneFSvrsMap) ->
    RezoneResult =
    try
        rezone_update(RezoneInfo, SvrZoneMap, SvrFMap, ZoneFSvrsMap)
    catch
        ?EXCEPTION(_Class, _Reason, _Stacktrace) ->
            ?ERR("rezone failed: {~w,~w},~n  ~p", [_Class, _Reason, ?GET_STACK(_Stacktrace)]),
            #{
                error => true
                ,svr_zone_map => SvrZoneMap
                ,svr_faction_map => SvrFMap
                ,zone_faction_svrs_map => ZoneFSvrsMap
                ,zone_change_map => #{}
                ,faction_change_map => #{}
            }
    end,
    #{error := IsErr} = RezoneResult,
    %% 如果失败，应当将当前任务排到最后
    NewConfirmRezoneData =
    case IsErr of
        true  ->
            OtherRezoneInfos ++ [RezoneInfo];
        false ->
            OtherRezoneInfos
    end,
    RezoneResult#{
        confirm_rezone_data => NewConfirmRezoneData
    }.

%% rezone_update/2 -> #{}
rezone_update(RezoneInfo, SvrZoneMap, SvrFMap, ZoneFSvrsMap) ->
    #rezone_info{
        zone_id = ZoneId1
        ,zone_id_2 = ZoneId2
        ,next_stage = NextStage
        ,svr_factions = SvrFactionsList
    } = RezoneInfo,
    %% 取出分区数据
    ZoneInfo1 = case cross_zone:proc_get_zone(ZoneId1) of
        #zone_info{} = Info1 -> Info1;
        _ -> #zone_info{zone_ext = #czone_info{}}
    end,
    ZoneInfo2 = case cross_zone:proc_get_zone(ZoneId2) of
        #zone_info{} = Info2 -> Info2;
        _ -> #zone_info{zone_ext = #czone_info{}}
    end,
    #zone_info{servers = ServerList1, zone_ext = CZoneInfo1} = ZoneInfo1,
    #zone_info{servers = ServerList2} = ZoneInfo2,
    %% 组装新分区数据，并统计变更情况
    %% 以ZoneInfo1和ZoneInfo2中的数据为基准，使用SvrFactionsList中的阵营数据进行更新
    {ServerListZone1, _FactionMapZone1, SvrFactionsListRem} = lists:foldl(
        fun update_zone_svrs_faction/2,
        {[], #{}, SvrFactionsList},
        ServerList1
    ),
    {NewServerList, FactionMapZone2, _} = lists:foldl(
        fun update_zone_svrs_faction/2,
        {ServerListZone1, #{}, SvrFactionsListRem},
        ServerList2
    ),
    %% 分区变化、阵营变化数据（主分区数据没有变化，不记录到变化量中）
    ZoneChangeMap = #{ZoneId2 => ZoneId1},
    FactionChangeMaps = #{
        % ZoneId1 => FactionMapZone1,
        ZoneId2 => FactionMapZone2
    },
    NewWorldLvAvg = world_lv_avg(NewServerList),
    NewCZoneInfo = CZoneInfo1#czone_info{
        stage = NextStage,
        world_lv_avg = NewWorldLvAvg,
        faction_names = []
    },
    NewZoneInfo = ZoneInfo1#zone_info{
        zone_ext = NewCZoneInfo,
        servers = NewServerList
    },
    %% 存储到DB中，以事务来确保分区数据一致性
    %% 接口上层会确保捕捉错误，以确定重试次数等
    ok = db:transaction(
        fun() ->
                db_save_svr_base_list(NewServerList),
                db_save_center_zones([NewZoneInfo]),
                db_delete_czone(ZoneId2),
                db_delete_rezone_info(ZoneId1),
                ok
        end
    ),
    %% 更新进程字典中数据
    cross_zone:proc_put_zone_only(NewZoneInfo),
    cross_zone:proc_erase_zones([ZoneId2]),
    %% 计算其他数据
    [TmpSvrFMap, TmpZoneFSvrsMap] = faction_map_remove_zone(ZoneInfo2, [SvrFMap, ZoneFSvrsMap]),
    [NewSvrFMap, NewZoneFSvrsMap] = faction_map_update_zone([NewZoneInfo], [TmpSvrFMap, TmpZoneFSvrsMap]),
    NewSvrZoneMap =
    lists:foldl(fun(#zone_svr{server_id = ServerId}, SvrZoneMapAcc) ->
                SvrZoneMapAcc#{ServerId => ZoneId1}
        end, SvrZoneMap, ServerList2),
    %% 清理失效的ZoneId对应的活跃度数据（更新另一个ZoneId数据由全部重新分区完成后统一进行）
    erase({vitality, ZoneId2}),
    #{
        error => false
        ,svr_zone_map => NewSvrZoneMap
        ,svr_faction_map => NewSvrFMap
        ,zone_faction_svrs_map => NewZoneFSvrsMap
        ,zone_change_map => ZoneChangeMap
        ,faction_change_map => FactionChangeMaps
        ,new_zone_info => NewZoneInfo
    }.

%% 为了避免单个失败影响全部操作，所以分开进行
%% 当confirm_rezone_data中数据未清空时，将继续发送消息进行处理
check_rezone_task(#czone_sys{confirm_rezone_data = [_ | _]}, ErrCount) ->
    Msg = {rezone, ErrCount},
    if
        ErrCount =:= 0 ->
            self() ! Msg;
        ErrCount < 10 ->
            erlang:send_after(2000, self(), Msg);
        true ->
            erlang:send_after(30000, self(), Msg)
    end;
check_rezone_task(_, _) ->
    ok.

%% 更新#zone_svr{}数据中的阵营faction
%% 注意：
%% 分区阵营中的一组游戏服，其阵营变更是统一的，
%% 不能一个阵营中不同服变更为不同阵营。
%% 这一点在生成预分区数据时需要得到保证，这里不验证。
update_zone_svrs_faction(ZoneSvr, _Acc = {ServerList, FactionMap, SvrFactionsList}) ->
    #zone_svr{server_id = ServerId, svr_ext = CZoneSvr} = ZoneSvr,
    #czone_svr{faction = OldFaction} = CZoneSvr,
    case lists:keytake(ServerId, 1, SvrFactionsList) of
        {value, {ServerId, Faction}, Others} ->
            NewCZoneSvr = CZoneSvr#czone_svr{faction = Faction},
            NewZoneSvr = ZoneSvr#zone_svr{svr_ext = NewCZoneSvr},
            NewFactionMap = FactionMap#{OldFaction => Faction},
            NewServerList = [NewZoneSvr | ServerList],
            {NewServerList, NewFactionMap, Others};
        _False ->
            NewFactionMap = FactionMap#{OldFaction => OldFaction},
            NewServerList = [ZoneSvr | ServerList],
            {NewServerList, NewFactionMap, SvrFactionsList}
    end.

%% -------------------------------------------------------
%% 计算分区进度
%% -------------------------------------------------------

%% 计算分区进度
%% NOTE：
%%   是否需要改为把所有不论是否相同的分区阶段都纳入统计？
%%   如果这样要求话，则不是计算MateZoneId，而应计算服范围，进而计算ZoneId范围，全部找出来
calc_rezone_progress(ZoneId) ->
    case cross_zone:proc_get_zone(ZoneId) of
        #zone_info{zone_ext = CZoneInfo, servers = ServerList} ->
            #czone_info{stage = Stage, world_lv_avg = WorldLvAvgZone} = CZoneInfo,
            NextStage = Stage + 1,
            {FactionSvrList, WorldLvAvgAll, MatchStage} =
            case valid_stage(NextStage) of
                true  ->
                    MateZoneId = calc_next_stage_mate_zone_id(ZoneId, Stage),
                    case cross_zone:proc_get_zone(MateZoneId) of
                        #zone_info{
                            zone_ext = #czone_info{stage = Stage},
                            servers = ServerList2
                        } ->
                            AllServerList = ServerList ++ ServerList2,
                            {
                                rezone_progress_svr_list(AllServerList),
                                world_lv_avg(AllServerList),
                                NextStage
                            };
                        _Other ->   % 找不到数据或不同的分区阶段，依然告诉前端下一阶段，但是不找相应的服
                            {
                                rezone_progress_svr_list(ServerList),
                                WorldLvAvgZone,
                                NextStage
                            }
                    end;
                false ->
                    {
                        rezone_progress_svr_list(ServerList),
                        WorldLvAvgZone,
                        Stage
                    }
            end,
            WorldLvReqt = czone_api:get_world_lv_reqt(MatchStage),
            {ok, MatchStage, WorldLvAvgZone, WorldLvReqt, WorldLvAvgAll, FactionSvrList};
        _False ->
            error
    end.

rezone_progress_svr_list(ZoneSvrList) ->
    [
        {Faction, {ServerId, WorldLv}} ||
        #zone_svr{
            server_id = ServerId,
            svr_ext = #czone_svr{
                faction = Faction,
                world_level = WorldLv
            }
        } <- ZoneSvrList
    ].

%% -------------------------------------------------------
%% 分区计算生成预分区数据
%% -------------------------------------------------------

%% 生成pre_rezone_data数据
%% 所有符合条件的服中，还需要剔除PreRezoneData、ConfirmRezoneData中预分区和已确认数据
gen_pre_rezone(ZoneInfoList, PreRezoneData, ConfirmRezoneData, TimerType) ->
    ConfirmZoneIdList = lists:append([
            [ZoneId1, ZoneId2] ||
            #rezone_info{zone_id = ZoneId1, zone_id_2 = ZoneId2}
            <- ConfirmRezoneData
        ]),
    UnneededZoneIdList = lists:foldl(
        fun(#rezone_info{zone_id = ZoneId1, zone_id_2 = ZoneId2, scheduled_time = ScheduledTime}, Acc) ->
                case ScheduledTime > 0 of
                    true  -> [ZoneId1, ZoneId2 | Acc];
                    false -> Acc
                end
        end, ConfirmZoneIdList, PreRezoneData),
    CheckZoneList = [
        ZoneInfo ||
        ZoneInfo = #zone_info{zone_id = ZoneId} <- ZoneInfoList,
        not lists:member(ZoneId, UnneededZoneIdList)
    ],
    case CheckZoneList of
        [_, _ | _] ->
            NowTime = utime:unixtime(),
            {_, ScheduledTime, _Remain} = calc_timer_secs(rezone_timer, NowTime),
            %% 排序使可能匹配的分区数据相邻，方便执行分区进阶检查
            SortList = lists:keysort(#zone_info.zone_id, CheckZoneList),
            check_gen_pre_rezone(SortList, PreRezoneData, NowTime, ScheduledTime, TimerType);
        _ ->
            PreRezoneData
    end.

check_gen_pre_rezone(
    [ZoneInfo1, ZoneInfo2 | ZoneList],
    PreRezoneData, NowTime, ScheduledTime, TimerType) ->
    #zone_info{
        zone_id = ZoneId1, zone_ext = #czone_info{stage = Stage1}
    } = ZoneInfo1,
    #zone_info{
        zone_id = ZoneId2, zone_ext = #czone_info{stage = Stage2}
    } = ZoneInfo2,
    if
        Stage1 =/= Stage2 ->
            %% 不是同阶子分区（不匹配的分区）
            check_gen_pre_rezone([ZoneInfo2 | ZoneList], PreRezoneData,
                NowTime, ScheduledTime, TimerType
            );
        TimerType == notice_timer andalso Stage1 > 1 ->
            %% notice_timer只对0阶和1阶分区进行处理，不符合的数据直接跳过
            check_gen_pre_rezone(ZoneList, PreRezoneData,
                NowTime, ScheduledTime, TimerType
            );
        TimerType == pre_timer andalso Stage1 =< 1 ->
            %% pre_timer只对2阶及以上分区进行处理，不符合的数据直接跳过（避免产生冗余无效数据）
            check_gen_pre_rezone(ZoneList, PreRezoneData,
                NowTime, ScheduledTime, TimerType
            );
        true ->
            %% 判断是否合法可进阶的对应分区
            NextStage = Stage1 + 1,
            case valid_stage(NextStage) of
                true  ->
                    case is_mate_zone(Stage1, ZoneId1, ZoneId2) of
                        true  ->
                            NewPreRezoneData = do_gen_pre_rezone(
                                ZoneInfo1, ZoneInfo2,
                                PreRezoneData, NowTime, ScheduledTime
                            ),
                            check_gen_pre_rezone(ZoneList, NewPreRezoneData,
                                NowTime, ScheduledTime, TimerType
                            );
                        false ->
                            %% 非对应分区
                            check_gen_pre_rezone(
                                [ZoneInfo2 | ZoneList], PreRezoneData,
                                NowTime, ScheduledTime, TimerType
                            )
                    end;
                false ->
                    %% 无效的分区阶段（应为已经达到配置中的最高级，或配置缺失）
                    check_gen_pre_rezone(
                        [ZoneInfo2 | ZoneList], PreRezoneData,
                        NowTime, ScheduledTime, TimerType
                    )
            end
    end;
check_gen_pre_rezone(_ZoneList, PreRezoneData, _NowTime, _ScheduledTime, _TimerType) ->
    PreRezoneData.

do_gen_pre_rezone(ZoneInfo1, ZoneInfo2, PreRezoneData, NowTime, ScheduledTime) ->
    #zone_info{
        zone_id = ZoneId1
        ,servers = ServerList1
        ,zone_ext = #czone_info{stage = Stage1}
    } = ZoneInfo1,
    #zone_info{
        zone_id = ZoneId2
        ,servers = ServerList2
    } = ZoneInfo2,
    NextStage = Stage1 + 1,
    %% 开服时间条件暂时忽略，后补
    {OpenDayReqt, WorldLvReqt, _} = data_czone:get_stage(NextStage),
    %% 相应子分区，需要再检查是否满足平均世界等级条件
    AllServerList = ServerList1 ++ ServerList2,
    WorldLvAvg = world_lv_avg(AllServerList),
    case WorldLvAvg >= WorldLvReqt of
        true  ->
            %% 检查开服天数条件
            IsOpenDayOk = lists:all(
                fun(#zone_svr{server_id = ServerId}) ->
                        case center_svr_reg:lookup_reg_svr(ServerId) of
                            [#reg_svr{open_time = OpenTime}] ->
                                OpenDay = utime:day_span(OpenTime, NowTime),
                                OpenDay >= OpenDayReqt;
                            _ -> true
                        end
                end, AllServerList),
            % IsOpenDayOk = true,
            case IsOpenDayOk of
                true  ->
                    %% 接口上层保证了已经存在的rezone_info，scheduled_time一定不大于0
                    NewRezoneInfo =
                    case lists:keyfind(ZoneId1, #rezone_info.zone_id, PreRezoneData) of
                        #rezone_info{zone_id_2 = ZoneId2} = RezoneInfoOld ->
                            %% 存在旧的预分区数据（通常为后台手工编辑），
                            %% 直接更新scheduled_time，其他数据应当不需要修正
                            RezoneInfoOld#rezone_info{scheduled_time = ScheduledTime};
                        #rezone_info{} ->
                            %% 分区Id不一致，算法理论上不可能，仅作为可能的接口返回值而存在
                            %% NOTE：
                            %%   暂时不用做，如果未来想要允许运营直接编辑分区方案，
                            %%   可能面临分区id生成方案的调整，才能保留使用运营编辑的方案
                            %%   暂时直接废弃此数据，按无数据的方式生成rezone_info
                            new_rezone_info(ZoneId1, ZoneId2, NextStage,
                                ServerList1, ServerList2,
                                ScheduledTime
                            );
                        _False ->
                            new_rezone_info(ZoneId1, ZoneId2, NextStage,
                                ServerList1, ServerList2,
                                ScheduledTime
                            )
                    end,
                    lists:keystore(ZoneId1, #rezone_info.zone_id, PreRezoneData, NewRezoneInfo);
                false ->
                    PreRezoneData
            end;
        false ->
            PreRezoneData
    end.

new_rezone_info(ZoneId1, ZoneId2, NextStage, ServerList1, ServerList2, ScheduledTime) ->
    %% 按活跃度进行阵营合并计算
    %% 不存在的阵营，作为0活跃计算，也可适用统一的算法
    FVList1 = proc_get_vitality(ZoneId1),
    FVList2 = proc_get_vitality(ZoneId2),
    FList1 = count_svrs_factions(ServerList1),
    FList2 = count_svrs_factions(ServerList2),
    Zone2FactionChange = faction_calc_by_vitality(FVList1, FVList2, FList1, FList2),
    SvrFactions2 = calc_svr_factions(ServerList2, Zone2FactionChange),
    %% Zone1中的数据，阵营虽然不变，也要记录数据，
    %% 以兼容运营后台进行编辑的操作（运营编辑的话，需要提供全量的阵营变化数据）
    SvrFactions1 = [
        {ServerId, Faction} ||
        #zone_svr{
            server_id = ServerId
            ,svr_ext = #czone_svr{faction = Faction}
        } <- ServerList1
    ],
    AllSvrFactions = SvrFactions1 ++ SvrFactions2,
    % ?INFO_TTY("f change:~w, SvrFactions:~w", [Zone2FactionChange, AllSvrFactions]),
    %% 其实，前面有排过序，可确定ZoneId1是较小值，这里只是从接口自身考虑，意义更明确
    NewZoneId1 = min(ZoneId1, ZoneId2),
    NewZoneId2 = max(ZoneId1, ZoneId2),
    #rezone_info{
        zone_id = NewZoneId1
        ,zone_id_2 = NewZoneId2
        ,next_stage = NextStage
        ,svr_factions = AllSvrFactions
        ,scheduled_time = ScheduledTime
    }.

%% -------------------------------------------------------
%% 阵营计算规则：
%% 在合并分区时，以小组服的活跃玩家总战力排名——
%% 即小组服内前3日有登陆的玩家战力排行前50名玩家总战力，
%% 定义为活跃玩家战力，根据这个活跃战力排名，进行阵营重新分组
%% 策划需求中，指定固定取凌晨2点的活跃数据，
%% 实际上采用定期上传方式，独立维护活跃度数据的更新及上传
%% -------------------------------------------------------

faction_calc_by_vitality(FVList1, FVList2, FList1, FList2) ->
    %% 以零活跃填充阵营列表
    %% 目的是填充后，可以方便地排序结果，然后对两个阵营进行阵营合并
    NewFVList1 = fill_faction_vitality_list(FVList1, FList1),
    NewFVList2 = fill_faction_vitality_list(FVList2, FList2),
    %% 按活跃度分别正排序和反排序
    %% （用lists:keysort(2, FVList)也可以简单实现，
    %% 但是无活跃度数据时阵营变化结果是不稳定的）
    SortedFVList1 = lists:sort(fun sort_faction_vitality_desc/2, NewFVList1),
    SortedFVList2 = lists:sort(fun sort_faction_vitality_asc/2, NewFVList2),
    % ?INFO_TTY("old  fvlist:~w, ~w", [FVList1, FVList2]),
    % ?INFO_TTY("sort fvlist:~w, ~w", [SortedFVList1, SortedFVList2]),
    %% 计算第二个List的阵营变更情况（第一个List阵营不变）
    lists:zipwith(fun({F1, _V1}, {F2, _V2}) ->
                {F2, F1}
        end, SortedFVList1, SortedFVList2).

%% 以0活跃值填充无活跃数据的真实阵营信息，以-1活跃值填充不存在的阵营信息
%%
%% 为了使阵营数不足时也可以用统一的方法排序合并，
%% 即需要满足两个列表中，真实数据的顺序是反向的：
%% FVList1尽量将真实数据靠前，FVList2尽量将真实数据靠后
%% 因为在真实数据的位置尽量分散的情况下，才可以合并出最多的阵营数量
%%
%% 反之，如果无数据及填充数据都取活跃值为0，
%% 会导致顺序不能满足以上条件，通常会变成两个列表中：
%% 真实阵营合并，不存在的阵营合并，最终生成不符合要求的合并关系
fill_faction_vitality_list(FVList, FList) ->
    fill_faction_vitality_list_2(FVList, FList, ?FACTION_LIST).

fill_faction_vitality_list_2(FVList, FList, [Faction | RemainFactions]) ->
    case lists:keymember(Faction, 1, FVList) of
        true  ->
            fill_faction_vitality_list_2(FVList, FList, RemainFactions);
        false ->
            VitalityVal =
            case lists:member(Faction, FList) of
                true  ->  0;
                false -> -1
            end,
            fill_faction_vitality_list_2([{Faction, VitalityVal} | FVList], FList, RemainFactions)
    end;
fill_faction_vitality_list_2(FVList, _FList, []) ->
    FVList.

%% 排序 - 活跃升序（次关键字阵营降序）
sort_faction_vitality_asc({F1, V1}, {F2, V2}) ->
    if
        V1 > V2 -> false;
        V1 < V2 -> true;
        F1 < F2 -> false;
        true -> true
    end.

%% 排序 - 活跃降序（次关键字阵营升序）
sort_faction_vitality_desc({F1, V1}, {F2, V2}) ->
    if
        V1 > V2 -> true;
        V1 < V2 -> false;
        F1 < F2 -> true;
        true -> false
    end.

%% -> [{ServerId, NewFaction}]
calc_svr_factions(ZoneSvrList, ZoneFactionChange) ->
    [
        {
            ServerId,
            proplists:get_value(Faction, ZoneFactionChange, Faction)
        }
        ||
        #zone_svr{
            server_id = ServerId,
            svr_ext = #czone_svr{
                faction = Faction
            }
        } <- ZoneSvrList
    ].

%% 统计ZoneSvrList中有哪些阵营
count_svrs_factions([#zone_svr{svr_ext = #czone_svr{faction = Faction}} | ZoneSvrList]) ->
    FList = count_svrs_factions(ZoneSvrList),
    case lists:member(Faction, FList) of
        true  -> FList;
        false -> [Faction | FList]
    end;
count_svrs_factions([]) ->
    [].

%% -------------------------------------------------------
%% 常用分区信息基础版生成构造，供给其他功能获取使用
%% -------------------------------------------------------
%% generate_faction_map(ZoneInfos) -> [SvrFMap, ZoneFSvrsMap]
%% 生成SvrFMap及ZoneFSvrsMap
%% SvrFMap      :: #{ServerId => Faction}
%% ZoneFSvrsMap :: #{ZoneId => FSvrsMap}
%% FSvrsMap     :: #{Faction => ServerIds}
generate_faction_map(ZoneInfos) ->
    faction_map_update_zone(ZoneInfos, [#{}, #{}]).

%% 根据变更ZoneInfo列表数据，更新SvrFMap及ZoneFSvrsMap
faction_map_update_zone(ZoneInfoUpdateList, [SvrFMap, ZoneFSvrsMap]) ->
    lists:foldl(
        fun statistic_zone_faction_info/2,
        [SvrFMap, ZoneFSvrsMap],
        ZoneInfoUpdateList
    ).

%% 统计分区阵营信息
statistic_zone_faction_info(ZoneInfo, [SvrFMap, ZoneFSvrsMap]) ->
    #zone_info{zone_id = ZoneId, servers = ServerList} = ZoneInfo,
    OldFSvrsMap = maps:get(ZoneId, ZoneFSvrsMap, #{}),
    [NewSvrFMap, FSvrsMap] = lists:foldl(
        fun statistic_svr_faction_info/2,
        [SvrFMap, OldFSvrsMap],
        ServerList
    ),
    NewZoneFSvrsMap = ZoneFSvrsMap#{ZoneId => FSvrsMap},
    [NewSvrFMap, NewZoneFSvrsMap].

%% 统计游戏服阵营信息
statistic_svr_faction_info(ZoneSvr, Acc = [SvrFMap, FSvrsMap]) ->
    case ZoneSvr of
        #zone_svr{server_id = ServerId, svr_ext = #czone_svr{faction = Faction}} ->
            NewSvrFMap = SvrFMap#{ServerId => Faction},
            FactionServerIds = maps:get(Faction, FSvrsMap, []),
            NewFSvrsMap =
            case lists:member(ServerId, FactionServerIds) of
                true  ->
                    FSvrsMap;
                false ->
                    NewFactionServerIds = [ServerId | FactionServerIds],
                    FSvrsMap#{Faction => NewFactionServerIds}
            end,
            [NewSvrFMap, NewFSvrsMap];
        _ ->
            Acc
    end.

%% 从SvrFMap及ZoneFSvrsMap中删除数据
faction_map_remove_zone(ZoneInfo, [SvrFMap, ZoneFSvrsMap]) ->
    #zone_info{
        zone_id = ZoneId
        ,servers = ServerList
    } = ZoneInfo,
    NewZoneFSvrsMap = maps:remove(ZoneId, ZoneFSvrsMap),
    NewSvrFMap = lists:foldl(
        fun (#zone_svr{server_id = ServerId}, MapAcc) ->
                maps:remove(ServerId, MapAcc);
            (_, MapAcc) ->
                MapAcc
        end,
        SvrFMap,
        ServerList
    ),
    [NewSvrFMap, NewZoneFSvrsMap].

czone_sys_faction_map_update_zone(CZoneSys, ZoneUpdateList) ->
    #czone_sys{
        svr_faction_map = OldSvrFMap,
        zone_faction_svrs_map = OldZoneFSvrsMap
    } = CZoneSys,
    [SvrFMap, ZoneFSvrsMap] = faction_map_update_zone(
        ZoneUpdateList, [OldSvrFMap, OldZoneFSvrsMap]
    ),
    CZoneSys#czone_sys{
        svr_faction_map = SvrFMap,
        zone_faction_svrs_map = ZoneFSvrsMap
    }.

%% -------------------------------------------------------
%% 世界等级数据的更新、计算
%% -------------------------------------------------------

%% 通知上传世界等级
notice_upload_world_level([ZoneSvr | CZoneSvrList]) ->
    #zone_svr{server_id = ServerId} = ZoneSvr,
    clusters_center:apply_cast(ServerId, {czone_api, upload_world_level, []}),
    notice_upload_world_level(CZoneSvrList);
notice_upload_world_level([]) ->
    ok.

%% 世界等级平均值
world_lv_avg(ZoneSvrList) ->
    WorldLvList = [
        WorldLv ||
        #zone_svr{svr_ext = #czone_svr{world_level = WorldLv}}
        <- ZoneSvrList
    ],
    case WorldLvList of
        [_|_] ->
            Sum = lists:sum(WorldLvList),
            Sum div length(WorldLvList);
        _ ->
            0
    end.

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

find_rezone_info(ZoneId, ConfirmRezoneData) ->
    MatchRezoneInfoList = [
        RezoneInfo ||
        RezoneInfo = #rezone_info{zone_id = ZoneIdX, zone_id_2 = ZoneId2X} <- ConfirmRezoneData,
        ZoneId =:= ZoneIdX orelse ZoneId =:= ZoneId2X
    ],
    case MatchRezoneInfoList of
        [RezoneInfo | _] ->
            RezoneInfo;
        _ ->
            false
    end.

valid_stage(Stage) ->
    AllStages = data_czone:all_stages(),
    lists:member(Stage, AllStages).

%% 设置定时器
set_timer(TimerType, OldTimer) ->
    {_NowTime, _TimerTime, RemainSecs} = calc_timer_secs(TimerType),
    set_timer(TimerType, OldTimer, RemainSecs).

set_timer(TimerType, OldTimer, RemainSecs) ->
    util:cancel_timer(OldTimer),
    erlang:start_timer(RemainSecs * 1000, self(), TimerType).


%% 通过ZoneSvrAddList与SvrZoneMap生成add_svrs所需参数AddServers
conv_add_svrs_param(ZoneSvrAddList, SvrZoneMap) ->
    lists:filtermap(fun(ZoneSvr) ->
                case conv_svr_zone_faction(ZoneSvr, SvrZoneMap) of
                    false -> false;
                    {_, 0, _} -> false;
                    Value -> {true, Value}
                end
        end, ZoneSvrAddList).

conv_svr_zone_faction(#zone_svr{
        server_id = ServerId,
        svr_ext = #czone_svr{faction = Faction}
    }, SvrZoneMap) ->
    ZoneId = maps:get(ServerId, SvrZoneMap, 0),
    {ServerId, ZoneId, Faction};
conv_svr_zone_faction(_, _) ->
    false.

proc_get_zone_stages() ->
    ZoneIds = cross_zone:proc_get_zone_ids(),
    [{ZoneId, proc_get_zone_stage(ZoneId)} || ZoneId <- ZoneIds].

proc_get_zone_stage(ZoneId) ->
    case cross_zone:proc_get_zone(ZoneId) of
        #zone_info{zone_id = ZoneId, zone_ext = #czone_info{stage = Stage}} ->
            Stage;
        _ ->
            0
    end.
