%%% -------------------------------------------------------
%%% @author huangyongxing@yeah.net
%%% @doc
%%% 怪物逻辑基础 工具性质的操作接口
%%% @end
%%% -------------------------------------------------------
-module(mon_state_util).
-export([
        %% 处理general_driven和state_driven的接口
        update_general_driven/3             % 更新一般事件驱动数据general_driven
        ,update_general_driven/2            % 更新一般事件驱动数据general_driven
        ,update_state_driven/3              % 更新状态驱动数据state_driven
        ,update_state_driven/2              % 更新状态驱动数据state_driven
        ,conv_delay_driven/2                % 延迟数据转换为具体时刻的驱动事件数据
        ,conv_delay_driven/1                % 延迟数据转换为具体时刻的驱动事件数据
        ,check_member_driven_info/2         % 检查是否包含指定driven_info()
        ,remove_driven_info/2               % 移除（单条）指定driven_info()
        ,filter_driven_info/2               % （使用指定函数判断）移除（全部）指定driven_info()
        ,empty_state_driven/0               % 空状态驱动数据 
        ,remove_state_driven/1              % 移除#ob_act{}中的状态驱动state_driven

        ,filter_special_event/1             % 取消特殊的special_event（对应旧版eref）
        ,filter_when_resume/1               % 取消怪物resume时需要取消的驱动事件
    ]).

-include("common.hrl").
-include("scene.hrl").

%% 毫秒数
-type microtime()  :: non_neg_integer().

%% 驱动数据中的延迟信息
%% 注意：
%% Type == add时    不取消列表中相同事件，直接增加事件
%% Type == check    检查列表中是否存在相同事件，存在则不添加，不存在才添加事件
%% Type == update   取消列表中前一个相同的事件，添加新事件
%% Type缺省为update，即不提供Type时，采用update的逻辑。因为这符合大多数逻辑所需
-type delay_info() ::
{DelayMS :: microtime(), Info :: any()} |
{DelayMS :: microtime(), Info :: any(), Type :: add | check | update}.

%% 单个驱动数据
-type driven_info() :: {TimeMS :: microtime(), Info :: any()}.

%% --------------------------------------------------------
%% 处理general_driven和state_driven的接口
%% --------------------------------------------------------

%% 更新一般事件驱动数据general_driven
-spec update_general_driven
(ObjData, DelayGeneralDriven, NowTimeMS) -> NewObjData when
    NowTimeMS           :: microtime(),
    DelayGeneralDriven  :: [ delay_info() ],
    ObjData             :: #ob_act{},
    NewObjData          :: #ob_act{};
(GeneralDriven, DelayGeneralDriven, NowTimeMS) -> NewGeneralDriven when
    NowTimeMS           :: microtime(),
    DelayGeneralDriven  :: [ delay_info() ],
    GeneralDriven       :: [ driven_info() ],
    NewGeneralDriven    :: [ driven_info() ].
