%%%-----------------------------------
%%% @Author     : gaomeng 595088880@qq.com
%%% @Created    : 2017/1/23
%%% @Doc        : 辅助函数库
%%%-----------------------------------

-module(util).
-include("common.hrl").

-export([
   unixtime/0,
   longunixtime/0,
   local_time/0,

   cn/1,
   datetime_to_seconds/1,
   ifelse/3,
   string_width/1,
   safe_create_ets/2,
   rand_list/1,
   term_to_string/1,
   term_to_bitstring/1,
   bitstring_to_term/1,
   bitstring_to_obj/1,
   string_to_term/1,
   get_max_svrid/0,
   to_list/1,
   check_name_length/1,
   rm_str_space/1,
   rm_str_lr_space/1,
   same_str/2,
   unixtime_to_now/1,
   unixtime_to_localtime/1,
   get_day_of_week/0,
   get_day_of_week/1,
   get_secs_from_midnight/0,
   get_secs_from_midnight/1,
   time_to_seconds/1,
   other_day/2,
   make_cdkey/2,
   parse_idstr/1,
   robot_name/0,
   localtime2date/0,
   localtime2date/1,
   check_sen_keyword/2,
   check_round_level/4,
   next_sec/1
]).

-export([
   quick_sort/2
   , upset_list/1
   , insert_last/2
]).

-export([
   open_day/0
   , merge_day/0
   , is_merge/0
   , week_diff_day/2
   , del_timer_ref/1
   , time_diff_sec/2
   , escape/1
   , under_week_time/2
   , calc_week_sec/1
   , calc_week_sec/2

   , boolen2num/1
   , check_roleid/1
   , sort_red/0
   , decompile/1
]).

-compile(export_all).

%% @doc 测试
test() ->
   L1 = [],
   L2 = [],
   Keys = [{2, ?true}],
   L11 = quick_sort(L1, Keys),
   ?INF("cmd1:~p~n", [L11]),
   L22 = quick_sort(L2, Keys),
   ?INF("cmd2:~p~n", [L22]),
   ok.

