%%%------------------------------------
%%% @Module  : mod_word
%%% @Author  : xyao
%%% @Email   : jiexiaowen@gmail.com
%%% @Created : 2012.02.08
%%% @Description: 关键字检查
%%%------------------------------------
-module(mod_word).

%%
%% Include files
%%

%%
%% Exported Functions
%%
%% php后台接口
-export([
        php_update_all/0,
        php_update_one/1
    ]).
%% 外部功能接口
-export([
        word_is_sensitive_words/1,
        word_is_sensitive_name/1,
        word_is_sensitive_banned/1,

        replace_sensitive_words/2,
        replace_sensitive_name/1,
    
        make_sensitive_show_string/1,
        replace_sensitive_kernel/5
    ]).
%% 用于取得匹配敏感词（GM查询）
-export([
        get_matches_name/1
        ,get_matches_talk/1
    ]).

-export([
        init/0,
        import_words_by_id/1,
        update_all/0
    ]).


-define(ETS_SENSITIVE_TEXT, game_sensitive_words_talk).
-define(ETS_SENSITIVE_NAME, game_sensitive_words_name).
-define(ETS_SENSITIVE_BANNED, game_sensitive_banned).       % 自动禁言、封号相关字库

%%
%% API Functions
%%

%% php通知更新
php_update_all() ->
    mod_word:update_all(),
    ok.

php_update_one(Id) ->
    mod_word:import_words_by_id(Id),
    ok.

%% 更新所有词库
update_all() ->
    Words = get_word_for_db(),
    talk_init(Words),
    name_init(),
    banned_init(Words),
    ok.

init() ->
    ets:new(?ETS_SENSITIVE_TEXT, [named_table, public, set]),
    ets:new(?ETS_SENSITIVE_NAME, [named_table, public, set]),
    ets:new(?ETS_SENSITIVE_BANNED, [named_table, public, set]),
    Words = get_word_for_db(),
    talk_init(Words),
    name_init(),
    banned_init(Words),
    ok.

banned_init(Words) ->
    ets:delete_all_objects(?ETS_SENSITIVE_BANNED),
    import_words_banned(?ETS_SENSITIVE_BANNED, Words),
    ok.

import_words_by_id(Id) ->
    Data = db:get_one(io_lib:format(<<"select `word` from `config_word` where id = ~p limit 1">> , [Id])),
    X = io_lib:format("~ts", [Data]),
    add_word_to_ets(X, ?ETS_SENSITIVE_TEXT),
    add_word_to_ets(X, ?ETS_SENSITIVE_NAME),
    add_word_to_ets(X, ?ETS_SENSITIVE_BANNED),
    ok.

%% 检测敏感词

word_is_sensitive_words([]) ->
    false;
word_is_sensitive_words(<<>>) ->
    false;
word_is_sensitive_words(Utf8String) when is_list(Utf8String) orelse is_binary(Utf8String) ->
    UniString =  unicode:characters_to_list(Utf8String, utf8),
    word_is_sensitive_kernel(UniString, ?ETS_SENSITIVE_TEXT).

word_is_sensitive_name([])->
    false;
word_is_sensitive_name(<<>>)->
    false;
word_is_sensitive_name(Utf8String) when is_list(Utf8String) orelse is_binary(Utf8String) ->
    UniString = unicode:characters_to_list(Utf8String, utf8),
    word_is_sensitive_kernel(UniString, ?ETS_SENSITIVE_NAME).

%% 检查是否匹配违规禁言词库
word_is_sensitive_banned([]) ->
    false;
word_is_sensitive_banned(<<>>) ->
    false;
word_is_sensitive_banned(Utf8String) when is_list(Utf8String) orelse is_binary(Utf8String) ->
    UniString = unicode:characters_to_list(Utf8String),
    word_is_sensitive_kernel(UniString, ?ETS_SENSITIVE_BANNED).

%% 替换敏感词为乱码

replace_sensitive_words(Utf8String, Lv) when is_binary(Utf8String)->
    UniString = case unicode:characters_to_list(Utf8String,unicode) of
        {error, _, _RestData} -> [];
        {incomplete, _, _} -> [];
        Data -> Data
    end,
    ReplacedString = replace_sensitive_kernel(UniString, Lv, 0, [], ?ETS_SENSITIVE_TEXT),
    unicode:characters_to_binary(ReplacedString, utf8);
replace_sensitive_words(InputString,Lv)when is_list(InputString)->
    Utf8Binary = unicode:characters_to_binary(InputString, utf8),
    replace_sensitive_words(Utf8Binary,Lv);
replace_sensitive_words(InputString,_Lv)->
    InputString.

