%%% -------------------------------------------------------
%%% @author huangyongxing@yeah.net
%%% @doc
%%% 跨服中 游戏服信息注册、全跨服唯一id生成管理
%%%
%%% 为尽量减小运维初始部署服务器时，连接跨服顺序问题
%%% （例如配置错误导致后面的服先连接到跨服）对唯一id生成的影响，
%%% 跨服中心固定每天1点，对已预注册、今天之前开服的游戏服，
%%% 按照开服顺序，分配唯一的id（称之为游戏服的跨服id，svr_cid）
%%%
%%% TODO 合服删除数据时，需要做数据移除
%%% @end
%%% -------------------------------------------------------
-module(center_svr_reg).

-behaviour(gen_server).

%% API
-export([
        start_link/0
        ,check_reg/1
        ,lookup_reg_svr/1               % 查询注册游戏服资料
        ,lookup_svr_base/1              % 查询游戏服基本资料
        ,reg_svr_cid/2                  % 计算注册服的全跨服唯一编号
        ,svr_cid_to_reg_order/1         % 注册服svr_cid转换为注册序号
        ,check_zone_omit_svrs/1         % 检查分区中遗漏的已注册服
    ]).

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

%% for czone use only
-export([
        find_server_ids/3
        ,find_server_ids_with_svr_cid/2
    ]).

%% for gm
-export([
        gm_do_reg/0
        ,gm_update_server_name/2
    ]).

-define(SERVER, ?MODULE).

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


%% TODO 待整理的各类零散说明
%%
%% 连接过该跨服服务器，就会被保存下来，用于尽可能确保跨服分区的稳定性
%% 如果运维配置错误，可能引发数据重复，需要合跨服根据数据状态更正（或手工修正？）
%%
%%

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

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

