%%% -------------------------------------------------------
%%% @author huangyongxing@yeah.net
%%% @doc
%%% 跨服分区相关接口
%%% @end
%%% -------------------------------------------------------
-module(czone_api).
%% 分区基础功能接口
-export([
        get_czone_info/1                    % 查询跨服小分区信息
        ,get_czone_combine_advance/1        % 查询跨服分区合并预告
        ,get_rezone_progress/1              % 查询跨服分区进度
        ,get_rezone_notice/1                % 获取跨服分区进度通知状态
        ,upload_world_level/0               % 世界等级资料上传到分区
        ,get_world_lv_reqt/1                % 从配置中获取世界等级需求
        ,notice_rezone_to_client/1          % 通知前端，跨服分区进入重新分区倒计时
        ,query_stage_award_state/1          % 跨服分区备战奖励状态查询
        ,recv_stage_award/1                 % 跨服分区备战奖励领取
        ,env_get_schedule/0
        ,env_set_schedule/1
        ,env_del_schedule/0
    ]).
%% 跨服主城相关接口
-export([
        role_zone/1
        ,server_zone/1
        ,env_get_zone/0
        ,env_set_zone/1
        ,env_get_zone_id/0
        ,env_get_faction/0
        ,env_del_zone/0
        ,center_city_open_day/0             % 跨服分区主城开放需求开服天数
        ,zone_open_check/0                  % 游戏服节点检查跨服分区开放
        ,zone_open_check_center/1           % 跨服中心节点检查分区开放
        ,zone_schedule_check/0              % 游戏服节点检查跨服分区开放及倒计时限制
        ,zone_schedule_check_center/1       % 跨服中心节点检查分区开放及倒计时限制
        ,zone_enter_base_check/1            % 跨服分区开放基本检查
        ,zone_enter_check_city/0            % 跨服分区主城开放基本检查
        ,zone_enter_check_city/1            % 跨服分区主城开放基本检查
        ,get_city_state/0                   % 获取跨服主城开放状态
        ,notice_city_state/0                % 通知跨服主城开放状态
        ,notice_city_state/1                % 通知跨服主城开放状态
        ,check_notice_city_state/0          % 在开放跨服主城当天午夜，需要通知前端状态变化
        ,handle_event/2                     % 角色事件回调
    ]).
-include("common.hrl").
-include("predefine.hrl").
-include("server.hrl").
-include("czone.hrl").
-include("center_svr_reg.hrl").
-include("def_event.hrl").
-include("rec_event.hrl").
-include("def_module.hrl").
-include("errcode.hrl").
-include("goods.hrl").

-spec get_czone_info(ServerId) -> {ok, Data} | error when
    ServerId :: non_neg_integer(),
    Data :: any().
%% 查询跨服小分区信息
get_czone_info(ServerId) ->
    CZoneInfo = clusters_node:apply_call(czone, get_czone_info, [ServerId]),
    case CZoneInfo of
        {ok, Stage, FactionNames, FactionDataList} ->
            ConvFun = fun conv_to_svr_cli_short/1,
            FactionList = fmt_faction_list_cli(ConvFun, FactionDataList, FactionNames, []),
            {ok, [Stage, FactionList]};
        unopened ->     % 未开放
            {ok, [0, []]};
        _Err ->
            error
    end.

%% -> [ {Faction, FactionSvrList} ]
fmt_faction_list_cli(ConvFun, [{Faction, SvrData} | SvrDataList], FactionNames, AccList) ->
    AccListNew =
    case lists:keytake(Faction, 1, AccList) of
        {value, {_, FactionName, FactionSvrList}, RestList} ->
            NewData = {Faction, FactionName, [ ConvFun(SvrData) | FactionSvrList]},
            [NewData | RestList];
        _ ->
            FactionName = find_faction_name(Faction, FactionNames),
            [{Faction, FactionName, [ConvFun(SvrData)]} | AccList]
    end,
    fmt_faction_list_cli(ConvFun, SvrDataList, FactionNames, AccListNew);