replace_sensitive_name(Utf8String) when is_binary(Utf8String)->
    UniString = unicode:characters_to_list(Utf8String,unicode),
    ReplacedString = replace_sensitive_kernel(UniString, 0, 1, [], ?ETS_SENSITIVE_NAME),
    unicode:characters_to_binary(ReplacedString, utf8);
replace_sensitive_name(InputString)when is_list(InputString)->
    Utf8Binary = unicode:characters_to_binary(InputString, utf8),
    replace_sensitive_name(Utf8Binary);
replace_sensitive_name(InputString)->
    InputString.

%% 查出名字中的敏感词（GM查询）
get_matches_name(Utf8String) ->
    UniString = unicode:characters_to_list(Utf8String, unicode),
    get_matches(UniString, ?ETS_SENSITIVE_NAME, []).

%% 查出普通文本性内容的敏感词（GM查询）
get_matches_talk(Utf8String) ->
    UniString = unicode:characters_to_list(Utf8String, unicode),
    get_matches(UniString, ?ETS_SENSITIVE_TEXT, []).

%%
%% Local Functions
%%

name_init() ->
    ets:delete_all_objects(?ETS_SENSITIVE_NAME),
    import_words_name(?ETS_SENSITIVE_NAME),
    ok.

talk_init(Words)->
    %% data_filter:talk() 聊天过滤的配置
    ets:delete_all_objects(?ETS_SENSITIVE_TEXT),
    import_words_talk(?ETS_SENSITIVE_TEXT),
    %% 后台动态添加的关键字
    lists:foreach(fun(Word) -> 
        add_word_to_ets(Word, ?ETS_SENSITIVE_TEXT)
    end, Words),
    ok.

%% 加载名字相关过滤
import_words_name(EtsName)->
    lists:foreach(fun(X)->
                add_word_to_ets(X, EtsName)
        end, get_name_sensitive_words()),
    ok.

%% 加载聊天相关过滤
%% @param EtsName  Ets名
import_words_talk(EtsName)->
    lists:foreach(fun(X)->
                add_word_to_ets(X, EtsName)
        end, data_filter:talk()),
    ok.

import_words_banned(EtsName, Words) ->
    lists:foreach(fun(X) ->
                add_word_to_ets(X, EtsName)
        end, Words),
    ok.

get_word_for_db() ->
    Data = db:get_all(<<"select `word` from `config_word`">>),
    Convert = fun([X]) ->
            io_lib:format("~ts", [X])
    end,
    lists:map(Convert, Data).

add_word_to_ets("", _) -> ignore;
add_word_to_ets(<<>>, _) -> ignore;
add_word_to_ets(Word, EtsName)->
    UniString = unicode:characters_to_list(Word, unicode),
    case UniString of
        [HeadChar | _Left] ->
            case ets:lookup(EtsName, HeadChar) of
                []-> ets:insert(EtsName, {HeadChar,[UniString]});
                [{_H, OldList}]->
                    case lists:member(UniString, OldList) of
                        false -> ets:insert(EtsName, {HeadChar,[UniString|OldList]});
                        true  -> ignore
                    end
            end;
        _ -> ignore
    end.

word_is_sensitive_kernel([], _EtsName)->
    false;
word_is_sensitive_kernel(UniString, EtsName)->
    [HeadChar|TailString] = UniString,
    UniStrLen = length(UniString),
    WordList = get_key_char_wordlist(HeadChar,EtsName),
    Match = fun(Word)->
            WordLen = length(Word),
            if
                %% 小于敏感词长度直接false
                WordLen > UniStrLen ->
                    false;
                %% 等于直接比较
                WordLen =:=	UniStrLen ->
                    UniString =:= Word;
                %% 大于取词比较
                true ->
                    HeadStr = lists:sublist(UniString,WordLen),
                    HeadStr =:= Word
            end
    end,
    case lists:any(Match, WordList) of
        true-> true;
        false-> word_is_sensitive_kernel(TailString,EtsName)
    end.

match_of_replace_sensitive_kernel(Word,Last,InputString,InputStrLen)->
    case Last of
        0 ->
            WordLen = length(Word),
            if
                WordLen > InputStrLen -> 0;
                WordLen =:= InputStrLen ->
                    if
                        (InputString =:= Word)->
                            WordLen;
                        true->
                            0
                    end;
                true ->
                    HeadStr = lists:sublist(InputString, length(Word)),
                    if(HeadStr =:= Word)->
                            WordLen;
                        true->
                            0
                    end
            end;
        _ -> Last
    end.