%% 检查进行注册
check_reg(GameNode = #game_node{}) ->
    gen_server:cast(?SERVER, {add_node, GameNode}).

%% 查询注册游戏服资料 -> [RegSvr] | [].
lookup_reg_svr(ServerId) ->
    ets:lookup(?ETS_REG_SVR_INFO, ServerId).

%% 查询游戏服基本资料 -> [SvrBase] | [].
lookup_svr_base(ServerId) ->
    ets:lookup(?ETS_ALL_SVR_BASE, ServerId).

%% 计算注册服的全跨服唯一编号
reg_svr_cid(CenterSvrId, RegCnt) ->
    <<SvrCid:32>> = <<CenterSvrId:16, RegCnt:16>>,
    SvrCid.

%% 注册服svr_cid转换为注册序号
svr_cid_to_reg_order(SvrCid) ->
    <<CenterSvrId:16, RegCnt:16>> = <<SvrCid:32>>,
    {CenterSvrId, RegCnt}.

%% 检查分区中遗漏的已注册服
check_zone_omit_svrs(ServerIdsHadZoned) ->
    gen_server:cast(?SERVER, {check_zone_omit_svrs, ServerIdsHadZoned}).

%% -----------------------------------------------
%% 从注册服信息中获取到相应的游戏服id列表
%% 注意：以下两个接口为分区数据特殊修复使用 或用于数据观察，
%%       通常不可用于常规业务中
%% -----------------------------------------------
%% 通过RegOrder范围来找到ServerIds
find_server_ids(CenterSvrId, MinRegOrder, MaxRegOrder) ->
    MinSvrCid = center_svr_reg:reg_svr_cid(CenterSvrId, MinRegOrder),
    MaxSvrCid = center_svr_reg:reg_svr_cid(CenterSvrId, MaxRegOrder),
    find_server_ids_with_svr_cid(MinSvrCid, MaxSvrCid).

%% 通过SvrCid范围找到ServerIds
find_server_ids_with_svr_cid(MinSvrCid, MaxSvrCid) ->
    MatchPattern = #reg_svr{server_id = '$1', svr_cid = '$2', _ = '_'},
    MatchGuard   = [{'>=', '$2', MinSvrCid}, {'=<', '$2', MaxSvrCid}],
    MatchBody    = ['$1'],
    MatchExpr    = [{MatchPattern, MatchGuard, MatchBody}],
    ets:select(?ETS_REG_SVR_INFO, MatchExpr).
%% -----------------------------------------------

%% GM - 操作对已连接过的未注册服进行注册
gm_do_reg() ->
    ?SERVER ! {timeout, undefined, {svr_reg_timer, 0}}.

%% GM - 更新游戏服名称
gm_update_server_name(ServerId, ServerName) ->
    ?SERVER ! {gm_update_server_name, ServerId, ServerName}.

%%%===================================================================
%%% gen_server callbacks
%%%===================================================================

init(_) ->
    process_flag(trap_exit, true),
    ets:new(?ETS_REG_SVR_INFO, [named_table, protected, set, {keypos, #reg_svr.server_id}, {read_concurrency, true}]),
    ets:new(?ETS_ALL_SVR_BASE, [named_table, protected, set, {keypos, #svr_base.server_id}, {read_concurrency, true}]),
    RegSvrList = load_reg_svr_data(),
    CenterSvrId = config:get_server_id(),
    RegCnt = load_reg_cnt(CenterSvrId),
    set_next_svr_reg_timer(),
    ets:insert(?ETS_REG_SVR_INFO, RegSvrList),
    load_svr_base_to_ets(),
    State = #svr_reg_proc_state{
        reg_svr_list = RegSvrList,
        reg_cnt = RegCnt,
        reg_svr_need_saved = []
    },
    {ok, State}.

handle_call(Request, From, State) ->
    try
        do_handle_call(Request, From, State)
    catch
        ?EXCEPTION(_ErrorClass, _Error, _Stacktrace) ->
            ?ERR("handle_call Request:~w, Error:~w, ~w", [Request, _Error, ?GET_STACK(_Stacktrace)]),
            {reply, error, State}
    end.

handle_cast(Msg, State) ->
    try
        do_handle_cast(Msg, State)
    catch
        ?EXCEPTION(_ErrorClass, _Error, _Stacktrace) ->
            ?ERR("handle_cast Msg:~w, Error:~w, ~w", [Msg, _Error, ?GET_STACK(_Stacktrace)]),
            {noreply, State}
    end.

handle_info(Info, State) ->
    try
        do_handle_info(Info, State)
    catch
        ?EXCEPTION(_ErrorClass, _Error, _Stacktrace) ->
            ?ERR("handle_info Info:~w, Error:~w, ~w", [Info, _Error, ?GET_STACK(_Stacktrace)]),
            {noreply, State}
    end.

terminate(_Reason, State) ->
    %% 退出前检查保存未存储的数据
    #svr_reg_proc_state{
        reg_svr_need_saved = RegSvrNeedSaved,
        svr_base_need_saved = SvrBaseNeedSaved
    } = State,
    try
        db_save_reg_svr_list(RegSvrNeedSaved),
        db_save_svr_base_list(SvrBaseNeedSaved)
    catch
        ?EXCEPTION(_Class, _Reason, _Stacktrace) ->
            ?ERR("save reg_svr [~p] error: ~w,~w,~p",
                [RegSvrNeedSaved, _Class, _Reason, ?GET_STACK(_Stacktrace)]
            )
    end,
    ok.

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


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

do_handle_call(_Request, _From, State) ->
    Reply = ok,
    {reply, Reply, State}.

do_handle_cast({add_node, GameNode = #game_node{}}, State) ->
    #svr_reg_proc_state{
        reg_svr_list = RegSvrList,
        reg_svr_need_saved = RegSvrNeedSaved,
        svr_base_need_saved = SvrBaseNeedSaved
    } = State,
    {
        RegSvrListNew,
        RegSvrNeedSavedNew
    } = check_svr_regd(RegSvrList, RegSvrNeedSaved, GameNode),
    SvrBaseNeedSavedNew = check_store_svr_base(GameNode, SvrBaseNeedSaved),
    NewState = State#svr_reg_proc_state{
        reg_svr_list = RegSvrListNew,
        reg_svr_need_saved = RegSvrNeedSavedNew,
        svr_base_need_saved = SvrBaseNeedSavedNew
    },
    check_set_reg_db_timer(NewState),
    check_set_svr_base_db_timer(NewState),
    {noreply, NewState};

do_handle_cast({check_zone_omit_svrs, ServerIdsHadZoned}, State) ->
    #svr_reg_proc_state{
        reg_svr_list = RegSvrList
    } = State,
    RegSvrUnZoned = [
        RegSvr ||
        RegSvr = #reg_svr{server_id = ServerId} <- RegSvrList,
        not lists:member(ServerId, ServerIdsHadZoned)
    ],
    %% 尚未分区的服，通知分区管理进程进行分区处理
    case RegSvrUnZoned of
        [_|_] -> czone:add_reg_svr_list(RegSvrUnZoned);
        _     -> ignore
    end,
    {noreply, State};

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

%% 管理reg_svr_need_saved数据的定期存储
do_handle_info({timeout, _Timer, reg_db_timer}, State) ->
    NewState =
    try
        OldTimer = erase(reg_db_timer),
        util:cancel_timer(OldTimer),
        #svr_reg_proc_state{reg_svr_need_saved = RegSvrNeedSaved} = State,
        case RegSvrNeedSaved of
            [_|_] ->
                db_save_reg_svr_list(RegSvrNeedSaved),
                % 预注册及更新数据时已经写入，不需要重新写入
                % ets:insert(?ETS_REG_SVR_INFO, RegSvrNeedSaved);
                ok;
            _ -> ignore
        end,
        State#svr_reg_proc_state{reg_svr_need_saved = []}
    catch
        ?EXCEPTION(_Class, _Reason, _Stacktrace) ->
            State
    end,
    check_set_reg_db_timer(NewState),
    {noreply, NewState};

do_handle_info({timeout, _Timer, svr_base_db_timer}, State) ->
    NewState =
    try
        OldTimer = erase(svr_base_db_timer),
        util:cancel_timer(OldTimer),
        #svr_reg_proc_state{svr_base_need_saved = SvrBaseNeedSaved} = State,
        case SvrBaseNeedSaved of
            [_|_] ->
                db_save_svr_base_list(SvrBaseNeedSaved);
            _ -> ignore
        end,
        State#svr_reg_proc_state{svr_base_need_saved = []}
    catch
        ?EXCEPTION(_Class, _Reason, _Stacktrace) ->
            State
    end,
    check_set_svr_base_db_timer(NewState),
    {noreply, NewState};

%% 对已预注册服进行正式注册（分配svr_cid）
do_handle_info({timeout, _Timer, {svr_reg_timer, ErrCnt}}, State) ->
    ?DEBUG_TTY("svr_reg_timer timeout, do server register"),
    NewState =
    try
        OldTimer = erase(svr_reg_timer),
        util:cancel_timer(OldTimer),
        #svr_reg_proc_state{
            reg_cnt = RegCnt,
            reg_svr_list = RegSvrList,
            reg_svr_need_saved = RegSvrNeedSaved
        } = State,
        {
            RegCntNew, RegSvrListNew, RegSvrNeedSavedNew
        } = svr_register(RegCnt, RegSvrList, RegSvrNeedSaved),
        set_next_svr_reg_timer(),
        %% 通知跨服分区管理器，刷新分区信息列表
        czone:add_reg_svr_list(RegSvrListNew -- RegSvrList),
        %% 将数据写入到ETS表方便其他地方直接查询
        State#svr_reg_proc_state{
            reg_cnt = RegCntNew,
            reg_svr_list = RegSvrListNew,
            reg_svr_need_saved = RegSvrNeedSavedNew
        }
    catch
        ?EXCEPTION(_Class, _Reason, _Stacktrace) ->
            NewErrCnt = ErrCnt + 1,
            case NewErrCnt >= 10 of
                true  -> ignore;
                false -> set_next_svr_reg_timer(60, NewErrCnt)
            end,
            ?ERR("server register faild[~w]: ~w,~w,~p", [
                    NewErrCnt, _Class, _Reason, ?GET_STACK(_Stacktrace)
                ]),
            State
    end,
    check_set_reg_db_timer(NewState),
    {noreply, NewState};

do_handle_info({gm_update_server_name, ServerId, ServerName}, State) ->
    case lookup_svr_base(ServerId) of
        [SvrBase] ->
            NewSvrBase = SvrBase#svr_base{server_name = ServerName},
            ets:insert(?ETS_ALL_SVR_BASE, NewSvrBase),
            db_save_svr_base_list(NewSvrBase),
            clusters_center:apply_to_all_node(svr_base_local_api, update_svr_base, [NewSvrBase]),
            #svr_reg_proc_state{svr_base_need_saved = SvrBaseNeedSaved} = State,
            SvrBaseNeedSavedNew = lists:keydelete(ServerId, #svr_base.server_id, SvrBaseNeedSaved),
            {noreply, State#svr_reg_proc_state{svr_base_need_saved = SvrBaseNeedSavedNew}};
        _ ->
            {noreply, State}
    end;

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

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

%% -------------------------------------------------------
%% 数据加载
%% -------------------------------------------------------

%% 加载注册游戏服数据
load_reg_svr_data() ->
    RegSvrList = db:get_all(?SQL_SELECT_REG_SVR),
    [db_to_reg_svr(RegSvrInfo) || RegSvrInfo <- RegSvrList].

db_to_reg_svr([ServerId, ServerNum, SvrCid, MServerIdsBitStr, OpenTime, Time]) ->
    MServerIds =
    case util:bitstring_to_term(MServerIdsBitStr) of
        [_|_] = List -> List;
        _ -> []
    end,
    #reg_svr{
        server_id = ServerId
        ,server_num = ServerNum
        ,svr_cid = SvrCid
        ,m_server_ids = MServerIds
        ,open_time = OpenTime
        ,time = Time
    }.

%% 加载跨服中游戏服注册次数 -> RegCnt
load_reg_cnt(CenterSvrId) ->
    Sql = util:fmt_bin(?SQL_SELECT_REG_CNT, [CenterSvrId]),
    RegCnt = db:get_one(Sql),
    case is_integer(RegCnt) of
        true  -> RegCnt;
        false ->
            Min = reg_svr_cid(CenterSvrId, 0),
            Max = reg_svr_cid(CenterSvrId + 1, 0) - 1,
            SqlGetMaxSvrCid = util:fmt_bin(?SQL_SELECT_MAX_SVR_CID, [Min, Max]),
            MaxSvrCid = db:get_one(SqlGetMaxSvrCid),
            case is_integer(MaxSvrCid) of
                true  ->
                    {_CenterSvrId, MaxRegCnt} = svr_cid_to_reg_order(MaxSvrCid),
                    MaxRegCnt;
                false ->
                    0
            end
    end.

load_svr_base_to_ets() ->
    DbDataList = db:get_all(?SQL_SELECT_SVR_BASE),
    SvrBaseList = [to_svr_base(DbSvrBase) || DbSvrBase <- DbDataList],
    ets:insert(?ETS_ALL_SVR_BASE, SvrBaseList).

to_svr_base([ServerId, ServerNum, ServerName]) ->
    #svr_base{
        server_id = ServerId
        ,server_num = ServerNum
        ,server_name = ServerName
    }.

%% -------------------------------------------------------
%% 数据写入
%% -------------------------------------------------------

%% 保存#reg_svr{}数据
db_save_reg_svr_list(RegSvrList) ->
    SqlPrefix = ?SQL_REPLACE_REG_SVR_PREFIX,
    DataFmtFun = fun fmt_reg_svr/1,
    udb:save_data_list(RegSvrList, SqlPrefix, DataFmtFun).

%% 格式化为需要保存的数据形式
fmt_reg_svr(RegSvr) ->
    #reg_svr{
        server_id = ServerId
        ,server_num = ServerNum
        ,svr_cid = SvrCid
        ,m_server_ids = MServerIds
        ,open_time = OpenTime
        ,time = Time
    } = RegSvr,
    util:fmt_bin(<<"(~w,~w,~w,'~ts',~w,~w)"/utf8>>,
        [ServerId, ServerNum, SvrCid, util:term_to_bitstring(MServerIds), OpenTime, Time]
    ).

-spec db_save_reg_cnt(CenterSvrId, RegCnt) -> Ignore :: any() when
    CenterSvrId :: non_neg_integer(), RegCnt :: non_neg_integer().
%% @doc 存储/更新reg_cnt数据
db_save_reg_cnt(CenterSvrId, RegCnt) ->
    Sql = util:fmt_bin(?SQL_REPLACE_REG_CNT, [CenterSvrId, RegCnt]),
    db:execute(Sql).

%% 保存游戏服基本信息（曾注册过的服数据）
db_save_svr_base_list(SvrBaseList) ->
    SqlPrefix = ?SQL_REPLACE_SVR_BASE_PREFIX,
    DataFmtFun = fun fmt_svr_base/1,
    udb:save_data_list(SvrBaseList, SqlPrefix, DataFmtFun).

fmt_svr_base(SvrBase) ->
    #svr_base{
        server_id = ServerId,
        server_num = ServerNum,
        server_name = ServerName
    } = SvrBase,
    util:fmt_bin(<<"(~w,~w,'~ts')"/utf8>>, [ServerId, ServerNum, ServerName]).

