%%% -------------------------------------------------------
%%% @author huangyongxing@yeah.net
%%% @doc
%%% 采集公共API
%%% @end
%%% -------------------------------------------------------
-module(collect_api).
%% 接口API
-export([
        check_stop_collect/1
        ,is_collect_mon/1
        ,is_no_count_limit/1
        ,collect_succ/2
        ,pre_check/2
        ,recalc_del_hp_driven/5
        ,clean_timeout_collectors/2
        ,valid_collectors/3
        ,collectors_del_hp/4
    ]).
%% 内部回调接口
-export([
        common_first_check/2
    ]).
-include("common.hrl").
-include("server.hrl").
-include("figure.hrl").
-include("errcode.hrl").
-include("scene.hrl").
-include("collect.hrl").
-include("battle.hrl").
%% 由场景进程调用，检测清理采集
%% 通常包括：
%% 1. 采集新怪物时，自动终止旧怪物的采集
%% 2. 离开场景时
%% 3. 玩家断开连接时
check_stop_collect(User = #ets_scene_user{id = RoleId, collect_pid = CollectPid}) ->
    case CollectPid of
        {OldMonPid, MonId, _MonCfgId} when is_pid(OldMonPid) ->
            mon_mgr:stop_collect(OldMonPid, MonId, RoleId);
        _ -> ignore
    end,
    User#ets_scene_user{collect_pid = {0, 0, 0}};
check_stop_collect(User) ->
    User.

%% 是否采集怪
-spec is_collect_mon(Kind :: non_neg_integer()) -> boolean().
is_collect_mon(?MON_KIND_COLLECT) -> true;
is_collect_mon(?MON_KIND_TASK_COLLECT) -> true;
is_collect_mon(_Kind) -> false.

%% 是否无次数限制的采集怪
-spec is_no_count_limit(Kind :: non_neg_integer()) -> boolean().
is_no_count_limit(?MON_KIND_TASK_COLLECT) -> true;
is_no_count_limit(_Kind) -> false.

%% --------------------------------------------------------------
%% 采集成功回调
%% --------------------------------------------------------------

%% 采集成功回调
collect_succ(Player = #player_status{
        id = RoleId, figure = Figure = #figure{guild_id = GuildId}
    },
    Mon = #scene_object{
        mon = #scene_mon{mid = Mid, create_key = _CreateKey}
    }) ->
    RecFigure = lib_role:figure_to_rec_picture(Figure),
    drop:mon_drop(Mon, #battle_return_atter{id = RoleId}, [#mon_atter{id = RoleId}], [#mon_atter{id = RoleId}]),
    lib_task_api:collect_mon(RoleId, Mid),
    camp_war_api:collect_mon(RoleId, RecFigure, GuildId, Mon),
    lib_guild_collect:collect_mon(RoleId, Mon),
    guild_war_api:collect_mon(RoleId, Mon),
    %% 回调接口列表  Module:Func(Player, Mon)
    MFList = [
        % {Module, collect_finish}
    ],
    NewPlayer = player_collect_succ_do(Player, Mon, MFList),
    {ok, NewPlayer}.

player_collect_succ_do(Player, MonInfo, [{Mod, Func} | MFList]) ->
    NewPlayer =
    case Mod:Func(Player, MonInfo) of
        #player_status{} = TmpPlayer -> TmpPlayer;
        {ok, #player_status{} = TmpPlayer} -> TmpPlayer;
        _ -> Player
    end,
    player_collect_succ_do(NewPlayer, MonInfo, MFList);
player_collect_succ_do(Player, _MonInfo, []) ->
    Player.

%% --------------------------------------------------------------
%% 采集检测
%% --------------------------------------------------------------

%% 采集预检测（角色进程先检查一些必要的条件）
-spec pre_check(Player, CheckArgs) -> true | {false, ErrCode} when
    Player :: #player_status{},
    CheckArgs :: #collect_args{},
    ErrCode :: non_neg_integer().
pre_check(Player, CheckArgs = #collect_args{}) ->
    %% 要求回调检查的接口，
    %%     返回值必须为true | {false, ErrCode}
    %%     输入参数列表统一为 [#player_status{}, #collect_args{}]
    CheckList = [
        {?MODULE, common_first_check}
        ,{lib_dungeon, check_collect}
        ,{guild_war_api, check_collect}
    ],
    callback_check_list(Player, CheckArgs, CheckList).

callback_check_list(Player, CheckArgs, [{Mod, Func} | RemainList]) ->
    case Mod:Func(Player, CheckArgs) of
        true ->
            callback_check_list(Player, CheckArgs, RemainList);
        Ret = {false, _} ->
            Ret;
        _ ->    % 错误返回结果
            {false, ?ERRCODE(err200_collec_wrong)}
    end;
callback_check_list(Player, CheckArgs, [_ | RemainList]) ->
    callback_check_list(Player, CheckArgs, RemainList);
callback_check_list(_Player, _CheckArgs, []) ->
    true.

%% 最早的公共性检查
common_first_check(Player, CheckArgs = #collect_args{option = Option}) ->
    case lists:member(Option, [?COLLECT_START, ?COLLECT_END, ?COLLECT_STOP]) of
        true  ->
            %% 检查背包空间（特定场景不检查）
            CellNum = lib_goods_api:get_cell_num(Player),
            CheckBag =
            case CellNum > 0 of
                true  -> true;
                false ->
                    %% 添加不需要检查的条件，达成条件返回true，否则返回false
                    lib_3v3_api:collect_pre_check(Player, CheckArgs)
            end,
            case CheckBag of
                true  -> true;
                false -> {false, ?ERRCODE(err200_goods_bag_limit)}
            end;
        false -> {false, ?ERRCODE(err200_collec_wrong)}
    end.

%% --------------------------------------------------------------
%% 通用的采集玩家掉血管理
%% --------------------------------------------------------------

%% 有新的采集者加入时，重新计算扣血定时器
%% （怪物管理进程执行）
recalc_del_hp_driven(GeneralDriven, _CLDelHpList, #scene_mon{collect_del_hp = 0}, _, _NowTimeMS) ->
    GeneralDriven;
recalc_del_hp_driven(GeneralDriven, _CLDelHpList, #scene_mon{collect_del_time = 0}, _, _NowTimeMS) ->
    GeneralDriven;
recalc_del_hp_driven(GeneralDriven, _CLDelHpList, _SceneMon, [], _NowTimeMS) ->
    mon_state_util:remove_driven_info(?COLLECTORS_DEL_HP, GeneralDriven);
recalc_del_hp_driven(GeneralDriven, CLDelHpList, #scene_mon{
        collect_del_time = CLDelTime
    }, Collectors, NowTimeMS) ->
    %% ?INFO_TTY("GeneralDriven:~w", [GeneralDriven]),
    case mon_state_util:check_member_driven_info(?COLLECTORS_DEL_HP, GeneralDriven) of
        true  ->
            %% 已经有定时任务，则忽略
            GeneralDriven;
        false ->
            After = calc_next_del_hp_time(NowTimeMS, CLDelHpList, CLDelTime, Collectors),
            case After >= 0 andalso After =< ?MAX_TIMEOUT of
                true  ->
                    DelayMSInfo = [{After, ?COLLECTORS_DEL_HP, check}],
                    mon_state_util:update_general_driven(GeneralDriven, DelayMSInfo, NowTimeMS);
                false ->
                    GeneralDriven
            end
    end.

%% 计算下一次扣血剩余时间 -> After :: non_neg_integer() | undefined.
calc_next_del_hp_time(_NowTime, _CLDelHpList, _CLDelTime, []) ->
    undefined;
calc_next_del_hp_time(NowTime, CLDelHpList, CLDelTime, Collectors) ->
    MinDelHpTime = calc_min_del_hp_time(NowTime, CLDelHpList, Collectors),
    max(MinDelHpTime + CLDelTime - NowTime, 0).

%% 计算采集者中，最后一次扣血最久远的时刻
calc_min_del_hp_time(NowTime, CLDelHpList, Collectors) ->
    {_, MinDelHpTime} = lists:foldl(
        fun (_, Acc = {[], _MinTime}) ->
                Acc;
            ({Id, _Pid, _Time}, Acc = {RemainDelHpList, MinTime}) ->
                case lists:keytake(Id, 1, RemainDelHpList) of
                    {value, {_, DelHpTime}, RemainList} ->
                        {RemainList, min(DelHpTime, MinTime)};
                    _False ->
                        Acc
                end;
            (_, Acc) -> Acc
        end, {CLDelHpList, NowTime}, Collectors),
    MinDelHpTime.

%% 处理采集者扣血，并清理多余的数据记录
%% -> {NewCollectors, NewCLDelHpList}
collectors_del_hp(NowTime, CLDelHpList, Collectors, Mon = #scene_object{
        scene = SceneId, scene_pool_id = PoolId,
        mon = #scene_mon{
            mid = _MonTypeId,
            collect_time = CLTime
            % collect_del_hp = CLDelHp
        }
    }) ->
    %% 注意：CLTime在配置中秒，而这里的NowTime、LastCollectTime是毫秒级数时间
    {NewCollectors, OverTimeList} = valid_collectors(NowTime, CLTime, Collectors),
    RemainCLDelHpList = [{Id, Time} || {Id, Time} <- CLDelHpList, lists:keymember(Id, 1, NewCollectors)],
    MinDelHpTime = calc_min_del_hp_time(NowTime, RemainCLDelHpList, NewCollectors),
    NewDelHpTime = MinDelHpTime + CLTime * 1000,
    %% 检查时，如果有未来0.1秒需要扣血的，也先扣血，减少定时器运转频率
    %% ?INFO_TTY("---mon:~w-MinDelHpTime:~w, NewDelHpTime:~w", [_MonTypeId, MinDelHpTime, NewDelHpTime]),
    CheckDelTime = NewDelHpTime + 100,
    {NewCLDelHpList, NeedDelHpRoleIds} =
    lists:foldl(fun({Id, _Pid, Time}, {CLDelHpListAcc, DelHpRoleIds}) ->
                FixDelHpTime =
                case lists:keyfind(Id, 1, CLDelHpListAcc) of
                    {Id, LastDelHpTime} when is_integer(LastDelHpTime) ->
                        LastDelHpTime;
                    _False ->   % 开始采集到现在未扣过血，以采集时间来判定是否需要扣血
                        Time
                end,
                case FixDelHpTime =< CheckDelTime of
                    true  ->
                        {
                            lists:keystore(Id, 1, CLDelHpListAcc, {Id, NewDelHpTime}),
                            [Id | DelHpRoleIds]
                        };
                    false ->
                        {CLDelHpListAcc, DelHpRoleIds}
                end;
            (_, Acc) -> Acc
        end, {RemainCLDelHpList, []}, NewCollectors),
    IsNoDie = true,
    case length(OverTimeList) > 0 of
        true  -> fix_mon_remain_state(NewCollectors, Mon);
        false -> ignore
    end,
    CLDelHp = 0,
    mod_scene_agent:apply_cast_with_scene(SceneId, PoolId, lib_battle, del_hp, [NeedDelHpRoleIds, 0, 0, CLDelHp, IsNoDie]),
    {NewCollectors, NewCLDelHpList}.

%% 清理超时的采集者信息
clean_timeout_collectors(Mon = #scene_object{
        mon = #scene_mon{
            kind = ?MON_KIND_COLLECT, collect_time = CLTime
        }
    }, Collectors) ->
    NowTime = utime:unixtime(ms),
    {NewCollectors, _OverTimeList} = valid_collectors(NowTime, CLTime, Collectors),
    fix_mon_remain_state(NewCollectors, Mon),
    NewCollectors.

%% -> {NewCollectors, OverTimeList}
valid_collectors(NowTime, CLTime, Collectors) ->
    %% 加几秒保护时间（时间也不宜过短，保护延迟高的玩家数据），
    %% 避免清理掉有效数据（前端本身就延迟了发送）
    Timeout = (CLTime + 5) * 1000,
    OverTime = NowTime - Timeout,
    lists:partition(fun({_CollectorId, _CollectorPid, Time}) ->
                Time > OverTime;
            (_) -> false
        end, Collectors).

%% 修复采集次数和状态
fix_mon_remain_state(Collectors, Mon = #scene_object{
        id = MonId, aid = MonPid,
        mon = #scene_mon{
            collect_count = CLCountMax, collect_times = CLCount
        }
    }) ->
    CollectorsLen = length(Collectors),
    RemainCount   = max(CLCountMax - CLCount - CollectorsLen, 0),
    IsCollecting  = case CollectorsLen > 0 of
        true  -> 1;
        false -> 0
    end,
    %% ?INFO_TTY("RemainCount:~w, IsCollecting:~w", [RemainCount, IsCollecting]),
    if
        RemainCount > 0 ->
            lib_scene_object:update(Mon, [{is_collecting, IsCollecting}, {has_cltimes, 1}]);
        IsCollecting =:= 1 ->
            lib_scene_object:update(Mon, [{is_collecting, IsCollecting}, {has_cltimes, 0}]);
        true ->
            mon_mgr:stop_mon(MonPid, MonId, 1)
    end.

