%%% -------------------------------------------------------
%%% @author huangyongxing@yeah.net
%%% @doc
%%% 非法字符、聊天敏感字检测及过滤
%%% @end
%%% @since 2017-03-07
%%% -------------------------------------------------------
-module(lib_word).
-export([
        validate_role_name/1                % 角色名字验证
        ,validate_name/2                    % 名字验证
        ,filter_text_gm/1                   % GM过滤
        ,filter_text/2                      % 普通文本过滤（敏感字替换为特殊屏蔽符号）
        ,check_keyword/1                    % 名字敏感字检查（主要用于名字检查）
        ,check_text/1                       % 文本敏感字检查（非角色名、帮会名等）
    ]).
%% normally for gm query
-export([
        get_name_sensitive_words/1          % 查询名字中的敏感字列表
        ,get_text_sensitive_words/1         % 查询普通文本中的敏感字列表
    ]).
%% only for test
-export([
    ]).

-include("game_vsn.hrl").

%% 角色名合法性检测
-spec validate_role_name(Name) -> true | {false, Reason} when
    Name :: iolist() | unicode:charlist(),
    Reason :: any().
validate_role_name(Name) ->
    CheckConds = [width, keyword, role_exist],
    validate_name(CheckConds, Name).

%% 关于validate_name/1,2，返回结果为 true | {false, Reason}，
%% true表明通过验证检查，{false, Reason}表明未通过，
%% 实际上，{false, Reason}中的Reason值包含：
%% invalid_utf8 : 无效utf8字符串
%% invalid_cond : 无效的检测条件
%% Other        : 检测的条件值作为Reason返回，表明该条件未通过检查

-spec validate_name(Conds, Name) -> true | {false, Reason} when
    Conds :: [Cond],
    Cond :: width | {width, MinWidth, MaxWidth} | keyword | role_exist | Other,
    MinWidth :: integer(), MaxWidth :: integer(),
    Name :: iolist() | unicode:charlist(),
    Reason :: any(),
    Other :: any().
validate_name(Conds, Name) ->
    WordChecked = false,
    validate_name(Conds, Name, WordChecked).

%% 名字长度检查
validate_name([width | Conds], Name, WordChecked) ->
    {MinWidth, MaxWidth} = data_vsn:get_name_width(),
    validate_name([{width, MinWidth, MaxWidth} | Conds], Name, WordChecked);

validate_name([{width, MinWidth, MaxWidth} | Conds], Name, WordChecked) ->
    case unicode:characters_to_list(Name) of
        {incomplete, _Encoded, _Rest} ->    % 非合法UTF8字符串数据
            {false, invalid_utf8};
        {error, _Encoded, _Rest} ->         % 非合法UTF8字符串数据
            {false, invalid_utf8};
        UnicodeList ->
            Width = util:string_width(UnicodeList),
            case Width =< MaxWidth andalso Width >= MinWidth of
                true  ->
                    validate_name(Conds, Name, WordChecked);
                false ->    % 名称长度不符合限制
                    {false, width}
            end
    end;
%% 判断角色的名字是否已经存在（角色名专用）
validate_name([role_exist | Conds], Name, WordChecked) ->
    case WordChecked of
        false ->    %% 检查角色名之前一定要先检查非法字符（因为需要查数据库）
            validate_name([keyword, role_exist | Conds], Name, WordChecked);
        true  ->
            case lib_player:is_exists(Name) of
                true ->     % 角色名称已经被使用
                    {false, role_exist};
                false ->
                    case lists:member(unicode:characters_to_list(Name), data_chat_simulation:get_chat_robot_name()) of
                        true  -> {false, role_exist};
                        false -> validate_name(Conds, Name, WordChecked)
                    end
            end
    end;
%% 判断名字是有敏感词
validate_name([keyword | Conds], Name, WordChecked) ->
    case WordChecked of
        false ->
            case check_keyword(Name) of
                true  -> {false, keyword};
                false ->
                    NewWordChecked = true,
                    validate_name(Conds, Name, NewWordChecked)
            end;
        true  ->
            validate_name(Conds, Name, WordChecked)
    end;