%% -------------------------------------------------------
%% 注册管理
%% -------------------------------------------------------

-spec check_svr_regd(RegSvrList, RegSvrNeedSaved, GameNode) ->
    {RegSvrListNew, RegSvrNeedSavedNew} when
    RegSvrList :: [RegSvr],
    RegSvrNeedSaved :: [RegSvr],
    RegSvrListNew :: [RegSvr],
    RegSvrNeedSavedNew :: [RegSvr],
    GameNode :: #game_node{},
    RegSvr :: #reg_svr{}.
%% @doc 检查是否注册，未注册的添加到待保存列表中
%% @private
check_svr_regd(RegSvrList, RegSvrNeedSaved, GameNode) ->
    #game_node{
        server_id = ServerId,
        server_num = ServerNum,
        merge_server_ids = MergeServerIds,
        open_time = OpenTime
    } = GameNode,
    case lists:keyfind(ServerId, #reg_svr.server_id, RegSvrList) of
        #reg_svr{
            server_num = OldServerNum,
            m_server_ids = OldMServerIds,
            open_time = OldOpenTime
        } = OldRegSvr ->
            %% 已有历史注册数据，检查是否更新
            case OldMServerIds =/= MergeServerIds orelse
                OldOpenTime =/= OpenTime orelse
                ServerNum =/= OldServerNum of
                true  ->
                    % ?INFO_TTY("信息变更"),
                    RegSvrChange = OldRegSvr#reg_svr{
                        server_num = ServerNum,
                        m_server_ids = MergeServerIds,
                        open_time = OpenTime,
                        time = utime:unixtime()
                    },
                    ets:insert(?ETS_REG_SVR_INFO, RegSvrChange),
                    % ?INFO_TTY("OldRegSvr:~w, RegSvrChange:~w", [OldRegSvr, RegSvrChange]),
                    {
                        lists:keystore(ServerId, #reg_svr.server_id, RegSvrList, RegSvrChange),
                        lists:keystore(ServerId, #reg_svr.server_id, RegSvrNeedSaved, RegSvrChange)
                    };
                false ->
                    % ?INFO_TTY("信息无变化"),
                    {RegSvrList, RegSvrNeedSaved}
            end;
        _ ->
            %% 没有数据，svr_cid初始为0，预注册（正式注册时从中筛选数据）
            RegSvrAdd = #reg_svr{
                svr_cid = 0,
                server_id = ServerId, server_num = ServerNum,
                m_server_ids = MergeServerIds, open_time = OpenTime,
                time = utime:unixtime()
            },
            ?DEBUG_TTY("pre register server: ~w", [RegSvrAdd]),
            ets:insert(?ETS_REG_SVR_INFO, RegSvrAdd),
            {
                [RegSvrAdd | RegSvrList],
                [RegSvrAdd | RegSvrNeedSaved]
            }
    end.

-spec svr_register(RegCnt, RegSvrList, RegSvrNeedSaved) ->
    {RegCntNew, RegSvrListNew, RegSvrNeedSavedNew} when
    RegCnt :: non_neg_integer(),
    RegCntNew :: non_neg_integer(),
    RegSvrList :: [RegSvr],
    RegSvrListNew :: [RegSvr],
    RegSvrNeedSaved :: [RegSvr],
    RegSvrNeedSavedNew :: [RegSvr],
    RegSvr :: #reg_svr{}.
%% @doc 检查RegSvrList中预注册的信息，进行注册，返回新数据
%% @private
svr_register(RegCnt, RegSvrList, RegSvrNeedSaved) ->
    Midnight = utime:unixdate(),
    CenterSvrId = config:get_server_id(),
    NeedRegSvrList = [
        RegSvr ||
        RegSvr = #reg_svr{svr_cid = 0, open_time = OpenTime} <- RegSvrList,
        OpenTime < Midnight
    ],
    NeedRegSvrListSorted = lists:keysort(#reg_svr.open_time, NeedRegSvrList),
    {RegCntNew, WaitRegSvrList, RegSvrListNew, RegSvrNeedSavedNew} =
    lists:foldl(
        fun (RegSvr, {Cnt, WaitList, RegSvrUpdt, NeedSavedUpdt}) ->
                CntNew = Cnt + 1,
                SvrCid = reg_svr_cid(CenterSvrId, CntNew),
                RegSvrNew = RegSvr#reg_svr{svr_cid = SvrCid},
                %% 收集本次注册需要保存的服数据
                WaitListNew = [RegSvrNew | WaitList],
                %% 更新RegSvrList中数据，删除RegSvrNeedSaved中数据
                ServerId = RegSvr#reg_svr.server_id,
                RegSvrUpdtNew = lists:keystore(
                    ServerId, #reg_svr.server_id, RegSvrUpdt, RegSvrNew
                ),
                NeedSavedUpdtNew = lists:keydelete(
                    ServerId, #reg_svr.server_id, NeedSavedUpdt
                ),
                {CntNew, WaitListNew, RegSvrUpdtNew, NeedSavedUpdtNew}
        end,
        {RegCnt, [], RegSvrList, RegSvrNeedSaved},
        NeedRegSvrListSorted),
    ?DEBUG_TTY(
        "old regCnt:~w, regSvrNeedSaved:~w~n"
        "new regCnt:~w, regSvrNeedSaved:~w~n"
        "wait reg svr list:~w", [
            RegCnt, RegSvrNeedSaved,
            RegCntNew, RegSvrNeedSavedNew,
            WaitRegSvrList
        ]),
    SaveFun = fun() ->
            db_save_reg_svr_list(WaitRegSvrList),
            db_save_reg_cnt(CenterSvrId, RegCntNew),
            ok
    end,
    case db:transaction(SaveFun) of
        ok ->
            % ?INFO_TTY("save ok!"),
            ets:insert(?ETS_REG_SVR_INFO, WaitRegSvrList),
            {RegCntNew, RegSvrListNew, RegSvrNeedSavedNew};
        _DbErr ->
            % ?INFO_TTY("db error:~w", [_DbErr]),
            {RegCnt, RegSvrList, RegSvrNeedSaved}
    end.

%% -------------------------------------------------------
%% 游戏服基本信息记录
%% -------------------------------------------------------
check_store_svr_base(GameNode, SvrBaseNeedSaved) ->
    #game_node{
        server_id = ServerId
        ,server_num = ServerNum
        ,server_name = ServerName
    } = GameNode,
    SvrBase = to_svr_base([ServerId, ServerNum, ServerName]),
    case lookup_svr_base(ServerId) of
        [#svr_base{} = SvrBase] ->
            %% 当前已有数据，且数据相符，则不需要更新存储
            SvrBaseNeedSaved;
        _ ->
            %% 当前未有数据，或者数据不符，则需要更新存储
            ets:insert(?ETS_ALL_SVR_BASE, SvrBase),
            clusters_center:apply_to_all_node(svr_base_local_api, update_svr_base, [SvrBase]),
            lists:keystore(ServerId, #svr_base.server_id, SvrBaseNeedSaved, SvrBase)
    end.

%% -------------------------------------------------------
%% 其他接口
%% -------------------------------------------------------

%% 设置下一次执行信息注册的定时器
set_next_svr_reg_timer() ->
    RemainSecs = calc_next_reg_time(),
    set_next_svr_reg_timer(RemainSecs, 0).

%% @doc 计算下一次执行信息注册的时间，用于设定定时器
%% calc_next_reg_time() -> RemainSecs :: non_neg_integer().
calc_next_reg_time() ->
    NowTime = utime:unixtime(),
    LogicMidnightHour = 1,
    NextTime = utime:get_next_logic_midnight(NowTime, LogicMidnightHour),
    NextTime - NowTime.

set_next_svr_reg_timer(RemainSecs, ErrCnt) ->
    Timer = erlang:start_timer(RemainSecs * 1000, self(), {svr_reg_timer, ErrCnt}),
    put(svr_reg_timer, Timer).

%% 检查是否需要新增设置定时器（有新增变动的时候就要调用）
check_set_reg_db_timer(#svr_reg_proc_state{reg_svr_need_saved = [_|_]}) ->
    case get(reg_db_timer) of
        undefined ->
            Timer = erlang:start_timer(?REG_DB_TIMER_DELAY, self(), reg_db_timer),
            put(reg_db_timer, Timer);
        _Timer ->
            %% 为了省去这里读取timer判断有效性，
            %% 则在timer过期时需要设置为undefined
            ignore
    end;
check_set_reg_db_timer(_) ->
    ignore.

check_set_svr_base_db_timer(#svr_reg_proc_state{svr_base_need_saved = [_|_]}) ->
    case get(svr_base_db_timer) of
        undefined ->
            Timer = erlang:start_timer(?SVR_BASE_DB_TIMER_DELAY, self(), svr_base_db_timer),
            put(svr_base_db_timer, Timer);
        _Timer ->
            %% 在timer过期时设置为undefined，则这里不需要判断是否有效
            ignore
    end;
check_set_svr_base_db_timer(_) ->
    ignore.