fmt_faction_list_cli(_ConvFun, [], _FactionNames, AccList) ->
    AccList.

%% 阵营名列表缓存的是存在自定义名称的阵营名，
%% 如果没有自定义名称，由前端根据阵营id使用默认的阵营名
find_faction_name(Faction, FactionNames = [_|_]) ->
    case lists:keyfind(Faction, 1, FactionNames) of
        {_, FactionName} ->
            FactionName;
        _ ->
            ""
    end;
find_faction_name(_Faction, _) ->
    "".

conv_to_svr_cli_short(ServerId) ->
    #svr_base{
        server_num = ServerNum,
        server_name = ServerName
    } = case svr_base_local_api:lookup_svr_base(ServerId) of
        [SvrBase] -> SvrBase;
        _ -> svr_base_local_api:unknown_svr_base(ServerId)
    end,
    {ServerId, ServerNum, ServerName}.

conv_to_svr_cli({ServerId, WorldLv}) ->
    {ServerId, ServerNum, ServerName} = conv_to_svr_cli_short(ServerId),
    {ServerId, ServerNum, ServerName, WorldLv}.

-spec get_czone_combine_advance(ServerId) -> {ok, Data} | error when
    ServerId :: non_neg_integer(),
    Data :: any().
%% 查询跨服分区合并预告
get_czone_combine_advance(ServerId) ->
    case clusters_node:apply_call(czone, get_combine_advance, [ServerId]) of
        {ok, ScheduledTime, NextStage, SvrFactions, FactionNames} ->
            ConvFun = fun conv_to_svr_cli_short/1,
            FactionSvrList = [{Faction, ServerIdX} || {ServerIdX, Faction} <- SvrFactions],
            FactionList = fmt_faction_list_cli(ConvFun, FactionSvrList, FactionNames, []),
            {ok, [ScheduledTime, NextStage, FactionList]};
        unopened ->
            {ok, [0, 0, []]};
        _Err ->
            error
    end.

-spec get_rezone_progress(ServerId) -> {ok, Data} | error when
    ServerId :: non_neg_integer(),
    Data :: any().
%% 查询跨服分区进度
get_rezone_progress(ServerId) ->
    case clusters_node:apply_call(czone, get_rezone_progress, [ServerId]) of
        {ok, NextStage, WorldLvAvgZone, WorldLvReqt, WorldLvAvg, SvrList} ->
            %% 暂定查看跨服进度时，显示默认阵营名，不显示个性化的阵营名
            FactionNames = [],
            ConvFun = fun conv_to_svr_cli/1,
            FactionList = fmt_faction_list_cli(ConvFun, SvrList, FactionNames, []),
            Data = [NextStage, WorldLvAvgZone, WorldLvReqt, WorldLvAvg, FactionList],
            {ok, Data};
        unopened ->
            {ok, [0, 0, 0, 0, []]};
        _Err ->
            error
    end.

-spec get_rezone_notice(ServerId) -> {ok, Data} | error when
    ServerId :: non_neg_integer(),
    Data :: any().
%% 获取跨服分区进度通知状态
get_rezone_notice(ServerId) ->
    case clusters_node:apply_call(czone, get_rezone_notice, [ServerId]) of
        {ok, ScheduledTime} ->
            {ok, [ScheduledTime]};
        unopened ->
            {ok, [0]};
        _Err ->
            error
    end.

%% 世界等级资料上传到分区
%% 实时上传到跨服czone
upload_world_level() ->
    ServerId = config:get_server_id(),
    WorldLv = util:get_world_lv(),
    clusters_node:apply_cast(czone, update_svr_world_level, [ServerId, WorldLv]).

%% 从配置中获取世界等级需求
get_world_lv_reqt(Stage) ->
    case data_czone:get_stage(Stage) of
        {_OpenDay, WorldLvReqt, _FactionCnt} ->
            WorldLvReqt;
        _ ->
            ?DEFAULT_WORLD_LV_REQT
    end.

