%%% -------------------------------------------------------
%%% @author huangyongxing@yeah.net
%%% 数据初始化公共服务及操作代理
%%% -------------------------------------------------------
-module(game_init).
-behaviour(gen_server).
-export([
        start_link/0
        ,get_merge_servers_from_db/1
    ]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-include("common.hrl").
-include("server.hrl").
-include("server_env.hrl").
-include("server_kv.hrl").
-include("buff.hrl").
-include("scene.hrl").
-include("team.hrl").
-include("relationship.hrl").
-include("rec_offline.hrl").
-include("role.hrl").
-include("goods.hrl").
-include("def_goods.hrl").
-include("fashion.hrl").
-include("chat.hrl").
-include("attr.hrl").
-include("custom_act.hrl").
-include("battle_field.hrl").
-include("activitycalen.hrl").
-include("dungeon_multi.hrl").
-include("convoy.hrl").
-include("mate.hrl").

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

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

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

init([]) ->
    process_flag(trap_exit, true),
    %% 更新所有角色在线标记
    ok = init_online(),
    %% 敏感词汇表
    ok = mod_word:init(),
    %% 初始ets表
    ok = init_ets(),
    %% 初始全服各游戏服基础信息数据表
    ok = svr_base_local_api:game_init_ets(),
    ok = check_init_merge_servers(),
    % ok = db_upgrade:execute(),
    ok = init_server_env(),
    ok = init_server_kv(),
    ok = lib_custom_act_dynamic_compile:dynamic_compile_cfg(),
    %% 临时修复装备数据
    spawn(equipment_api, gm_fix_bag, []),
    {ok, ?MODULE}.

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

handle_call(_R , _FROM, State) ->
    {reply, ok, State}.

%% 在获取RegServerId失败后，定时尝试重新获取；超出次数后放弃
handle_info({timeout, _Timer, {check_reg_server_id, Count}}, State) ->
    CurrCount = Count + 1,
    check_reg_server_id() =:= undefined
    andalso CurrCount < 20
    andalso erlang:start_timer(60000, self(), {check_reg_server_id, Count + 1}),
    {noreply, State};

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

terminate(_Reason, State) ->
    [{_, AwardLog}] = lib_server_kv:get_value([{?SERVER_KV_LEVEL_AWARD_LOG, []}]),
    AwardLog =/= [] andalso lib_server_kv:update_server_kv_db(
        #server_kv{key = ?SERVER_KV_LEVEL_AWARD_LOG, value = AwardLog, time = utime:unixtime()}
    ),
    {ok, State}.

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

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

%% 初始ETS表
init_ets() ->
    ets:new(?SERVER_ENV,           [{keypos, #server_env.name}, named_table, public, set]),             % 服务器信息
    ets:new(?SERVER_KV,            [{keypos, #server_kv.key}, named_table, public, set]),               % 服务器键值kv
    ets:new(?ETS_SYS_NOTICE,       [named_table, public, set]),                                         % 系统公告
    ets:new(?ETS_ONLINE,           [{keypos, #ets_online.id}, named_table, public, set]),               % 玩家在线数据
    ets:new(?ETS_BUFF,             [{keypos, #ets_buff.id}, named_table, public, set]),                 % 玩家buff
    ets:new(?ETS_TEAM,             [{keypos, #ets_team.team_id}, named_table, public, set]),            % 队伍
    ets:new(?ETS_OFFLINE_ACCESS,   [{keypos, #ets_offline.id}, named_table, public, set]),              % 离线玩家
    ets:new(?ETS_OFFLINE_PLAYER,   [{keypos, #player_status.id}, named_table, public, set]),            % 离线访问
    ets:new(?ETS_ROLE_SHOW,        [{keypos, #ets_role_show.id}, named_table, public, set]),
    ets:new(?ETS_ROLE_FUNC_CHECK,  [{keypos, #ets_role_func_check.key_id}, named_table, public, set]),
    ets:new(?ETS_CUSTOM_ACT,       [{keypos, #act_info.key}, named_table, public, set]),                % 当前开启的定制活动

    ets:new(?ETS_ROLE_BATTLE_PID,  [{keypos, #role_battle_pid.id}, named_table, public, set]),          % 玩家战场pid
    ets:new(?ETS_ACTIVITYCALEN,    [{keypos, #ets_activitycalen.id}, named_table, public, set]),        % 玩家活跃度信息
    ets:new(?DUN_MULTI_ROLE_ETS,   [{keypos, #dun_multi_role.id}, named_table, public, set]),
    % ets:new(?ETS_CONVOY_ROLE,      [{keypos, #ets_convoy_role.role_id}, named_table, public, set]),     % 护送数据
    ets:new(?ETS_ROLE_RELA,        [{keypos, #ets_role_rela.key}, named_table, public, set]),             % 好友关系数据
    ets:new(?ETS_ROLE_LOGOUT_TIME, [{keypos, 1}, named_table, public, set]),                              % 玩家离线时间
    ok.

%% 启动时初始化修正online_flag
init_online() ->
    db:execute("UPDATE `player_login` SET `online_flag`=0 WHERE `online_flag`<>0"),
    ok.

%% 服务器状态
init_server_env() ->
    #{
        merge_time := MergeTime
        ,open_time := OpenTime
        ,merge_count := MergeCount
        ,real_open_time := RealOpenTime
    } = init_server_open_info(),
    %% 游戏服名字
    ServerName = case catch db:get_row(<<"select `cf_value` from base_game where `cf_name` = 'game_title'">>) of
        [_ServerName] -> _ServerName;
        _ ->
            ServerId = config:get_server_id(),
            list_to_binary([<<"Unknown S"/utf8>>, integer_to_binary(ServerId)])
    end,
    ets:insert(?SERVER_ENV, #server_env{name=open_time, value=OpenTime}),
    ets:insert(?SERVER_ENV, #server_env{name=merge_time, value=MergeTime}),
    ets:insert(?SERVER_ENV, #server_env{name=merge_count, value=MergeCount}),
    ets:insert(?SERVER_ENV, #server_env{name=real_open_time, value=RealOpenTime}),
    ets:insert(?SERVER_ENV, #server_env{name=server_name, value=ServerName}),
    %% 注册系统中的游戏服号
    check_reg_server_id() =:= undefined andalso erlang:start_timer(60000, self(), {check_reg_server_id, 1}),
    ok.

%% 服务器常用键值
init_server_kv()->
    case db:get_all(<<"select `key`, `value`, `time` from `server_kv`">>) of
        List when is_list(List) ->
            F = fun([Key, Value, Time]) ->
                        V = util:bitstring_to_term(Value),
                        ets:insert(?SERVER_KV, #server_kv{key = Key, value = V, time = Time})
                end,
            [F(X) || X <- List];
        _ ->
            skip
    end,
    ok.

check_init_merge_servers() ->
    SerId = config:get_server_id(),
    MergeSerIds = get_merge_servers_from_db(SerId),
    if
        is_integer(SerId) == false orelse SerId == 0 -> exit({server_id_err, SerId});
        is_list(MergeSerIds) == false -> exit({merge_server_ids_err, MergeSerIds});
        true -> skip
    end,
    case lists:member(SerId, MergeSerIds) of
        true  -> ok;
        false -> exit({server_id_not_in_merge, SerId, MergeSerIds})
    end.

get_merge_servers_from_db(ServerId) ->
    Sql = "SELECT DISTINCT `server_id` FROM `player_login`",
    All = db:get_all(Sql),
    MergeServerIdsInDb =
    case is_list(All) of
        true  -> lists:append(All);
        false -> []
    end,
    InitMergeServeIds =
    case lists:member(ServerId, MergeServerIdsInDb) of
        true  -> MergeServerIdsInDb;
        false -> [ServerId | MergeServerIdsInDb]
    end,
    %% 对merge_server_ids进行排序，方便对比差异
    ets:insert(?SERVER_ENV,
        #server_env{
            name = merge_server_ids
            ,value = lists:sort(InitMergeServeIds)
        }
    ),
    InitMergeServeIds.

%% 检测设置reg_server_id
%% 与server_id不一样，server_id是后台服号，属于内部编号；
%% 注册系统的r_server_id是外部对接用的编号，
%% 此值由php在游戏服部署后尚处于测试服状态时，每10分钟检测并重写入此值，
%% 所以从游戏服的角度来说，理论上有可能取出空值或错误值（运维从其他服拷贝数据进行部署），
%% 但是正式服应该不会，这属于游戏服无法控制的因素，不予理会，姑且认为有值就是对的，
%% 无值则需要定期获取，在php写入成功后取得正确结果。但是如果超出一定次数则放弃
check_reg_server_id() ->
    RegServerId = config:get_reg_server_id(),
    case is_integer(RegServerId) of
        true  ->
            RegServerId;
        false ->
            NewRegServerId =
            case db:get_one(<<"SELECT `cf_value` FROM `base_game` WHERE `cf_name`='r_server_id'">>) of
                null ->
                    %% 数据库无此值（可能为开发版服务器，或者处于刚部署好尚未写入数据状态的游戏服）
                    undefined;
                BinStr ->
                    case string:to_integer( unicode:characters_to_list(BinStr) ) of
                        {Id, []} when Id >= 0 ->
                            Id;
                        _ ->    % 服号数据配置异常，理论上应该是一个非负整数
                            undefined
                    end
            end,
            ets:insert(?SERVER_ENV, #server_env{name = reg_server_id, value = NewRegServerId}),
            NewRegServerId
    end.

init_server_open_info() ->
    %% 服务器合服时间、开服时间、合服次数
    Sql = ?SERVER_ENV_SELECT,
    TimeInfoList = db:get_all(Sql),
    InitMap = #{
        merge_time => undefined,
        open_time => undefined,
        merge_count => undefined
    },
    #{
        merge_time := MergeTime,
        open_time := OpenTime,
        merge_count := MergeCount
    } = split_server_time_infos(TimeInfoList, InitMap),
    NewMergeTime =
    case is_integer(MergeTime) of
        true  -> MergeTime;
        false -> 0
    end,
    NewMergeCount =
    case is_integer(MergeCount) of
        true  -> MergeCount;
        false -> 0
    end,
    %% 如果在merge_server表中存在数据，则以之为准，否则以标准规则计算开服时间
    %% merge_server表中的数据，仅用于特殊需要，外部手工指定开服时间（不需要回写数据）
    {NewOpenTime, RealOpenTime} =
    case is_integer(OpenTime) of
        true  ->
            {utime:unixdate(OpenTime), OpenTime};
        false ->
            %% 最早的服的服务器开服时间
            %% 为了使结果尽量稳定（个人开发机器角色数少），
            %% 所以不取前10个号中最晚注册时间，直接先取第11个角色，不存在取第1个角色的注册时间
            case db:get_row(<<"SELECT `reg_time` FROM `player_login` ORDER BY `id` ASC LIMIT 10,1">>) of
                [RegTime] when is_number(RegTime) ->
                    {utime:unixdate(RegTime), RegTime};
                [] ->
                    case db:get_row(<<"SELECT `reg_time` FROM `player_login` ORDER BY `id` ASC LIMIT 1">>) of
                        [RegTime] when is_number(RegTime) ->
                            {utime:unixdate(RegTime), RegTime};
                        _ ->
                            {utime:unixdate(), utime:unixtime()}
                    end;
                _ ->
                    {utime:unixdate(), utime:unixtime()}
            end
    end,
    #{
        merge_time => NewMergeTime
        ,open_time => NewOpenTime
        ,merge_count => NewMergeCount
        ,real_open_time => RealOpenTime
    }.

split_server_time_infos([[_Type = 1, MergeTime] | Others], TimeInfoMap) ->
    NewTimeInfoMap = TimeInfoMap#{merge_time => MergeTime},
    split_server_time_infos(Others, NewTimeInfoMap);
split_server_time_infos([[_Type = 2, OpenTime] | Others], TimeInfoMap) ->
    NewTimeInfoMap = TimeInfoMap#{open_time => OpenTime},
    split_server_time_infos(Others, NewTimeInfoMap);
split_server_time_infos([[_Type = 3, MergeCount] | Others], TimeInfoMap) ->
    NewTimeInfoMap = TimeInfoMap#{merge_count => MergeCount},
    split_server_time_infos(Others, NewTimeInfoMap);
split_server_time_infos([_ | Others], TimeInfoMap) ->
    split_server_time_infos(Others, TimeInfoMap);
split_server_time_infos([], TimeInfoMap) ->
    TimeInfoMap.