%% @doc 反编代码
decompile(Mod) ->
   {ok, {_, [{abstract_code, {_, AC}}]}} = beam_lib:chunks(code:which(Mod), [abstract_code]),
   ?PRINT("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).

%% @doc 打印最大reductions计数, PID
sort_red() ->
   [Pid | T] = erlang:processes(),
   {_, MaxVal} = erlang:process_info(Pid, reductions),
   Pid1 = find_max(MaxVal, T, Pid),
   ?INF("max_reductions_pid:~p~n", [erlang:process_info(Pid1)]).

find_max(MaxVal, [TemPid | T], Pid) ->
   {_, TemMaxVal} = erlang:process_info(TemPid, reductions),
   case TemMaxVal > MaxVal of
      true ->
         find_max(TemMaxVal, T, TemPid);
      _ ->
         find_max(MaxVal, T, Pid)
   end;
find_max(_, [], Pid) ->
   Pid.

%% @doc 检测是否符合玩家id ,  true|false
check_roleid(RoleId) ->
   <<SvrId:32, _:32>> = <<RoleId:64>>,
   SvrIds = config:server_ids(),
   lists:member(SvrId, SvrIds).

%% @doc 状态转数字
boolen2num(true) ->
   ?true;
boolen2num(_) ->
   ?false.

%% @doc 获取当前日期和时间，相当于 elrang:localtime() -> {{Y, M, D}, {H, Mi, S}}
local_time() ->
   UnixTime = unixtime(),
   unixtime_to_localtime(UnixTime).

%% @doc 计算距离日期时间（秒）
%% @spec calc_week_sec(WeekTime) -> TimeSec
%% where WeekTime = {Week, Time} ; Week=int() ; Time={hour, minute, second}
calc_week_sec(WeekTime) ->
   CurrWeek = get_day_of_week(),
   {_, Time} = local_time(),
   calc_week_sec({CurrWeek, Time}, WeekTime).

calc_week_sec({CurrWeek, CurrTime}, {TarWeek, TarTime}) ->
   CurrSec = calendar:time_to_seconds(CurrTime),
   TarSec = calendar:time_to_seconds(TarTime),
   DiffDay = week_diff_day(CurrWeek, TarWeek),
   case CurrWeek =:= TarWeek of
      true ->
         case CurrSec >= TarSec of
            true ->
               7 * 86400 - CurrSec + TarSec;
            _ ->
               TarSec - CurrSec
         end;
      _ ->
         DiffDay * 86400 - CurrSec + TarSec
   end.

%% @doc 检测是否小于指定时间
%% under_week_time(A, B) -> true|false
%% A 是否小于 B
under_week_time({Week1, _}, {Week2, _}) when Week1 < Week2 ->
   true;
under_week_time({Week1, Time1}, {Week1, Time2}) ->
   TimeSec1 = calendar:time_to_seconds(Time1),
   TimeSec2 = calendar:time_to_seconds(Time2),
   TimeSec1 < TimeSec2;
under_week_time(_, _) ->
   false.

%% @doc 字符串转义 ' => \\'
escape(Str) ->
   re:replace(Str, "'", "\\\\'", [global, {return, list}]).

%% @doc 插入到数组尾部
insert_last(List, Info) when is_list(List) ->
   Other = lists:reverse(List),
   Other1 = [Info | Other],
   lists:reverse(Other1);
insert_last(_, _) ->
   [].

%% @doc 打乱数组(只适合比较少的数组)
upset_list(List) when is_list(List) ->
   Len = length(List),
   case List of
      [_ | _] ->
         upset_list_loop(Len, List, []);
      _ ->
         List
   end;
upset_list(_) ->
   [].

upset_list_loop(_, [], L) ->
   L;
upset_list_loop(Len, List, L) when Len > 0 ->
   Index = util:random(1, Len),
   Tuple = lists:nth(Index, List),
   Other = lists:delete(Tuple, List),
   upset_list_loop(Len - 1, Other, [Tuple | L]);
upset_list_loop(_, _, L) ->
   L.

%% @doc 两个时间的间隔(秒)
time_diff_sec(Time1, Time2) ->
   Time1Sec = calendar:time_to_seconds(Time1),
   Time2Sec = calendar:time_to_seconds(Time2),
   max(0, Time2Sec - Time1Sec).

%% @doc 删除指定定时器
del_timer_ref(TimerRef) when is_reference(TimerRef) ->
   erlang:cancel_timer(TimerRef);
del_timer_ref(_) ->
   ok.

%% @doc 距离指定星期多少天
week_diff_day(Curr, List) when is_list(List) ->
   week_diff_day(Curr, List, 0);
week_diff_day(Curr, Tar) ->
   week_diff_day(Curr, [Tar]).

week_diff_day(Curr, [TarWeek | _T], _Min) when TarWeek >= Curr ->
   TarWeek - Curr;
week_diff_day(Curr, [TarWeek | T], Min) ->
   case TarWeek < Min of
      true ->
         week_diff_day(Curr, T, TarWeek);
      _ ->
         week_diff_day(Curr, T, Min)
   end;
week_diff_day(Curr, [], Min) ->
   case Min > 0 of
      true ->
         7 - Curr + Min;
      _ ->
         1
   end.

%% @spec next_sec(Time) -> Sec
%% where Time =:= {hour, min, sec}, Sec =:= integer()
%% @doc 判断时间间隔
next_sec(Time) ->
   OpenTimeSec = calendar:time_to_seconds(Time),
   {_, NowTime} = local_time(),
   NowTimeSec = calendar:time_to_seconds(NowTime),
   case NowTimeSec < OpenTimeSec of
      true ->
         OpenTimeSec - NowTimeSec;
      _ ->
         (?ONE_DAY_SECONDS - NowTimeSec) + OpenTimeSec
   end.

%% @doc 快速排序
quick_sort([X | L], Keys) ->
   {L1, L2} = sort_helper(L, X, Keys, [], []),
   quick_sort(L1, Keys) ++ [X] ++ quick_sort(L2, Keys);
quick_sort([], _) -> [].

%% 辅助函数
sort_helper([H | L], X, Keys, L1, L2) ->
   case match_sort(Keys, X, H) of
      true ->
         sort_helper(L, X, Keys, [H | L1], L2);
      _ ->
         sort_helper(L, X, Keys, L1, [H | L2])
   end;
sort_helper([], _, _, L1, L2) -> {L1, L2}.

%% @doc 计算顺序
match_sort([{Index, Flag} | T], X1, X2) ->
   Val1 = element(Index, X1),
   Val2 = element(Index, X2),
   if
      Val1 > Val2 ->
         Flag =< ?false;
      Val1 < Val2 ->
         Flag > ?false;
      true ->
         match_sort(T, X1, X2)
   end;
match_sort([], _, _) ->
   false.

%%@doc 检测等级和转数是否符合
check_round_level(Lv, ReLv, Round, ReRound) ->
   case ReRound > 0 of
      true ->
         Round >= ReRound;
      _ ->
         Lv >= ReLv
   end.

%% @doc 是否是合服
is_merge() ->
   MergeSec = merge_time(),
   OpenSec = open_time(),
   OpenSec < MergeSec.

%% @doc 获取合服天数
merge_day() ->
   case is_merge() of
      true ->
         MergeDate = config:merge_date(),
         MergeTime = {MergeDate, {0, 0, 0}},
         {CurrDate, _} = local_time(),
         NowTime = {CurrDate, {0, 0, 0}},
         {DiffDay, _} = calendar:time_difference(MergeTime, NowTime),
         max(1, DiffDay + 1);
      _ ->
         0
   end.

%% @doc 获取开服时间戳
open_time() ->
   OpenDate = config:open_date(),
   OpenTime = {OpenDate, {0, 0, 0}},
   datetime_to_seconds(OpenTime).

%% @doc 获取合服时间戳
merge_time() ->
   MergeDate = config:merge_date(),
   MergeTime = {MergeDate, {0, 0, 0}},
   datetime_to_seconds(MergeTime).

%% @doc 获取开服天数
open_day() ->
   Date = config:open_date(),
   OpenTime = {Date, {0, 0, 0}},
   {CurrDate, _} = local_time(),
   NowTime = {CurrDate, {0, 0, 0}},
   {DiffDay, _} = calendar:time_difference(OpenTime, NowTime),
   max(1, DiffDay + 1).

%% @doc 拼凑日期数2017.1.2=>20170102
localtime2date() ->
   {Date, _} = local_time(),
   localtime2date(Date).

localtime2date({Y, M, D}) ->
   Str = io_lib:format("~w~2..0w~2..0w", [Y, M, D]),
   Str1 = lists:flatten(Str),
   list_to_integer(Str1).

%% @doc 随机机器人名称
robot_name() ->
   Max = data_robotname:get_max(),
   HitNum = util:random(1, Max),
   data_robotname:get(HitNum).

%% @doc 整数列表转字符串==> [1,2,3] => "1,2,3"
parse_idstr(Ids) ->
   parse_idstr(Ids, []).

parse_idstr([H | T], L) ->
   StrH =
      case T of
         [] ->
            to_list(H);
         _ ->
            "," ++ to_list(H)
      end,
   parse_idstr(T, StrH ++ L);
parse_idstr([], L) ->
   L.

%% @doc 拼凑CDKEY
make_cdkey(Key, Num) when Num > 0 ->
   CDkEY = Key ++ md5(lists:concat([?MIL_NOW, Num])),
   ?PRINT("~s~n", [CDkEY]),
   make_cdkey(Key, Num - 1);
make_cdkey(_, _) ->
   ok.

%% @doc 判断两个时间戳是否不是同一天
other_day(Time1, Time2) ->
   {Date1, _} = unixtime_to_localtime(Time1),
   {Date2, _} = unixtime_to_localtime(Time2),
   Date1 =/= Date2.

%% Unix时间转换成对应的Erlang时间
%% 返回 Now = {MegaSecs, Secs, _MicroSecs} Erlang时间
unixtime_to_now(UnixTime) ->
   M = UnixTime div 1000000,
   S = UnixTime rem 1000000,
   {M, S, 0}.

%% Unix时间转换成对应的Localtime时间
%% 返回 LocalTime = {Date, Time} Localtime时间
unixtime_to_localtime(UnixTime) ->
   Now = unixtime_to_now(UnixTime),
   calendar:now_to_local_time(Now).

%% 获取今天是星期几
get_day_of_week() ->
   NowTime = unixtime(),
   get_day_of_week(NowTime).

%% 获取指定的Unix时间是星期几
get_day_of_week(UnixTime) ->
   {Date, _Time} = unixtime_to_localtime(UnixTime),
   calendar:day_of_the_week(Date).

%% 获取当天0点到现在的秒数
get_secs_from_midnight() ->
   NowTime = unixtime(),
   get_secs_from_midnight(NowTime).

%% 获取指定的Unix时间当天0点到当时的秒数
get_secs_from_midnight(UnixTime) ->
   {{_Year, _Month, _Day}, Time} = unixtime_to_localtime(UnixTime),
   calendar:time_to_seconds(Time).

%% @doc 获取指定时间距离当天0点的秒数
time_to_seconds(Time) ->
   calendar:time_to_seconds(Time).

%% @doc 检测两个字符串是否相等
same_str(Str1, Str2) ->
   TemStr1 =
      case is_binary(Str1) of
         true ->
            binary_to_list(Str1);
         _ ->
            Str1
      end,
   TemStr2 =
      case is_binary(Str2) of
         true ->
            binary_to_list(Str2);
         _ ->
            Str2
      end,
   TemStr1 =:= TemStr2.


%% @doc 去除字符串左右空格
rm_str_lr_space(String) ->
   String1 = rm_str_lr_space1(String),
   String2 = lists:reverse(String1),
   String3 = rm_str_lr_space1(String2),
   lists:reverse(String3).

rm_str_lr_space1([32 | T]) ->
   rm_str_lr_space1(T);
rm_str_lr_space1(T) ->
   T.

%% @doc 去除字符串所有空格
rm_str_space(String) ->
   rm_str_space(String, []).

rm_str_space([32 | T], L) ->
   rm_str_space(T, L);
rm_str_space([H | T], L) ->
   rm_str_space(T, [H | L]);
rm_str_space([], L) ->
   lists:reverse(L).

%% @doc 检测名称长度
check_name_length(Name) ->
   case lists:member(32, Name) of
      true ->
         {error, ?GMSG(svrmsg_10001)};
      _ ->
         case unicode:characters_to_list(list_to_binary(Name)) of
            {error, _, _Reason} ->
               %%非法字符
               {error, ?GMSG(svrmsg_10001)};
            CharList ->
               CharList1 = re:replace(CharList, "[\x{00010000}-\x{0010ffff}]", "", [{return, list}, global, unicode]),%过滤4字节的字符
               case CharList1 =:= CharList of
                  true ->
                     Len = util:string_width(CharList1),
                     case Len < 15 andalso Len > 2 of
                        true ->
                           true;
                        _ ->
                           %%名称长度为2~6个汉字
                           {error, ?GMSG(svrmsg_10002)}
                     end;
                  _ ->
                     {error, ?GMSG(svrmsg_10001)}
               end
         end
   end.

%% 转换其他类型为list
to_list(X) when is_integer(X) -> integer_to_list(X);
to_list(X) when is_float(X) -> float_to_list(X);
to_list(X) when is_atom(X) -> atom_to_list(X);
to_list(X) when is_binary(X) -> binary_to_list(X);
to_list(X) when is_tuple(X) -> tuple_to_list(X);
to_list(X) when is_list(X) -> X.

%% @doc 获取最高的服务器ID
get_max_svrid() ->
   SvrIds = config:server_ids(),
   lists:max(SvrIds).

%% @doc %% @spec bitstring_to_obj(String::list()) -> term()
%% @doc bitstring转换为term
bitstring_to_obj(BitObj) ->
   case catch bitstring_to_term(BitObj) of
      {ok, Obj} ->
         Obj;
      _ ->
         to_list(BitObj)
   end.

%% @spec bitstring_to_term(String::list()) -> {error, Why::term()} | {ok, term()}
%% @doc term反序列化，bitstring转换为term
bitstring_to_term(undefined) -> {ok, undefined};
bitstring_to_term(BitString) -> string_to_term(binary_to_list(BitString)).

%% @spec string_to_term(String::list()) -> {error, Why::term()} | {ok, term()}
%% @doc term反序列化，string转换为term，e.g., "[{a},1]"  => [{a},1]
string_to_term(String) ->
   case erl_scan:string(String ++ ".") of
      {ok, Tokens, _} ->
         erl_parse:parse_term(Tokens);
      {error, Err, _} -> {error, Err};
      Err -> {error, Err}
   end.

%% term序列化，term转换为string格式，e.g., [{a},1] => "[{a},1]"
term_to_string(Term) ->
   binary_to_list(list_to_binary(io_lib:format("~p", [Term]))).

%% term序列化，term转换为bitstring格式，e.g., [{a},1] => <<"[{a},1]">>
term_to_bitstring(Term) ->
   erlang:list_to_bitstring(io_lib:format("~p", [Term])).

%% @doc 随机列表
rand_list([Obj]) ->
   Obj;
rand_list(L) ->
   Len = length(L),
   case Len > 0 of
      true ->
         Index = random(1, Len),
         lists:nth(Index, L);
      _ ->
         false
   end.

%% @doc 安全创建ets,不抛任何异常～～
safe_create_ets(Name, Args) ->
   try
      case ets:info(Name) of
         undefined ->
            ets:new(Name, Args);
         _ -> %%已经存在就不重复创建了
            Name
      end
   catch _:EM ->
      ?ERR("safe_create_ets(~p,~p) exception ~p", [Name, Args, EM]),
      false
   end.

%% @doc 三目运算
ifelse(A, B, C) ->
   case A of
      true ->
         B;
      _ ->
         C
   end.

%% @doc 获取unix时间戳
unixtime() ->
   {MegaSecs, Secs, _MicroSecs} = os:timestamp(),
   MegaSecs * 1000000 + Secs.

longunixtime() ->
   {A, B, C} = os:timestamp(),
   A * 1000000000 + B * 1000 + C div 1000.

%%=========================================================================
%% 列表操作
%%=========================================================================

make_sign(Accid, Accname) ->
   TICKET = config:get_ticket(),
   Tstamp = unixtime(),
   Hex = md5(lists:concat([Accid, Accname, Tstamp, TICKET])),
   {Tstamp, Hex}.

%% 随机取出list元素
list_rand([]) -> null;
list_rand(List) ->
   Len = length(List),
   Index = random(1, Len),
   lists:nth(Index, List).

%% 随机从列表中选n个元素
%% @return:: null | List
list_rand_n([], _PickNum) -> null;
list_rand_n(List, PickNum) ->
   list_rand_n(List, PickNum, []).

list_rand_n([], _PickNum, AccList) -> AccList;
list_rand_n(_List, 0, AccList) -> AccList;
list_rand_n(List, PickNum, AccList) ->
   PickOne = list_rand(List),
   LeftList = List -- [PickOne],
   list_rand_n(LeftList, PickNum - 1, [PickOne | AccList]).

%% 依据权重，从元组列表中随机挑选N个元素，返回被抽中的元组列表
%% @args:: 
%%     Tuples::  元组列表 
%%     Index::   是权重所在的位置
%%     PickNum:: 随机出的数量
rand_by_weight(Tuples, Index, PickNum) when PickNum >= 0 ->
   rand_N_by_weight__(Tuples, Index, PickNum, []).

rand_N_by_weight__(_Tuples, _Index, 0, Ret) -> Ret;
rand_N_by_weight__([], _Index, _PickNum, Ret) -> Ret;
rand_N_by_weight__(Tuples, Index, PickNum, Ret) ->
   PickOne = rand_by_weight(Tuples, Index),
   LeftTuples = lists:delete(PickOne, Tuples),
   rand_N_by_weight__(LeftTuples, Index, PickNum - 1, [PickOne | Ret]).


%% 依据权重，从元组列表中随机挑选一个元素，返回被抽中的元组，
%%           如果没有对应的元素，则抛出异常
%%           Index是权重所在的位置
%% @return:  Tuple
rand_by_weight([], _Index) ->
   error(badargs);
rand_by_weight(Tuples, Index) ->
   Sum = lists:sum([element(Index, Tuple) || Tuple <- Tuples]),
   P = random(1, Sum),
   rand_one_by_weight__(Tuples, Index, P).

rand_one_by_weight__([Tuple], _, _) ->
   Tuple;
rand_one_by_weight__([Tuple | T], Index, P) ->
   case element(Index, Tuple) of
      Weight when P =< Weight ->
         Tuple;
      Weight ->
         rand_one_by_weight__(T, Index, P - Weight)
   end.

%% 过滤掉元组列表中某个元素相同的列表
%% eg：L=[{1,2},{2,2},{3,1}]. list_filter(L, 2) -> [{1,2},{3,1}]
list_filter(List, N) ->
   list_filter_helper(List, N, [], []).

list_filter_helper([H | T], N, ResultList, KeyList) ->
   Key = element(N, H),
   case lists:member(Key, KeyList) of
      true -> list_filter_helper(T, N, ResultList, KeyList);
      false -> list_filter_helper(T, N, [H | ResultList], [Key | KeyList])
   end;
list_filter_helper([], _, ResultList, _) -> ResultList.

%% 随机打乱list元素顺序
list_shuffle(List) ->
   Len = length(List),
   List1 = [{random(1, Len + 10000), X} || X <- List],
   List2 = lists:sort(List1),
   [E || {_, E} <- List2].

%% 根据下标替换list元素值
list_replace(Index, NewElem, List) ->
   list_replace_helper(List, Index, NewElem, 1, []).
list_replace_helper([], _Index, _NewElem, _CurIndex, NewList) ->
   NewList;
list_replace_helper([H | T], Index, NewElem, CurIndex, NewList) ->
   if Index =:= CurIndex ->
      list_replace_helper(T, Index, NewElem, CurIndex + 1, NewList ++ [NewElem]);
      true ->
         list_replace_helper(T, Index, NewElem, CurIndex + 1, NewList ++ [H])
   end.

%% 根据list的元素值获得下标
list_get_index(Elem, List) ->
   list_get_index_helper(List, Elem, 0).
list_get_index_helper([], _Elem, _Index) ->
   0;
list_get_index_helper([H | T], Elem, Index) ->
   if H =:= Elem ->
      Index + 1;
      true ->
         list_get_index_helper(T, Elem, Index + 1)
   end.

%% 根据list的元素值获得下标（加强版）
%% @param: (Elem, N, List), List为元组列表，N为元组中第N个元素等于Elem
%% @return {0,null} | {Index, H}
list_get_index_ex(Elem, N, List) when is_list(List), is_integer(N) ->
   list_get_index_ex(Elem, N, List, 0);
list_get_index_ex(_, _, _) -> {0, null}.
list_get_index_ex(_Elem, _N, [], _) -> {0, null};
list_get_index_ex(Elem, N, [H | _], Index) when element(N, H) =:= Elem -> {Index + 1, H};
list_get_index_ex(Elem, N, [_ | L], Index) -> list_get_index_ex(Elem, N, L, Index + 1).


%% 多个列表数值相加，结果以第一个列表的长度为准
lists_add([ResultList]) -> ResultList;
lists_add([List1, List2 | T]) ->
   ResultList = lists_add_helper(List1, List2, []),
   lists_add([ResultList | T]).

lists_add_helper([], _List2, ResultList) ->
   lists:reverse(ResultList);
lists_add_helper(List1, [], ResultList) ->
   lists:reverse(ResultList) ++ List1;
lists_add_helper([H1 | T1], [H2 | T2], ResultList) ->
   lists_add_helper(T1, T2, [H1 + H2 | ResultList]).

%% 扩展版lists:min/1
%% @param: (List, N), List为元组列表，N为元组中第N个元素
min_ex([H | T], N) -> min_ex(T, H, N).
min_ex([H | T], Min, N) when element(N, H) < element(N, Min) -> min_ex(T, H, N);
min_ex([_ | T], Min, N) -> min_ex(T, Min, N);
min_ex([], Min, _) -> Min.

%% 扩展版lists:max/1
%% @param: (List, N), List为元组列表，N为元组中第N个元素
max_ex([H | T], N) -> max_ex(T, H, N);
max_ex([], _N) -> 0.

max_ex([H | T], Max, N) when element(N, H) > element(N, Max) -> max_ex(T, H, N);
max_ex([_ | T], Max, N) -> max_ex(T, Max, N);
max_ex([], Max, _) -> Max.

%% 扩展版lists:max/1
%% @param: (List, N), List为元组列表，N为元组中第N个元素, Record为列表为空时调用者预期返回的内容
keymax([H | T], N, Record) -> keymax(T, H, N, Record);
keymax([], _N, Record) -> Record.

keymax([H | T], Max, N, Record) when element(N, H) > element(N, Max) -> keymax(T, H, N, Record);
keymax([_ | T], Max, N, Record) -> keymax(T, Max, N, Record);
keymax([], Max, _, _) -> Max.

%% 列表中的元素是否全部相同
%% @param: (List, N), List为元组列表，N为元组中第N个元素
is_all_same([H | T], N) -> is_all_same(T, H, N).

is_all_same([H | T], Min, N) when element(N, H) =:= element(N, Min) -> is_all_same(T, H, N);
is_all_same(L, _, _) when L =/= [] -> false;
is_all_same([], _, _) -> true.

%% 列表中某元素的总和
sum_ex(L, N) -> sum_ex(L, 0, N).
sum_ex([H | T], Sum, N) -> sum_ex(T, Sum + element(N, H), N);
sum_ex([], Sum, _) -> Sum.

%% for循环
for(Max, Max, F) ->
   F(Max);
for(I, Max, F) ->
   F(I),
   for(I + 1, Max, F).

%% 带返回状态的for循环
%% @return {ok, State}
for(Max, Min, _F, State) when Min < Max -> {ok, State};
for(Max, Max, F, State) -> F(Max, State);
for(I, Max, F, State) -> {ok, NewState} = F(I, State), for(I + 1, Max, F, NewState).

%% 在List中的每两个元素之间插入一个分隔符
implode(_S, []) ->
   [<<>>];
implode(S, L) when is_list(L) ->
   implode(S, L, []).
implode(_S, [H], NList) ->
   lists:reverse([type:object_to_list(H) | NList]);
implode(S, [H | T], NList) ->
   L = [type:object_to_list(H) | NList],
   implode(S, T, [S | L]).

%% 字符->列
explode(S, B) ->
   re:split(B, S, [{return, list}]).
explode(S, B, int) ->
   [list_to_integer(Str) || Str <- explode(S, B), length(Str) > 0].

%% 扩展的map函数
map_ex(_Fun, [], _Arg) ->
   [];
map_ex(Fun, [H | T], Arg) ->
   [Fun(H, Arg) | map_ex(Fun, T, Arg)].

%% 截取列表的第Begin个到第End个
sublist(L, Begin, End) ->
   sublist(L, Begin, End, {1, []}).
sublist([], _Begin, _End, {_NowNth, RetL}) ->
   lists:reverse(RetL);
sublist([_ | _L], _Bigen, End, {NowNth, RetL}) when NowNth > End ->
   lists:reverse(RetL);
sublist([Item | L], Begin, End, {NowNth, RetL}) when Begin =< NowNth andalso NowNth =< End ->
   sublist(L, Begin, End, {NowNth + 1, [Item | RetL]});
sublist([_ | L], Begin, End, {NowNth, RetL}) ->
   sublist(L, Begin, End, {NowNth + 1, RetL}).


%%=========================================================================
%% 字符文本操作
%%=========================================================================

%% 字符加密
check_char_encrypt(Id, Time, TK) ->
   TICKET = "7YnELt8MmA4jVED7",
   Hex = md5(lists:concat([Time, Id, TICKET])),
   %NowTime = time:unixtime(),
   %Hex =:= TK andalso NowTime - Time >= -10 andalso NowTime - Time < 300.
   Hex =:= TK.

%% 转换成HEX格式的md5
md5(S) ->
   lists:flatten([io_lib:format("~2.16.0b", [N]) || N <- binary_to_list(erlang:md5(S))]).

%% Function: 检查客户端发过来的内容,false为不合法,true为合法
%% @param: String: 客户端发来的字符串
%% @param: Length: 服务端限制的字符串长度
check_string(String, Length) ->
   case check_length(String, Length) of
      true ->
         case check_keyword(String, ["'", "/", "\"", "_", "<", ">"]) of
            false ->
               case check_keyword(String) of
                  false -> true;
                  true -> false
               end;
            true ->
               false
         end;
      false ->
         false
   end.

%% 检查关键字，存在非法字符返回true，否则false
%% @spec check_keyword(Text, Words) -> false | true
%% @param Text : 需要检查的字符串（或字符串的二进制形式）
%% @param Words: 非法字符列表
check_keyword(_, []) ->
   false;
check_keyword(Text, [Word | Words]) ->
   case re:run(Text, Word, [{capture, none}]) of
      match ->
         true;
      nomatch ->
         check_keyword(Text, Words)
   end.

filter_text_gm(Text) when is_bitstring(Text) ->
   Text;
filter_text_gm(Text) when is_list(Text) ->
   list_to_bitstring(Text).

%% 敏感词过滤
%% @param Text list() | bitstring()
%% @return bitstring() 过滤后的文本
filter_text(Text, Lv) when is_bitstring(Text) ->
   S = bitstring_to_list(Text),
   filter_text(S, Lv);
filter_text(Text, Lv) when is_list(Text) ->
   [Term] = io_lib:format("~ts", [Text]),
   svr_word:replace_sensitive_talk(Term, Lv).

%% 名字过滤
%% @param Text list() | bitstring()
%% @return bitstring() 过滤后的文本
filter_name(Text) when is_bitstring(Text) ->
   S = bitstring_to_list(Text),
   filter_name(S);
filter_name(Text) when is_list(Text) ->
   [Term] = io_lib:format("~ts", [Text]),
   svr_word:replace_sensitive_name(Term).

%% 敏感词检测
%% @return true 存在关键词
%%          false 不存在关键词
%% @var Text：字符串
check_keyword(Text) ->
   if
      is_list(Text) ->
         svr_chat:word_base_other(Text);
      true ->
         true
   end.

%% @doc 敏感词检测
%% @spec check_sen_keyword(Type, Text) -> true|false
%% where Type::base or base_other ; true 存在关键词 ； false 不存在关键词
check_sen_keyword(base, Text) when is_list(Text) ->
   svr_chat:word_base(Text);
check_sen_keyword(base_other, Text) when is_list(Text) ->
   svr_chat:word_base_other(Text);
check_sen_keyword(_, _) ->
   true.


%% 长度合法性检查
check_length(Item, LenLimit) ->
   check_length(Item, 1, LenLimit).

check_length(Item, MinLen, MaxLen) ->
   % case asn1rt:utf8_binary_to_list(list_to_binary(Item)) of
   %     {ok, UnicodeList} ->
   %         Len = string_width(UnicodeList),
   %         Len =< MaxLen andalso Len >= MinLen;
   %     {error, _Reason} ->
   %         false
   % end.
   case unicode:characters_to_list(list_to_binary(Item)) of
      UnicodeList when is_list(UnicodeList) ->
         Len = string_width(UnicodeList),
         Len =< MaxLen andalso Len >= MinLen;
      _ ->
         false
   end.

%% 字符宽度，1汉字=2单位长度，1数字字母=1单位长度
string_width(String) ->
   string_width(String, 0).
string_width([], Len) ->
   Len;
string_width([H | T], Len) ->
   case H > 255 of
      true ->
         string_width(T, Len + 2);
      false ->
         string_width(T, Len + 1)
   end.

%% IP元组转字符
ip2bin({A, B, C, D}) ->
   [integer_to_list(A), ".", integer_to_list(B), ".", integer_to_list(C), ".", integer_to_list(D)].

%% 过滤掉字符串中的特殊字符
filter_string(String, CharList) ->
   case is_list(String) of
      true ->
         filter_string_helper(String, CharList, []);
      false when is_binary(String) ->
         ResultString = filter_string_helper(binary_to_list(String), CharList, []),
         list_to_binary(ResultString);
      false ->
         ?ERR("filter_string: Error string=[~w]", [String]),
         String
   end.

filter_string_helper([], _CharList, ResultString) ->
   ResultString;
filter_string_helper([H | T], CharList, ResultString) ->
   case lists:member(H, CharList) of
      true -> filter_string_helper(T, CharList, ResultString);
      false -> filter_string_helper(T, CharList, ResultString ++ [H])
   end.

%%=========================================================================
%% 其他操作
%%=========================================================================

%% 产生一个介于Min到Max之间的随机整数
random(Same, Same) -> Same;
random(Min, Max) when Max < Min -> 0;
random(Min, Max) ->
   %% 以保证不同进程都可取得不同的种子
   case get("rand_seed") of
      undefined ->
         rand:seed(exs64, os:timestamp()),
         put("rand_seed", 1);
      _ -> skip
   end,
   M = Min - 1,
   if
      Max - M =< 0 -> 0;
      true -> rand:uniform(Max - M) + M
   end.

%% 向上取整
ceil(N) ->
   T = trunc(N),
   case N == T of
      true -> T;
      false -> 1 + T
   end.

%% 向下取整
floor(X) ->
   T = trunc(X),
   case (X < T) of
      true -> T - 1;
      _ -> T
   end.

%% 睡眠
sleep(T) ->
   receive
   after T -> ok
   end.

sleep(T, F) ->
   receive
   after T -> F()
   end.

%% 获取客户端ip
get_ip(Socket) ->
   case catch inet:peername(Socket) of
      {ok, {Ip, _Port}} ->
         Ip;
      _ ->
         case catch ssl:peername(Socket) of
            {ok, {Ip, _Port}} ->
               Ip;
            _ ->
               {0, 0, 0, 0}
         end
   end.

%%计数器
counter(Type) ->
   counter(Type, 1).
counter(Type, Inc) ->
   ets:update_counter(ets_counter, Type, Inc).

%% 获取运营服ID
get_server_id() ->
   Server = case application:get_env(game, card_server) of
               {ok, [_Ser]} -> _Ser;
               _ -> ""
            end,
   %{ok, [Server]} = application:get_env(card_server),
   Len = string:len(Server),
   case Len > 1 of
      true ->
         ServerId = string:sub_string(Server, 2, Len),
         case string:to_integer(ServerId) of
            {Id, _} -> Id;
            _ -> 0
         end;
      false ->
         0
   end.

%% 显示record 
%% 用法 : r2p(#ets_online{...}, record_info(fields, ets_online))    
r2p(A, B) -> record_to_proplist(A, B).
record_to_proplist(Record, Fields) ->
   record_to_proplist(Record, Fields, record__).

record_to_proplist(Record, Fields, TypeKey)
   when tuple_size(Record) - 1 =:= length(Fields) ->
   lists:zip([TypeKey | Fields], tuple_to_list(Record)).

%% 角度和cos的转换，cos(60') = 0.5
angle_to_float(Angle) ->
   math:cos(math:pi() * Angle / 180).

%% 分页取数据
% @param Data 所有数据（列表）
% @param Page 第几页数据（大于总页数则默认最后页）
% @param PageNum 每一页数量
% @return {总页数, 当前页, 当前页数据}
page_data(Data, Page, PageNum) ->
   Len = length(Data),
   PageTotal = ceil(Len / PageNum),
   PageNow = case Page > PageTotal of true -> max(1, PageTotal); false -> Page end,
   StartIndex = (PageNow - 1) * PageNum + 1,
   PickData = lists:sublist(Data, StartIndex, PageNum),
   {PageTotal, PageNow, PickData}.


%% --------------------------
%% 计算字符串的相似度
%% --------------------------
% 用于聊天检测
calc_string_compare(A, B) ->
   AWordDict = word_dict(unicode:characters_to_list(type:object_to_binary(A))),
   BWordDict = word_dict(unicode:characters_to_list(type:object_to_binary(B))),
   Dict = merge_dict(AWordDict, BWordDict),
   F = fun(_K, {V1, V2}, {DenominatorAcc, Sqdoc1Acc, Sqdoc2Acc}) ->
      {DenominatorAcc + V1 * V2
         , Sqdoc1Acc + V1 * V1
         , Sqdoc2Acc + V2 * V2
      }
       end,
   {Denominator, Sqdoc1, Sqdoc2} = dict:fold(F, {0, 0, 0}, Dict),
   case Sqdoc1 =:= 0 orelse Sqdoc2 =:= 0 of
      true -> 0;
      false -> Denominator / math:sqrt(Sqdoc1 * Sqdoc2)
   end.

merge_dict(D1, D2) ->
   F1 = fun(_K, V) -> {V, 0} end,
   D1_ = dict:map(F1, D1),
   F2 = fun(K, V, Dict) ->
      case dict:find(K, D1_) of
         error -> dict:store(K, {0, V}, Dict);
         {ok, {V1, 0}} -> dict:store(K, {V1, V}, Dict);
         _ -> Dict
      end
        end,
   D2_ = dict:fold(F2, D1_, D2),
   D2_.
% % 过滤常用词
% F3 = fun(K, _V) -> not lists:member(K, ?GENERAL_WORD) end,
% D3_ = dict:filter(F3, D2_),
% D3_.

% 取字（连续数字、连续字符当作一个字）
word_dict(L) -> word__(L, [], dict:new()).
% A-Z   65-90 
% a-z   97-122
% 0-9   48-57
word__([A | L], Word, WordDict) when (A >= 65 andalso A =< 90) orelse
   (A >= 97 andalso A =< 122) orelse
   (A >= 48 andalso A =< 57) ->
   word__(L, [A | Word], WordDict);
word__([I | L], [], WordDict) ->
   word__(L, [], dict:update_counter([I], 1, WordDict));
word__([I | L], Word, WordDict) ->
   WordDict1 = dict:update_counter(Word, 1, WordDict),
   WordDict2 = dict:update_counter([I], 1, WordDict1),
   word__(L, [], WordDict2);
word__([], [], WordList) ->
   WordList;
word__([], Word, WordDict) ->
   dict:update_counter(Word, 1, WordDict).

%% @doc 打印进程信息
process_infos() ->
   filelib:ensure_dir("../logs/"),
   File = "../logs/processes_infos.log",
   {ok, Fd} = file:open(File, [write, raw, binary, append]),
   Fun = fun(Pi) ->
      Info = io_lib:format("=>~p \n\n", [Pi]),
      case filelib:is_file(File) of
         true -> file:write(Fd, Info);
         false ->
            file:close(Fd),
            {ok, NewFd} = file:open(File, [write, raw, binary, append]),
            file:write(NewFd, Info)
      end,
      timer:sleep(20)
         end,
   [Fun(erlang:process_info(P)) || P <- erlang:processes()].

cn(Data) ->
   io:format("~ts~n", [Data]).

%% @spec datetime_to_seconds(DateTime) -> false | SecondsTime
%% DateTime = {{2011,11,15},{16,14,57}} = {{Y, M, D}, {h, m, s}} 
%% @doc 将日期转换unix时间戳
datetime_to_seconds(0) -> 0;
datetime_to_seconds(DateTime) ->
   case calendar:local_time_to_universal_time_dst(DateTime) of
      [] -> false;
      [_, Udate] ->
         calendar:datetime_to_gregorian_seconds(Udate) - 719528 * 24 * 3600;
      [Udate] ->
         calendar:datetime_to_gregorian_seconds(Udate) - 719528 * 24 * 3600
   end.

%% @doc 重新定向中央服务器
redirect_center(NodeName, Cookie) ->
   case svr_node_kf:get_node_id() =:= 0 of
      true ->
         case nodes(hidden) of
            [] ->
               ok;
            Nodes ->
               F = fun(TemNodeName) ->
                  catch rpc:call(TemNodeName, svr_kfcenter, del_node, [node()]),
                  net_kernel:disconnect(TemNodeName)
                   end,
               [F(TemNodeName) || TemNodeName <- Nodes]
         end,
         erlang:set_cookie(NodeName, Cookie),
         case net_adm:ping(NodeName) of
            pong ->
               Sql = io_lib:format(<<"replace into base_kf(`id`,`name`,`cookie`) values(~p, ~p, '~p')">>, [100, NodeName, Cookie]),
               db:execute_nohalt(Sql);
            _ ->
               ok
         end;
      _ ->
         ok
   end.

%% @doc 检测是否在范围内
check_range(X, Y, TarX, TarY, Range) ->
   X1 = abs(X - TarX),
   Y1 = abs(Y - TarY),
   math:sqrt(X1 * X1 + Y1 * Y1) =< Range.