%%% -------------------------------------------------------
%%% @author huangyongxing@yeah.net
%%% @doc
%%% 关于技能调整参数
%%%
%%% 技能调整参数是一个列表： [{MatchKey, Adjust}, ...]
%%% 关于MatchKey：
%%%   该值用于检查 #skill.lv_data 中的 effect 列表中的首位 BuffKey 值是否匹配
%%%   对于匹配的effect，将对其中的 Int 和 Float 值按 Adjust 规则进行相应调整，
%%%   特殊情况下，如果MatchKey为all，则整个effect列表中的所有效果都将进行调整
%%% 关于Adjust：
%%%   该值一般分为两种类型，Adjust :: number() | {ExprValType, Expr}
%%%
%%%   1. Adjust为 number()，直接与 Int 和 Float 相乘，计算出新 Int 和 Float
%%%   2. Adjust为 {ExprValType, Expr}，
%%%     ExprValType 用于指定Expr表达式中 变量V 的取值来源类型，
%%%     Expr 是个可能包含变量V的一般数学表达式字符串，计算结果将
%%%          与 Int 和 Float 相乘，得到新的 Int 和 Float
%%%
%%%     ExprValType的具体取值见后方 adjust 接口说明
%%%     Expr只支持几个运算符 + - * / div rem ( )，
%%%         div表示求 除法整商数，
%%%         rem表示取模（除法余数），
%%%         括号用于改变运算优先级
%%% @end
%%% -------------------------------------------------------
-module(skill_adjust_api).
-include("common.hrl").
-include("skill.hrl").
-export([
        valid_skill_adjust/1
        ,adjust/3
    ]).
-export([
        erase_expr/0
        ,erase_expr/2
        ,erase_expr_on_scene/0
    ]).

%% 检验技能调整参数有效性
%% SkillAdjust 正确格式应为一个列表
valid_skill_adjust(SkillAdjust) ->
    sub_valid_skill_adjust(SkillAdjust, []).

sub_valid_skill_adjust([AdjustInfo = {_MatchKey, Adjust} | SkillAdjust], AccList) when is_number(Adjust) ->
    sub_valid_skill_adjust(SkillAdjust, [AdjustInfo | AccList]);
sub_valid_skill_adjust([AdjustInfo = {_MatchKey, {_ExprType, _Expr}} | SkillAdjust], AccList) ->
    sub_valid_skill_adjust(SkillAdjust, [AdjustInfo | AccList]);
sub_valid_skill_adjust([_ | SkillAdjust], AccList) ->
    sub_valid_skill_adjust(SkillAdjust, AccList);
sub_valid_skill_adjust(_, AccList) -> AccList.

%% -------------------------------------------------------
%% 关于ExprValType对Expr表达式计算的影响说明
%% -------------------------------------------------------
%% ExprValType  Expr中V的取值
%% role_num     当前场景房间角色数
%%
%% 例如：
%% {role_num, "V*5/6"}，表示：
%% AdjustFactor = "当前场景房间角色数 * 5 / 6"
%% IntNew       = util:ceil( AdjustFactor * Int )
%% FloatNew     = AdjustFactor * Float

%% 遍历 “技能调整参数”列表，对“技能配置”进行调整，返回新的技能配置
-spec adjust(AdjustParams, OldSkillConfig, SkillAdjust) -> SkillConfig when
    SkillAdjust :: [ AdjustInfo ], AdjustParams :: #adjust_params{},
    OldSkillConfig :: #skill{}, SkillConfig :: #skill{},
    AdjustInfo :: {MatchKey :: all | any(), Adjust},
    Adjust :: number() | {ExprValType, Expr},
    ExprValType :: role_num | area_role_num | any(),
    Expr :: string().
adjust(_AdjustParams, SkillConfig, []) ->
    SkillConfig;
