%%%-------------------------------------------------------------------
%%% @author liushl
%%% @doc
%%%     沙盘争夺战-总管理系统
%%% @end
%%%-------------------------------------------------------------------
-module(cross_point_fight_center).
-author("liushl").

-include("cross_point_fight.hrl").
-include("czone.hrl").
-include("errcode.hrl").
-include("common.hrl").

-behaviour(gen_server).

%% API
-export([start_link/0]).

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

-export([
    next_day/0                  %% 跨天处理
    , recalc_zone/1             %% 跨服小分区合区
    , handle_merge/3            %% 合服处理
    , node_connect/1            %% 游戏服连接上跨服中心
    , season_switch/1

    , ask_global_info/2         %% 查看沙盘信息-据点整体信息
    , ask_point_info/4          %% 查看单个据点信息
    , ask_point_log/4           %% 查看单个据点攻占记录
    , attack_point/3            %% 攻打据点
    , mass_status/4             %% 插旗
    , finish_dun/6              %% 副本结束上传攻城数据
    , refresh_point_score/4     %% 玩家据点攻打积分刷新榜单
    , get_seize_award/3         %% 领取据点占领奖励
    , role_data_update/3        %% 更新玩家数据

    , set_mass_status/8         %% 军团会议室回调：插旗
    , set_mass_status/4         %% 直接插旗，GM操作

    , apply_to_cluster_zone/1   %% 游戏服调用：将消息派发到各个分区处理

    , apply_to_zone/3           %% 跨服中心调用：将消息派发到各个分区处理
    , send_to_zone/3
    , send_after_zone/5

    , apply_cast/1
]).
-define(SERVER, ?MODULE).

%%%===================================================================
%%% @doc 系统逻辑处理
%%%===================================================================
%% 跨天处理
next_day() ->
    apply_cast(next_day).