%% 通知前端，跨服分区进入重新分区倒计时
notice_rezone_to_client(ScheduledTime) ->
    {ok, BinData} = pt_103:write(10303, [ScheduledTime]),
    lib_server_send:send_to_all(BinData).

%% 跨服分区备战奖励状态查询
-spec query_stage_award_state(RoleId) -> {Stage, RecvState} when
    RoleId :: non_neg_integer(),
    Stage :: non_neg_integer(),
    RecvState :: 0 | 1.
query_stage_award_state(RoleId) ->
    case env_get_zone() of
        #czone_svr_local{stage = Stage} ->
            RecvState =
            case data_czone:rezone_award(Stage) of
                [_|_] ->
                    Type = ?CZONE_COUNTER_STAGE_AWARD_STATE,
                    Count = lib_counter:get_count(RoleId, ?MOD_CZONE, Type),
                    case (not is_integer(Count)) orelse Count >= Stage of
                        true  -> 1;
                        false -> 0
                    end;
                _ ->
                    1
            end,
            {Stage, RecvState};
        _Undefined ->
            {0, 1}
    end.

%% 跨服分区备战奖励领取
-spec recv_stage_award(Player) -> {EvalCode, NewPlayer} | EvalCode when
    Player :: #player_status{}, NewPlayer :: #player_status{},
    EvalCode :: non_neg_integer().
recv_stage_award(Player = #player_status{id = RoleId}) ->
    case env_get_zone() of
        #czone_svr_local{stage = Stage} ->
            case data_czone:rezone_award(Stage) of
                [_|_] = Award ->
                    Type = ?CZONE_COUNTER_STAGE_AWARD_STATE,
                    Count = lib_counter:get_count(RoleId, ?MOD_CZONE, Type),
                    if
                        not is_integer(Count) ->    % 状态数据查询失败
                            ?ERRCODE(data_error);
                        Count >= Stage ->           % 已经领取过了
                            ?ERRCODE(reward_is_got);
                        true ->
                            CanGive = lib_goods_api:can_give_goods(Player, Award),
                            case CanGive =/= true of
                                true  ->
                                    %% 不能收纳道具（通常是背包空间不足）
                                    ?ERRCODE(err150_no_cell);
                                false ->
                                    lib_counter:set_count(RoleId, ?MOD_CZONE, Type, Stage),
                                    NewPlayer = send_stage_award(Player, Award),
                                    {?SUCCESS, NewPlayer}
                            end
                    end;
                _ ->
                    ?ERRCODE(data_error)
            end;
        _ ->
            ?ERRCODE(data_error)
    end.

%% 发送备战奖励
send_stage_award(Player, Award) ->
    Produce = #produce{
        type = czone, subtype = 0, reward = Award
        ,remark = ""
    },
    lib_goods_api:send_reward(Player, Produce).

%% 检查推送备战奖励状态信息
check_push_stage_award_state(Player) ->
    #player_status{id = RoleId, sid = Sid} = Player,
    {Stage, RecvState} = query_stage_award_state(RoleId),
    case RecvState > 0 of
        true  ->    % 已经领取过了，不需要推送
            ignore;
        false ->
            lib_server_send:send_to_sid(Sid, pt_103, 10304, [Stage, RecvState])
    end,
    ok.

%% -------------------------------------------------------
%% 供游戏节点查询、记录重新分区倒计时状态的接口
%% -------------------------------------------------------

-spec env_get_schedule() -> ScheduledTime :: non_neg_integer() | undefined.
%% 游戏服节点查询计划时间
%% 在即将分区前一小段时间内设置此值，
%% 相关功能入口的开放，可以根据查询此值是否undefined来确定
env_get_schedule() ->
    game_env:get({czone, schedule}).

%% 设置计划时间
env_set_schedule(ScheduledTime) ->
    game_env:set({czone, schedule}, ScheduledTime).

env_del_schedule() ->
    game_env:del({czone, schedule}).
%% -------------------------------------------------------