replace_sensitive_kernel([],_Lv,_TalkOrName,LastRepaced, _EtsName)->
    LastRepaced;
%%@param TalkOrName 0表示聊天 1表示名字
replace_sensitive_kernel(InputString,Lv,TalkOrName,LastReplaced,EtsName)->
    [HeadChar|TailString] = InputString,
    WordList = get_key_char_wordlist(HeadChar,EtsName),
    InputStrLen = length(InputString),
    Match = fun(Word,Last)->
            match_of_replace_sensitive_kernel(Word,Last,InputString,InputStrLen)
    end,
    case lists:foldl(Match, 0, WordList) of
        0 ->
            NewReplaced = LastReplaced ++ [HeadChar],
            replace_sensitive_kernel(TailString,Lv,TalkOrName,NewReplaced,EtsName);
        SensWordLen->
            LeftString = lists:sublist(InputString, SensWordLen + 1, InputStrLen - SensWordLen ),
            NewReplaced = LastReplaced ++ make_sensitive_show_string(SensWordLen),
            replace_sensitive_kernel(LeftString,Lv,TalkOrName,NewReplaced,EtsName)
    end.

get_key_char_wordlist(KeyChar,EtsName)->
    case ets:lookup(EtsName,KeyChar) of
        []-> [];
        [{_H,WordList}]-> WordList
    end.

make_sensitive_show_string(1)->
    "*";
make_sensitive_show_string(2)->
    "*&";
make_sensitive_show_string(3)->
    "*&^";
make_sensitive_show_string(4)->
    "*&^%";
make_sensitive_show_string(5)->
    "*&^%$";
make_sensitive_show_string(6)->
    "*&^%$#";
make_sensitive_show_string(7)->
    "*&^%$#@";
make_sensitive_show_string(8)->
    "*&^%$#@!";
make_sensitive_show_string(N)->
    M = N rem 8,
    C = N div 8,
    L1 = lists:append(lists:duplicate(C, "*&^%$#@!")),
    case M > 0 of
        true  ->
            L2 = make_sensitive_show_string(M),
            lists:append([L1, L2]);
        false ->
            L1
    end.

%test() ->
%    [DescList] = io_lib:format("~ts", ["蜀门"]),
%    io:format("~p~n",[word_is_sensitive(DescList)]),
%    [DescList1] = io_lib:format("~ts", ["玉之魂"]),
%    io:format("~p ~p~n", ["玉之魂", replace_sensitive(DescList1)]),
%    [DescList2] = io_lib:format("~ts", ["梦-话-西-游"]),
%    io:format("~p ~p~n", ["梦-话-西-游", replace_sensitive(DescList2)]),
%    [DescList3] = io_lib:format("~ts", ["游ke"]),
%    io:format("~p~n",[word_is_sensitive(DescList3)]),
%    [DescList4] = io_lib:format("~ts", ["纯-白"]),
%    io:format("~p~n",[word_is_sensitive(DescList4)]).

%% 查出所有匹配的敏感字
get_matches([HeadChar | TailString] = UniString, EtsName, Acc) ->
    WordList  = get_key_char_wordlist(HeadChar, EtsName),
    UniStrLen = length(UniString),
    Match = fun(Word)->
            WordLen = length(Word),
            if
                %% 小于敏感词长度直接false
                WordLen > UniStrLen ->
                    false;
                %% 等于直接比较
                WordLen =:=	UniStrLen ->
                    UniString =:= Word;
                %% 大于取词比较
                true ->
                    HeadStr = lists:sublist(UniString,WordLen),
                    HeadStr =:= Word
            end
    end,
    MatchList = lists:filter(Match, WordList),
    NewAcc =
    case MatchList of
        [] -> Acc;
        _  -> MatchList ++ (Acc -- MatchList)
    end,
    get_matches(TailString, EtsName, NewAcc);
get_matches([], _, Acc) ->
    Acc.

get_name_sensitive_words() ->
    [
        "❶", %% 以下10个为编辑器不显示的特殊数字1-10
        "❷",
        "❸",
        "❹",
        "❺",
        "❻",
        "❼",
        "❽",
        "❾",
        "❿",
        "*",
        "^",
        "$",
        "\r",
        "\n",
        "手游",
        "shouyou",
        "sh0uy0u",
        "shouy0u",
        "sh0uyou",
        "weixin",
        %% ----------------------------------
        %% 数据库非法字符
        %% ----------------------------------
        " ",
        "  ",
        "'",
        ",",
        ";",
        "\"",
        "<",
        ">",
        "\\",
        "|",
        "#",
        "@",
        "_",
        "/",
        "\\\\"
    ] ++ data_filter:talk().