adjust(AdjustParams, OldSkillConfig, [{MatchKey, Adjust} | SkillAdjust]) ->
    case OldSkillConfig of
        #skill{lv_data = LvData = #skill_lv{effect = EffOfLvData}} ->
            NewEffOfLvData = sub_adjust(EffOfLvData, MatchKey, Adjust, AdjustParams, []),
            SkillConfig = OldSkillConfig#skill{
                lv_data = LvData#skill_lv{effect = NewEffOfLvData}
            },
            adjust(AdjustParams, SkillConfig, SkillAdjust);
        _ ->
            OldSkillConfig
    end;
adjust(AdjustParams, OldSkillConfig, [_ | SkillAdjust]) ->
    adjust(AdjustParams, OldSkillConfig, SkillAdjust);
adjust(_AdjustParams, OldSkillConfig, _SkillAdjust) ->
    OldSkillConfig.

sub_adjust([EffDataOne | EffOfLvData], MatchKey, Adjust, AdjustParams, EffListAcc) ->
    case check_match(MatchKey, EffDataOne) of
        true  ->
            NewEffDataOne = do_adjust_effect_one(EffDataOne, Adjust, AdjustParams),
            sub_adjust(EffOfLvData, MatchKey, Adjust, AdjustParams, [NewEffDataOne | EffListAcc]);
        false ->
            sub_adjust(EffOfLvData, MatchKey, Adjust, AdjustParams, [EffDataOne | EffListAcc])
    end;
sub_adjust(_, _MatchKey, _Adjust, _AdjustParams, EffListAcc) ->
    EffListAcc.

%% 判断是否符合调整条件
check_match(_, {}) ->
    false;
check_match(MatchKey, EffDataOne) when is_tuple(EffDataOne) ->
    MatchKey =:= all orelse MatchKey =:= element(1, EffDataOne);
check_match(_, _) ->
    false.

%% 执行Effect数据的调整
%% 主动技效果格式：
%%   (参照 lib_skill_buff 模块 calc_active_other_effect 函数)
%%   {Key, BuffType, PerMil, AffectedParties, Int, Float, AttrId, LastTime, _Num, EffectId}
%% 被动技效果格式：
%%   (参照 lib_skill_buff 模块 calc_passive_attr_effect 函数)
%%   {Key, BuffType, PerMil, AffectedParties, Int, Float, LastTime, Stack, EffectId}
do_adjust_effect_one({
        BuffKey, BuffType, PerMil, AffectedParties, Int, Float, AttrId, LastTime, Num, EffectId
    }, Adjust, AdjustParams) ->
    NewInt   = value_adjust(Int, AdjustParams, Adjust),
    NewFloat = value_adjust(Float, AdjustParams, Adjust),
    {BuffKey, BuffType, PerMil, AffectedParties, NewInt, NewFloat, AttrId, LastTime, Num, EffectId};
do_adjust_effect_one({
        BuffKey, BuffType, PerMil, AffectedParties, Int, Float, LastTime, Stack, EffectId
    }, Adjust, AdjustParams) ->
    NewInt   = value_adjust(Int, AdjustParams, Adjust),
    NewFloat = value_adjust(Float, AdjustParams, Adjust),
    {BuffKey, BuffType, PerMil, AffectedParties, NewInt, NewFloat, LastTime, Stack, EffectId};
do_adjust_effect_one(EffDataOne, _Adjust, _AdjustParams) ->
    EffDataOne.

%% Int或Float的值调整
value_adjust(Value, _AdjustParams, 1) ->
    Value;
value_adjust(Value, _AdjustParams, Adjust) when is_number(Adjust) ->
    % ?INFO_TTY("value_adjust"),
    if
        is_integer(Value) ->
            util:floor(Value * Adjust);
        is_float(Value) ->
            Value * Adjust;
        true ->
            Value
    end;