%% -------------------------------------------------------
%% 跨服主城相关
%%
%% 跨服主城归属分区信息会在分区时，同步缓存到游戏服节点，
%% 以方便直接在游戏服节点查找所属分区使用，不需要等待跨服返回查询结果
%% -------------------------------------------------------

%% 在跨服中心节点，根据角色Id查询所在分区ZoneId
role_zone(RoleId) ->
    RoleServerId = mod_player_create:get_serid_by_id(RoleId),
    ServerId = clusters_center:find_now_server_id(RoleServerId),
    czone:get_zone_id(ServerId).

%% 在跨服中心节点，根据游戏服查询所在分区ZoneId
server_zone(ServerId) ->
    czone:get_zone_id(ServerId).

%% -------------------------------------------------------
%% 供游戏节点查询、记录分区信息的接口
%% -------------------------------------------------------
-spec env_get_zone() -> CZoneSvrLocal :: #czone_svr_local{} | undefined.
%% 游戏服节点查询跨服分区信息
env_get_zone() ->
    game_env:get(czone).

env_set_zone(CZoneSvrLocal) when is_record(CZoneSvrLocal, czone_svr_local) ->
    game_env:set(czone, CZoneSvrLocal).

%% 在游戏服节点删除跨服分区信息
env_del_zone() ->
    game_env:del(czone).

-spec env_get_zone_id() -> non_neg_integer() | undefined.
env_get_zone_id() ->
    case env_get_zone() of
        #czone_svr_local{zone_id = ZoneId} -> ZoneId;
        _ -> undefined
    end.

env_get_faction() ->
    case env_get_zone() of
        #czone_svr_local{faction = Faction} -> Faction;
        _ -> 0
    end.
%% -------------------------------------------------------

%% 跨服分区开检查 - 游戏服使用
%% -> {true, ZoneId}  开放
%%    false           禁止（无分区、单服分区或其他错误）
zone_open_check() ->
    case env_get_zone() of
        #czone_svr_local{
            zone_id = ZoneId,
            stage = Stage
        } when Stage > 0 ->
            {true, ZoneId};
        _ -> false
    end.

%% 跨服分区开放检查 - 跨服中心节点使用
%% -> {true, ZoneId}  开放
%%    false           禁止（无分区、单服分区或其他错误）
zone_open_check_center(ServerId) ->
    case czone:get_zone_svr_local(ServerId) of
        #czone_svr_local{
            zone_id = ZoneId,
            stage = Stage
        } when Stage > 0 ->
            {true, ZoneId};
        _ ->
            false
    end.

%% 跨服分区开放及倒计时检查 - 游戏服使用
%% -> {true, ZoneId}  开放
%%    false           禁止或倒计时阶段
zone_schedule_check() ->
    case env_get_zone() of
        #czone_svr_local{
            zone_id = ZoneId,
            stage = Stage
        } when Stage > 0 ->
            case env_get_schedule() of
                undefined     -> {true, ZoneId};
                _ScheduleTime -> false
            end;
        _ ->
            false
    end.

%% 跨服分区开放及倒计时检查 - 跨服中心节点使用
%% -> {true, ZoneId}  开放
%%    false           禁止或倒计时阶段
zone_schedule_check_center(ServerId) ->
    case czone:get_scheduled_time(ServerId) of
        {_NoticeState, undefined, _, _} ->
            false;
        {false, ZoneId, _ScheduleTime, _NextStage} ->
            {true, ZoneId};
        {_NoticeState, ZoneId, 0, NextStage} when NextStage > 1 ->
            %% 1阶及以上分区，未处于倒计时状态才开放
            %% （0阶分区不关心状态，所以也返回false）
            {true, ZoneId};
        {true, _ZoneId, _ScheduledTime, _NextStage} ->
            false;
        _ ->
            false
    end.

%% 跨服主城开放的开服天数条件
center_city_open_day() ->
    case data_key_value:get(?CENTER_CITY_OPEN_KEY) of
        CenterCityOpenDay when is_integer(CenterCityOpenDay) ->
            CenterCityOpenDay;
        _ -> 9999
    end.