%% 跨服小分区合区-赛季切换
recalc_zone(#{stage := Stage, zone_map := ZoneMap}) ->
    apply_cast({recalc_zone, Stage, ZoneMap}).

%% 服务器合并
handle_merge(NowServerId, NowServerNum, MergeServers) ->
    case czone:get_zone_svr_local(NowServerId) of
        #czone_svr_local{zone_id = ZoneId, stage = Stage, faction = Faction} when Stage >= ?POINT_BEGIN_CROSS_STAGE ->
            apply_to_zone(ZoneId, Faction, {merge, NowServerId, NowServerNum, MergeServers});
        _ ->
            ok
    end.

%% 游戏服连接上跨服中心-同步活动状态
node_connect(ServerId) ->
    case czone:get_zone_svr_local(ServerId) of
        #czone_svr_local{zone_id = ZoneId, stage = Stage, faction = Faction} when Stage >= ?POINT_BEGIN_CROSS_STAGE ->
            apply_to_zone(ZoneId, Faction, {node_connect, ServerId});
        _ ->
            ok
    end.

%% 赛季切换接口
season_switch(Type) ->
    apply_cast({season_switch, Type}).
%%%===================================================================
%%% @doc 玩家逻辑处理
%%%===================================================================
%% 查看沙盘信息-据点整体信息
ask_global_info(Sid, Info) ->
    apply_to_cluster_zone({ask_global_info, Sid, Info}).
%% 查看单个据点信息
ask_point_info(Sid, PointId, IsSweep, AwardTimes) ->
    apply_to_cluster_zone({ask_point_info, Sid, PointId, IsSweep, AwardTimes}).
%% 查看单个据点攻占记录
ask_point_log(Sid, PointId, MyScore, RoleId) ->
    apply_to_cluster_zone({ask_point_log, Sid, PointId, MyScore, RoleId}).
%% 攻打据点
attack_point(RoleId, PointId, Sweep) ->
    apply_to_cluster_zone({attack_point, PointId, Sweep, RoleId, node()}).
%% 插旗状态:先到军团会议室去检查权限
mass_status(RoleId, Name, PointId, Status) ->
    case czone_api:env_get_zone() of
        #czone_svr_local{zone_id = ZoneId, stage = Stage, faction = Faction} when Stage >= ?POINT_BEGIN_CROSS_STAGE ->
            clusters_node:apply_cast(mod_legion_mgr, check_point_mass, [ ZoneId, Faction, node(), RoleId, Name, PointId, Status ]);
        _ ->
            ignore
    end.
%% 副本结束上传攻城数据
finish_dun(RoleId, PointId, Score, Value, StartTime, SweepOrFight) ->
    apply_to_cluster_zone({finish_dun, RoleId, PointId, Score, Value, StartTime, SweepOrFight, node()}).
%% 玩家据点攻打积分刷新榜单
refresh_point_score(RoleInfo, PointId, Score, Hurt) ->
    apply_to_cluster_zone({refresh_point_score, RoleInfo, PointId, Score, Hurt}).
%% 领取据点占领奖励
get_seize_award(RolePid, PointId, LastAwardTime) ->
    apply_to_cluster_zone({get_seize_award, PointId, RolePid, node(), LastAwardTime}).
%% 更新玩家数据
role_data_update(RoleId, Key, Value) ->
    apply_to_cluster_zone({role_data_update, RoleId, Key, Value}).

%% 游戏服调用：将消息派发到各个分区处理
apply_to_cluster_zone(Msg) ->
    case czone_api:env_get_zone() of
        #czone_svr_local{zone_id = ZoneId, stage = Stage, faction = Faction} when Stage >= ?POINT_BEGIN_CROSS_STAGE ->
            clusters_node:apply_cast(?MODULE, apply_to_zone, [ ZoneId, Faction, Msg ]);
        _ ->
            %% 发一个错误码，表明沙盘没开
            ignore
    end.

%% 军团会议室回调：插旗
set_mass_status(_ZoneId, _Faction, Node, RoleId, _Name, _PointId, _Status, false) ->
    {ok, Bin} = pt_604:write(60405, [?ERRCODE(err604_mass_right), 0, 0]),
    lib_server_send:send_to_uid(Node, RoleId, Bin),
    ok;
set_mass_status(ZoneId, Faction, Node, RoleId, Name, PointId, Status, true) ->
    apply_to_zone(ZoneId, Faction, {mass_status, PointId, Status, RoleId, Name, Node}),
    ok.
set_mass_status(RoleId, Name, PointId, Status) ->
    apply_to_cluster_zone({mass_status, PointId, Status, RoleId, Name, node()}).
%% 跨服中心调用：将消息派发到各个分区处理
%% Faction 非必须，看具体逻辑需求
apply_to_zone(ZoneId, Faction, Msg) ->
    gen_server:cast(?MODULE, {apply_to_zone, ZoneId, Faction, Msg}).
send_to_zone(ZoneId, Faction, Msg) ->
    ?MODULE ! {apply_to_zone, ZoneId, Faction, Msg}.
send_after_zone(Time, Pid, ZoneId, Faction, Msg) ->
    erlang:send_after(Time * 1000, Pid, {apply_to_zone, ZoneId, Faction, Msg}).

apply_cast(Msg) ->
    gen_server:cast(?MODULE, Msg).
%%--------------------------------------------------------------------
%% @doc
%% Starts the server
%%
%% @end
%%--------------------------------------------------------------------
-spec(start_link() ->
    {ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
start_link() ->
    gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Initializes the server
%%
%% @spec init(Args) -> {ok, State} |
%%                     {ok, State, Timeout} |
%%                     ignore |
%%                     {stop, Reason}
%% @end
%%--------------------------------------------------------------------
-spec(init(Args :: term()) ->
    {ok, State :: #point_fight_center{}} | {ok, State :: #point_fight_center{}, timeout() | hibernate} |
    {stop, Reason :: term()} | ignore).
init([]) ->
    %% 加载各个分区的据点信息
    Now = utime:unixtime(),
    AllZones = db:get_all(?DB_GET_ZONE_INFO),
    Zones = lists:foldl(
        fun([ZoneId, SeasonId, SeasonTime, PointMassBin, PointBuffBin, BuffTime], Temp) ->
            case BuffTime > Now of
                true  ->
                    PointBuff = util:bitstring_to_term(PointBuffBin),
                    BuffEnd   = BuffTime;
                false ->
                    PointBuff = [],
                    BuffEnd   = 0
            end,
            ZoneData = #point_fight_zone{
                zone_id     = ZoneId,
                season_id   = SeasonId,
                season_time = SeasonTime,
                point_mass   = util:bitstring_to_term(PointMassBin),
                point_buff   = PointBuff,
                buff_time    = BuffEnd
            },
            Temp#{ZoneId => ZoneData}
        end, #{}, AllZones
    ),
    [ apply_to_zone(ZoneId, 0, init) || ZoneId <- maps:keys(Zones) ],
    self() ! check_point_buff,
    {ok, #point_fight_center{zone_data = Zones}}.

%%%===================================================================
%%% Logic 
%%%===================================================================
do_handle_call(_Request, _From, State) ->
    io:format("time:[~p], module:~p: do_handle_call unexpect message:~p~n", [erlang:localtime(), ?MODULE, _Request]),
    {reply, ok, State}.

%% 分区变化-开始新的赛季
do_handle_event({recalc_zone, Stage, ZoneMap}, State) ->
    #point_fight_center{zone_data = Zones} = State,
    [{OldZone, NewZone}|_] = maps:to_list(ZoneMap),
    case Stage >= ?POINT_BEGIN_CROSS_STAGE of
        true  ->
            case maps:is_key(NewZone, Zones) of
                true ->
                    %% 已经存在数据，则进行赛季切换
                    Zones1 = Zones;
                false ->
                    %% 没有数据，表明是第一次开启赛季, 先构造一份数据，从无到有，也属于赛季切换
                    %% 从第0赛季切换到第1赛季
                    NewZoneData = #point_fight_zone{zone_id = NewZone},
                    Zones1 = Zones#{NewZone => NewZoneData}
            end,
            State1 = State#point_fight_center{zone_data = Zones1},
            %% 先将新的分区进行赛季切换
            apply_to_zone(NewZone, 0, {season_switch, ?SWITCH_TYPE_RECALC_ZONE}),
            %% 然后关闭 OldZone 的赛季
            apply_to_zone(OldZone, 0, season_close),
            {noreply, State1};
        false ->
            {noreply, State}
    end;

%% 跨天：判断定时检测BUFF/赛季重置
do_handle_event(next_day, State) ->
    %% 通知军团会议室
    mod_legion_rank_mgr:day_timer(),
    Week = utime:day_of_week(),
    case Week of
        1 ->
            %% 周一赛季重置
            do_handle_event({season_switch, ?SWITCH_TYPE_FIXED_TIME}, State);
        _ ->
            ok
    end,
    do_handle_event(check_point_buff, State);

%% 赛季切换
do_handle_event({season_switch, Type}, State) ->
    #point_fight_center{zone_data = Zones} = State,
    [ apply_to_zone(ZoneId, 0, {season_switch, Type}) || ZoneId <- maps:keys(Zones) ],
    {noreply, State};

%% 判断据点BUFF
do_handle_event(check_point_buff, State) ->
    case cross_point_fight_logic:get_point_cfg(buff_time, {0, 0, 0}) of
        {Week, {H, M}, _Last} ->
            case utime:day_of_week() =:= Week of
                true  ->
                    BuffTime = utime:unixtime({H, M, 0}),
                    Now = utime:unixtime(),
                    case BuffTime > Now of
                        true  ->
                            util:cancel_timer(State#point_fight_center.ref_buff),
                            NewState = State#point_fight_center{
                                ref_buff = erlang:send_after((BuffTime - Now) * 1000, self(), active_point_buff)
                            },
                            {noreply, NewState};
                        false ->
                            {noreply, State}
                    end;
                false ->
                    {noreply, State}
            end;
        _ ->
            {noreply, State}
    end;

do_handle_event(active_point_buff, State) ->
    #point_fight_center{zone_data = Zones} = State,
    case cross_point_fight_logic:get_point_cfg(buff_time, {0, 0, 0}) of
        {Week, {_H, _M}, Last} when Last > 0 ->
            case utime:day_of_week() =:= Week of
                true  ->
                    [ apply_to_zone(ZoneId, 0, {active_point_buff, Last}) || ZoneId <- maps:keys(Zones) ],
                    ok;
                false ->
                    ok
            end;
        _ ->
            ok
    end,
    {noreply, State};

%% 分区通用数据处理
do_handle_event({apply_to_zone, ZoneId, Faction, Msg}, State) ->
    #point_fight_center{zone_data = Zones} = State,
    case maps:find(ZoneId, Zones) of
        {ok, ZoneData} ->
            case cross_point_fight_zone:handle(Msg, Faction, ZoneData) of
                #point_fight_zone{} = NewZoneData ->
                    NewZones = Zones#{ZoneId => NewZoneData},
                    {noreply, State#point_fight_center{zone_data = NewZones}};
                remove_self ->
                    NewZones = maps:remove(ZoneId, Zones),
                    {noreply, State#point_fight_center{zone_data = NewZones}};
                _ ->
                    {noreply, State}
            end;
        error ->
            {noreply, State}
    end;

%% 清理活动数据

%% 删除分区数据
do_handle_event({del_zone, ZoneId, N}, State) ->
    Self = self(),
    N =< 5 andalso spawn(
        fun() ->
            case catch db_del_zone(ZoneId) of
                ok ->
                    ok;
                Error ->
                    ?ERR("~p", [Error]),
                    erlang:send_after(5000, Self, {del_zone, ZoneId, N + 1})
            end
        end
    ),
    {noreply, State};

%% 清理赛季数据
do_handle_event({clean_zone_data, ZoneId, N}, State) when N =< 5 ->
    Self = self(),
    N =< 5 andalso spawn(
        fun() ->
            F = fun() ->
                %% 清理沙盘数据
                db:execute(io_lib:format(?DB_CLEAN_ZONE_POINTS, [ZoneId])),
                db:execute(io_lib:format(?DB_CLEAN_ZONE_BATTLE_LOG, [ZoneId])),
                db:execute(io_lib:format(?DB_CLEAN_ROLE_DATA, [ZoneId])),
                %% 清理军团会议室数据
                mod_legion_rank_mgr:db_reset_issue(ZoneId),
                ok
                end,
            case db:transaction(F) of
                ok ->
                    ok;
                Other ->
                    ?ERR("~p", [Other]),
                    erlang:send_after(5000, Self, {clean_zone_data, ZoneId, N + 1})
            end
        end
    ),
    {noreply, State};
do_handle_event(_Request, State) ->
    {noreply, State}.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling call messages
%%
%% @end
%%--------------------------------------------------------------------
-spec(handle_call(Request :: term(), From :: {pid(), Tag :: term()},
    State :: #point_fight_center{}) ->
    {reply, Reply :: term(), NewState :: #point_fight_center{}} |
    {reply, Reply :: term(), NewState :: #point_fight_center{}, timeout() | hibernate} |
    {noreply, NewState :: #point_fight_center{}} |
    {noreply, NewState :: #point_fight_center{}, timeout() | hibernate} |
    {stop, Reason :: term(), Reply :: term(), NewState :: #point_fight_center{}} |
    {stop, Reason :: term(), NewState :: #point_fight_center{}}).
handle_call(Msg, From, State) ->
    try
        do_handle_call(Msg, From, State)
    catch _:_Error:Trace ->
        io:format("[~p]~nhandle_call Msg:~p~n Error:~p~n ~p~n", [erlang:localtime(), Msg, _Error, Trace]),
        {reply, error, State}
    end.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling cast messages
%%
%% @end
%%--------------------------------------------------------------------
-spec(handle_cast(Request :: term(), State :: #point_fight_center{}) ->
    {noreply, NewState :: #point_fight_center{}} |
    {noreply, NewState :: #point_fight_center{}, timeout() | hibernate} |
    {stop, Reason :: term(), NewState :: #point_fight_center{}}).
handle_cast(Msg, State) ->
    try
        do_handle_event(Msg, State)
    catch _:_Error:Trace ->
        io:format("[~p]~nhandle_cast Msg:~p~n Error:~p~n ~p~n", [erlang:localtime(), Msg, _Error, Trace]),
        {noreply, State}
    end.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling all non call/cast messages
%%
%% @spec handle_info(Info, State) -> {noreply, State} |
%%                                   {noreply, State, Timeout} |
%%                                   {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
-spec(handle_info(Info :: timeout() | term(), State :: #point_fight_center{}) ->
    {noreply, NewState :: #point_fight_center{}} |
    {noreply, NewState :: #point_fight_center{}, timeout() | hibernate} |
    {stop, Reason :: term(), NewState :: #point_fight_center{}}).
handle_info(Msg, State) ->
    try
        do_handle_event(Msg, State)
    catch _:_Error:Trace ->
        io:format("[~p]~nhandle_info Msg:~p~n Error:~p~n ~p~n", [erlang:localtime(), Msg, _Error, Trace]),
        {noreply, State}
    end.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any
%% necessary cleaning up. When it returns, the gen_server terminates
%% with Reason. The return value is ignored.
%%
%% @spec terminate(Reason, State) -> void()
%% @end
%%--------------------------------------------------------------------
-spec(terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()),
    State :: #point_fight_center{}) ->
    term()).
terminate(_Reason, _State) ->
    ok.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Convert process state when code is changed
%%
%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
%% @end
%%--------------------------------------------------------------------
-spec(code_change(OldVsn :: term() | {down, term()}, State :: #point_fight_center{},
    Extra :: term()) ->
    {ok, NewState :: #point_fight_center{}} | {error, Reason :: term()}).
code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

%%%===================================================================
%%% Internal functions
%%%===================================================================
%% 删除活动数据
db_del_zone(ZoneId) ->
    SQL = io_lib:format(?DB_CLEAN_ZONE_INFO, [ZoneId]),
    db:execute(SQL),
    ok.