update_general_driven(ObjData = #ob_act{general_driven = GeneralDriven}, DelayGeneralDriven = [_|_], NowTimeMS) ->
    NewGeneralDriven = update_general_driven(GeneralDriven, DelayGeneralDriven, NowTimeMS),
    ObjData#ob_act{general_driven = NewGeneralDriven};
update_general_driven(GeneralDriven, DelayGeneralDriven = [_|_], NowTimeMS) ->
    conv_delay_driven_2(GeneralDriven, DelayGeneralDriven, NowTimeMS);
update_general_driven(Value, _DelayDriven, _NowTimeMS) ->
    Value.

%% 更新一般事件驱动数据general_driven
-spec update_general_driven
(ObjData, DelayGeneralDriven) -> NewObjData when
    DelayGeneralDriven  :: [ delay_info() ],
    ObjData             :: #ob_act{},
    NewObjData          :: #ob_act{};
(GeneralDriven, DelayGeneralDriven) -> NewGeneralDriven when
    DelayGeneralDriven  :: [ delay_info() ],
    GeneralDriven       :: [ driven_info() ],
    NewGeneralDriven    :: [ driven_info() ].
update_general_driven(Value, DelayGeneralDriven) ->
    NowTimeMS = utime:unixtime(ms),
    update_general_driven(Value, DelayGeneralDriven, NowTimeMS).

%% 更新状态驱动数据state_driven
-spec update_state_driven(ObjData, DelayDriven, NowTimeMS) -> NewObjData when
    NowTimeMS   :: microtime(),
    DelayDriven :: [ delay_info() ],
    ObjData     :: #ob_act{},
    NewObjData  :: #ob_act{}.
update_state_driven(ObjData = #ob_act{}, DelayDriven = {_DelayMS, _Info}, NowTimeMS) ->
    NewStateDriven = conv_delay_driven(DelayDriven, NowTimeMS),
    ObjData#ob_act{state_driven = NewStateDriven}.

%% 更新状态驱动数据state_driven
-spec update_state_driven(ObjData, DelayDriven) -> NewObjData when
    DelayDriven :: [ delay_info() ],
    ObjData     :: #ob_act{},
    NewObjData  :: #ob_act{}.
update_state_driven(ObjData = #ob_act{}, DelayDriven) ->
    NowTimeMS = utime:unixtime(ms),
    update_state_driven(ObjData, DelayDriven, NowTimeMS).

%% 将延迟数据转换为具体时刻的驱动事件数据
%% 一般用于需要直接赋值driven时使用
-spec conv_delay_driven(DelayDriven, NowTimeMS) -> NewDriven when
    NowTimeMS   :: microtime(),
    DelayDriven :: delay_info()  | [ delay_info() ],
    NewDriven   :: driven_info() | [ driven_info() ].
conv_delay_driven({DelayMS, Info}, NowTimeMS) ->
    {NowTimeMS + DelayMS, Info};
conv_delay_driven({DelayMS, Info, _}, NowTimeMS) ->
    {NowTimeMS + DelayMS, Info};
conv_delay_driven(DelayGeneralDriven, NowTimeMS) ->
    conv_delay_driven_2([], DelayGeneralDriven, NowTimeMS).

%% 作用同conv_delay_driven(DelayDriven, NowTimeMS)，
%% 每次调用都重新产生一个NowTimeMS去计算
-spec conv_delay_driven(DelayDriven) -> NewDriven when
    DelayDriven :: delay_info()  | [ delay_info() ],
    NewDriven   :: driven_info() | [ driven_info() ].
conv_delay_driven(DelayDriven) ->
    NowTimeMS = utime:unixtime(ms),
    conv_delay_driven(DelayDriven, NowTimeMS).

%% 内部接口，延迟数据转换为驱动事件或进行更新计算
conv_delay_driven_2(OldGeneralDriven, DelayGeneralDriven, NowTimeMS) ->
    %% 生成数据实际是倒序的，通常外部模块不关心顺序，在管理模块中会重新排序
    lists:foldl(
        fun ({DelayMS, Info}, Acc) ->
                %% 与Type = update逻辑相同，取消前一条，添加本条数据
                %% （因为大多数情况需要取消前一个，所以缺省使用此逻辑）
                [{NowTimeMS + DelayMS, Info} | lists:keydelete(Info, 2, Acc)];
            ({DelayMS, Info, add}, Acc) ->
                %% 直接新增一条数据，不取消前面的同类事件
                [{NowTimeMS + DelayMS, Info} | Acc];
            ({DelayMS, Info, check}, Acc) ->
                %% 检查是否已经存在此类事件
                case lists:keymember(Info, 2, Acc) of
                    true  -> Acc;
                    false -> [{NowTimeMS + DelayMS, Info} | Acc]
                end;
            ({DelayMS, Info, update}, Acc) ->
                %% 后面的数据条目，会替换掉前一个数据条目
                [{NowTimeMS + DelayMS, Info} | lists:keydelete(Info, 2, Acc)];
            (_, Acc) ->
                Acc
        end,
        OldGeneralDriven, DelayGeneralDriven).

%% 检查是否包含指定driven_info()
check_member_driven_info(Info, GeneralDriven = [_|_]) ->
    lists:keymember(Info, 2, GeneralDriven);
check_member_driven_info(Info, _StateDriven = {_TimeMS, InfoX}) ->
    Info =:= InfoX;
check_member_driven_info(_, _) ->
    false.

%% 移除（单条）指定driven_info()
remove_driven_info(Info, GeneralDriven = [_|_]) ->
    lists:keydelete(Info, 2, GeneralDriven);
remove_driven_info(Info, _StateDriven = {_TimeMS, InfoX}) ->
    case Info =:= InfoX of
        true  -> {0, undefined};
        false -> _StateDriven
    end;
remove_driven_info(_, Any) ->
    Any.

%% （使用指定函数判断）移除（全部）指定driven_info()
%% 函数Filter(Info, Elem)返回true则保留，false则移除
filter_driven_info(GeneralDriven = [_|_], Filter) when is_function(Filter, 1) ->
    [
        DrivenInfo ||
        DrivenInfo = {_, InfoX} <- GeneralDriven,
        Filter(InfoX)
    ];
filter_driven_info(_StateDriven = {_TimeMS, InfoX}, Filter) when is_function(Filter, 1) ->
    case Filter(InfoX) of
        false -> {0, undefined};
        true  -> _StateDriven
    end;
filter_driven_info(Any, _Fun) ->
    Any.

%% 空状态驱动数据
empty_state_driven() ->
    ?EMPTY_STATE_DRIVEN.

%% 移除#ob_act{}中的状态驱动state_driven
remove_state_driven(ObjData = #ob_act{}) ->
    ObjData#ob_act{state_driven = {0, undefined}}.

%% --------------------------------------------------------
%% 一些特殊的filter接口
%% --------------------------------------------------------

%% 取消special_event（对应于旧版eref）
filter_special_event(GeneralDriven = [_|_]) ->
    Filter = fun(InfoX) ->
            case InfoX of
                {'special_event', _} -> false;
                _ -> true
            end
    end,
    filter_driven_info(GeneralDriven, Filter);
filter_special_event(ObjData = #ob_act{general_driven = GeneralDriven}) ->
    NewGeneralDriven = filter_special_event(GeneralDriven),
    ObjData#ob_act{general_driven = NewGeneralDriven};
filter_special_event(_Any) ->
    _Any.

%% resume状态需要取消的驱动消息
%% 取消对应于旧版eref的 {'special_event', Event}
%% 取消对应于旧版special_ai_ref的 {ai_kill_scene_role, RoleNum}
filter_when_resume(GeneralDriven = [_|_]) ->
    Filter = fun(InfoX) ->
            case InfoX of
                {'special_event', _}    -> false;
                {ai_kill_scene_role, _} -> false;
                _ -> true
            end
    end,
    filter_driven_info(GeneralDriven, Filter);
filter_when_resume(ObjData = #ob_act{general_driven = GeneralDriven}) ->
    NewGeneralDriven = filter_when_resume(GeneralDriven),
    ObjData#ob_act{general_driven = NewGeneralDriven};
filter_when_resume(_Any) ->
    _Any.