%% 进入跨服分区基本检查
%% 如果已知所处节点，也可以直接调用对应的接口：
%% zone_open_check/0 或 zone_open_check_center/1
%% -> {true, ZoneId} 开放
%%    false          禁止.
zone_enter_base_check(ServerId) ->
    IsCenterNode = clusters_api:is_center(),
    case IsCenterNode of
        true  -> zone_open_check_center(ServerId);
        false -> zone_open_check()
    end.

%% 跨服主城开放检查
%% -> true | false.
%%
%% 跨服主城开放检查 - 游戏服节点调用
zone_enter_check_city() ->
    case zone_open_check() of
        {true, _ZoneId} ->
            OpenDay = util:get_open_day(),
            OpenDay >= center_city_open_day();
        false -> false
    end.
%% 跨服主城开放检查 - 游戏服节点及跨服节点都可调用
zone_enter_check_city(ServerId) ->
    case zone_enter_base_check(ServerId) of
        {true, _ZoneId} ->
            IsCenterNode = clusters_api:is_center(),
            %% 还要检查是否已经开放跨服主城
            OpenDay =
            case IsCenterNode of
                true  ->
                    OpenTime = clusters_center:get_open_time(ServerId),
                    util:calc_open_day(OpenTime);
                false ->
                    util:get_open_day()
            end,
            OpenDay >= center_city_open_day();
        false -> false
    end.

%% 获取跨服主城开放状态
get_city_state() ->
    case zone_enter_check_city() of
        true  -> 1;
        false -> 0
    end.

%% 通知跨服主城开放状态
notice_city_state() ->
    notice_city_state(all).

notice_city_state(ConnPid) ->
    CityState = get_city_state(),
    {ok, BinData} = pt_103:write(10310, [CityState]),
    case ConnPid of
        all -> lib_server_send:send_to_all(BinData);
        _   -> lib_server_send:send_to_sid(ConnPid, BinData)
    end.

%% 在开放跨服主城当天午夜，需要通知前端状态变化
check_notice_city_state() ->
    case util:get_open_day() =:= center_city_open_day() of
        true  ->
            notice_city_state();
        false ->
            ignore
    end.

%% 角色进程回调事件
%% 完成分区合并
handle_event(Player, #event_callback{type_id = ?EVENT_CZONE_REZONE_FINSIH}) ->
    #player_status{scene = SceneId, x = X, y = Y, scene_pool_id = ScenePoolId} = Player,
    check_push_stage_award_state(Player),
    case lib_scene:is_center_city(SceneId) orelse
        convoy_api:is_center_convoy(SceneId) of
        true  ->
            case scene_mgr:get_pool_scope(SceneId) of
                {PoolMin, PoolMax} ->
                    case ScenePoolId >= PoolMin andalso ScenePoolId =< PoolMax of
                        true  ->    %% 在合适的分区线路
                            {ok, Player};
                        false ->    %% 在需要回收的分区线路，尽可能切换到新场景分线原位置
                            NewPlayer = lib_scene:change_scene(Player, SceneId, 0, 0, X, Y, []),
                            {ok, NewPlayer}
                    end;
                _False ->
                    %% 未有分区，应退出当前主城场景，请求切换场景会自动管理
                    NewPlayer = lib_scene:change_scene(Player, SceneId, 0, 0, X, Y, []),
                    {ok, NewPlayer}
            end;
        false ->
            {ok, Player}
    end;
%% 分区连接
handle_event(Player, #event_callback{type_id = ?EVENT_CZONE_CONN}) ->
    check_push_stage_award_state(Player),
    {ok, Player};
%% 分区初始
handle_event(Player, #event_callback{type_id = ?EVENT_CZONE_INIT}) ->
    check_push_stage_award_state(Player),
    {ok, Player};
handle_event(Player, _) ->
    {ok, Player}.