%% 无效条件
validate_name([_ | _Conds], _Name, _WordChecked) ->
    {false, invalid_cond};
%% 通过检查
validate_name([], _Name, _WordChecked) ->
    true.

%% 实际上对于GM，不过滤，只保持与filter_text一样返回binary()
filter_text_gm(Text) when is_bitstring(Text) ->
    Text;
filter_text_gm(Text) when is_list(Text) ->
    unicode:characters_to_binary(Text).

%% 敏感词过滤
%% @param Text list() | bitstring()
%% @return bitstring()      过滤后的文本（二进制格式）
filter_text(Text, Lv) when is_bitstring(Text) orelse is_list(Text) ->
    case need_check_words() of
        true  -> mod_word:replace_sensitive_words(Text, Lv);
        false -> util:make_sure_binary(Text)
    end.

%% 名字敏感词检测
%% @return true  存在关键词
%%         false 不存在关键词
%% @var Text：字符串 | 字符串的二进制格式
check_keyword(Text) ->
    if
        is_list(Text) orelse is_binary(Text) ->
            UTF8Binary = unicode:characters_to_binary(Text),
            case re:run(UTF8Binary, regex_name(), [{capture, none}, caseless, unicode]) of
                match ->
                    true;
                nomatch ->
                    need_check_words() andalso
                    mod_word:word_is_sensitive_name(UTF8Binary)
            end;
        true ->
            false
    end.

%% 文本敏感词检测
%% @return true  存在关键词
%%         false 不存在关键词
%% @var Text：字符串 | 字符串的二进制格式
check_text(Text) ->
    if
        is_list(Text) orelse is_binary(Text) ->
            UTF8Binary = unicode:characters_to_binary(Text),
            case re:run(UTF8Binary, regex_text(), [{capture, none}, caseless, unicode]) of
                match ->
                    true;
                nomatch ->
                    need_check_words() andalso
                    mod_word:word_is_sensitive_words(UTF8Binary)
            end;
        true ->
            true
    end.

%% 获取名字中的敏感词（GM查询）
get_name_sensitive_words(Name) ->
    UTF8Binary = unicode:characters_to_binary(Name),
    RegexMatchList =
    case re:run(UTF8Binary, regex_name(), [global, {capture, all, list}, caseless, unicode]) of
        {match, MatchList} ->
            lists:append(MatchList);
        nomatch ->
            []
    end,
    case need_check_words() of
        true  ->
            WordMatchList = mod_word:get_matches_name(Name),
            RegexMatchList ++ (WordMatchList -- RegexMatchList);
        false ->
            RegexMatchList
    end.

%% 获取一般文本性内容中的敏感词（GM查询）
get_text_sensitive_words(Text) ->
    UTF8Binary = unicode:characters_to_binary(Text),
    RegexMatchList =
    case re:run(UTF8Binary, regex_text(), [global, {capture, all, list}, caseless, unicode]) of
        {match, MatchList} ->
            lists:append(MatchList);
        nomatch ->
            []
    end,
    case need_check_words() of
        true  ->
            WordMatchList = mod_word:get_matches_talk(Text),
            RegexMatchList ++ (WordMatchList -- RegexMatchList);
        false ->
            RegexMatchList
    end.

%%%===================================================================
%%% Internal functions
%%%===================================================================

%% 判断是否需要检查敏感词库
%% true需要检查 false不需要
need_check_words() ->
    case config:get_apple() of
        false ->
            case data_vsn:get_game_vsn() of
                ?VER_MAIN -> true;
                _         -> false
            end;
        _AppleVer ->
            false
    end.

%% 名字非法格式正则匹配式
regex_name() ->
    "[\、\;\\^\,\.\"\'\:\+\=\!\?\<\>\/\*\|\~\`\@\#\$\%\(\)\{\}\\\-\\\[\\\]\\\\]".

%% 普通文本非法格式正则匹配式
regex_text() ->
    "[\;\\^\"\'\~\`\#\$\%\\\-\\\[\\\]\\\\]".