value_adjust(Value, AdjustParams, {ExprValType, Expr}) when is_list(Expr) ->
    case get_expr_v_val(ExprValType, AdjustParams) of
        V when is_integer(V) ->
            Func = load_expr_fun(Expr),
            % ?INFO_TTY("load_expr_fun"),
            case is_function(Func, 1) of
                true  ->
                    Ret = Func(V),
                    if
                        is_number(Ret) ->
                            % ?INFO_TTY("expr_fun is_number"),
                            value_adjust(Value, AdjustParams, Ret);
                        true ->
                            Value
                    end;
                _ ->
                    Value
            end;
        _ ->
            Value
    end;
value_adjust(Value, _AdjustParams, _) ->
    Value.

%% -------------------------------------------------------
%% 根据类型确定表达比Expr中变量V的值

%% 场景角色数量
get_expr_v_val(role_num, #adjust_params{copy_id = CopyId}) ->
    lib_scene_agent:get_role_count(CopyId);
%% 场景9格区域角色数量
get_expr_v_val(area_role_num, #adjust_params{copy_id = CopyId, aer_x = X, aer_y = Y}) ->
    AreaId = lib_scene_calc:get_xy(X, Y),
    lib_scene_agent:get_area_role_count(AreaId, CopyId);
%% 无效配置或参数
get_expr_v_val(_, _) ->
    undefined.
%% -------------------------------------------------------

load_expr_fun(Expr) ->
    case get_expr_fun(Expr) of
        undefined ->    % 这里只解析一遍，即缓存到进程字典
            parse_expr_to_fun(Expr);
        error ->
            error;
        Func ->
            % ?INFO_TTY("load_expr_fun exist"),
            Func
    end.

%% 解析表达式为函数
parse_expr_to_fun(Expr) ->
    %% 这里不对Expr做其他安全检查了，策划大爷应该不可能写个其他注入的代码进来
    Func = util:eval("fun(V) -> " ++ Expr ++ " end."),
    case Func of
        {error, _ExType, _ExPattern, _ErrInfo} ->
            ?ERR("parse_expr_to_fun error: ~p~n", [Func]),
            put_expr_fun(Expr, error),
            error;
        _ ->
            % ?INFO_TTY("parse_expr_to_fun"),
            case is_function(Func, 1) of
                true  ->
                    % ?INFO_TTY("parse_expr_to_fun is ok"),
                    put_expr_fun(Expr, Func),
                    Func;
                false ->
                    ?ERR("parse_expr_to_fun error, params count error"),
                    put_expr_fun(Expr, error),
                    error
            end
    end.

put_expr_fun(Expr, Func) ->
    put({expr, Expr}, Func).

get_expr_fun(Expr) ->
    case get({expr, Expr}) of
        undefined -> undefined;
        error -> error;
        Func  -> Func
    end.

%% 清除表达式缓存
%%
%% 在热更AI配置之后，如果期望刷新配置，需要调用erase_expr/0
%% 由于通常不需要更新此配置，所以暂不做在热更新回调中
erase_expr() ->
    SceneIdList = data_scene:get_id_list(),
    erase_expr(SceneIdList).

erase_expr([SceneId | SceneIdList]) ->
    PoolIds = scene_mgr:get_scene_pool_ids(SceneId),
    lists:foreach(fun(PoolId) ->
                erase_expr(SceneId, PoolId)
        end, PoolIds),
    erase_expr(SceneIdList);
erase_expr([]) ->
    ok.

erase_expr(SceneId, PoolId) ->
    mod_scene_agent:apply_cast(SceneId, PoolId, ?MODULE, erase_expr_on_scene, []).

erase_expr_on_scene() ->
    Keys = get_keys(),
    check_erase_expr(Keys).

check_erase_expr([{expr, _} = Key | Keys]) ->
    erase(Key),
    check_erase_expr(Keys);
check_erase_expr([_ | Keys]) ->
    check_erase_expr(Keys);
check_erase_expr(_) ->
    ok.

