%%%-----------------------------------
%%% module  : util
%%% @author  : xyao
%%% email   : jiexiaowen@gmail.com
%%% Created : 2011.06.14
%%% description: 公共函数
%%%-----------------------------------
-module(util).
-include("server.hrl").
-include("server_env.hrl").
-include("server_kv.hrl").
-include("common.hrl").
-include("node.hrl").
-include("errcode.hrl").
-include("goods.hrl").
-include("def_fun.hrl").
-include("def_goods.hrl").
-export([
        bitstring_to_term/1
        ,boolean_to_integer/1
        ,send_after/4                   %% 设定定时器
        ,cancel_timer/1                 %% 取消定时器（自带判断）
        ,cast_event_to_players/1        %% 通知所有线路的玩家信息
        ,cast_event_to_players_helper/1 %% 通知所有线路的玩家信息（辅助函数）
        ,cast_event_to_players_delay/2
        ,cast_event_to_players_helper_delay/2
        ,check_char_encrypt/3
        ,check_length/2
        ,check_length/3
        ,combine_list/1                 %% 合并KeyValueList(Key相同的项 Value值相加)
        ,combine_list/2                 %% 合并KeyValueList(Key相同的项 Value值相加)
        ,minus_list/2                   %% 减去KeyValueList(Key相同的项 Value值相减)
        ,debug/4                        %% 直接写入文本数据到日志文件 logs/xxx
        ,errlog/4                       %% 直接写入文本数据到日志文件 logs/xxx
        ,info/4                         %% 直接写入文本数据到日志文件 logs/xxx
        ,log_file/1
        ,log_file/2
        ,log_file/5
        ,filter_string/2
        ,find_ratio/2
        ,float_sub/2                    %% 按精度保留浮点数
        ,for/3                          %% 循环
        ,for/4                          %% 带状态循环
        ,get_list_elem_index/2
        ,is_merge_game/0                %% 是否合服过 true | false
        ,get_merge_time/0               %% 获取合服时间
        ,get_merge_day/0                %% 获取现在是合服第几天
        ,get_merge_day/1                %% 获取现在是合服第几天
        ,get_merge_day_start_time/0     %% 获取合服当天的开始时间
        ,get_merge_count/0              %% 获取合服次数
        ,get_open_day/0
        ,get_open_day/1
        ,calc_open_day/1, calc_open_day/2
        ,get_open_time/0
        ,get_real_open_time/0
        ,check_open_day/1
        ,check_open_day/2
        ,get_tuplelist_elem_index/3
        ,get_validate_code/0            %% 获得验证码,只有十位(验证码有可能相同)
        ,integer_to_boolean/1
        ,is_cls/0                       %% 是否跨服中心
        ,list_to_record/2
        ,make_sure_binary/1
        ,make_sure_list/1
        ,md5/1
        ,multiserver_delay/4            %% 叠服延时运行
        ,rand_time_to_delay/4           %% 延迟1s内，起进程处理
        ,protocol/3
        ,record_return_form/2           %% 记录返回格式化，自定返回记录中的字段组成
        ,record_to_list/1
        ,replace/2                      %% 替换字符串内容
        ,replace_list_elem/3
        ,string_to_term/1
        ,string_width/1
        ,string_width/2
        ,term_to_bitstring/1
        ,term_to_string/1
        ,to_term/1
        ,ulist/1                        %% 保证列表的元素是唯一的unique_list
        ,fmt_bin/1                      %% format为字符串的binary形式
        ,fmt_bin/2
        ,fmt_list/1                     %% format为字符串的list形式
        ,fmt_list/2
        ,join_bitstring/2               %% 将二进制形式的字符串列表以指定字符串分隔
        ,eval/1                         %% 扫描字符串执行erlang语句
        ,valid_mfa/1                    %% 对MFA进行基本的验证
        ,do_mfa/1                       %% 执行MFA
        ,count_page/3                   %% 计算分页
        ,change_case/2
        ,strip/1
        ,term_area_value/4              %% 条件区域取值(返回元组中指定一元素)
        ,term_area_elem/3               %% 条件区域取值(返回元组)
        ,additive_tuplelist_elem/5      %% 累加tuplelist某一项的值
        ,minus_tuplelist_elem/5         %% 累减tuplelist某一项的值
        ,check_list/1                   %% 检查列表
        ,check_list/2                   %% 检查列表
        ,link_list/1                    %% 连接列表
        ,link_list/2                    %% 连接列表
        ,link_list_extra/1              %% 多个字符拼接成一个字符串
        ,parse_error_code/1
        ,make_error_code_msg/1
        ,make_goods_str/1
        ,pack_tv_goods/1
        ,check_lists/2
        ,ceil/1
        ,floor/1
        ,get_world_lv/0
        ,index_of_record/2
        ,get_server_name/0
        ,record_plus/2                  %% 数值型record相加
        ,record_minus/2                 %% 数值型record相减
        ,ip2bin/1
        ,debug_time_info/0

        ,snap/2
    ]).

%% 下面几个函数通常不直接使用，而是以include/common.hrl中的宏定义替代
%% ?ERR(Format, Args)
%% ?INFO(Format, Args)
%% ?DEBUG(Format, Args)
%%errlog(Format, Args, Mod, Line) -> log_file("ERROR", Format, Args, Mod, Line).
%%info(Format, Args, Mod, Line)   -> log_file("INFO",  Format, Args, Mod, Line).
%%debug(Format, Args, Mod, Line)  -> log_file("DEBUG", Format, Args, Mod, Line).

errlog(Format, Args, Mod, Line) -> manual_log(error, Format, Args, Mod, Line).
info(Format, Args, Mod, Line)   -> manual_log(info,  Format, Args, Mod, Line).
debug(Format, Args, Mod, Line)  -> manual_log(debug, Format, Args, Mod, Line).

manual_log(Level, Format, Args, Mod, Line) ->
    logger_game_h:manual_log(Level, io_lib:format("[~p:~p]~n" ++ Format ++ "~n", [Mod, Line|Args])).

%% 日志记录函数
log_file(Format) ->
    log_file(error, Format, [], undefined, undefined).
log_file(Format, Args) ->
    log_file(error, Format, Args, undefined, undefined).
log_file(Tag, Format, Args, Mod, Line) ->
    {{Y, M, D}, {H, I, S}} = erlang:localtime(),
    TagLower = strip(change_case(lower, Tag)),
    FDKey = {fd, TagLower},
    IoDevice =
    case get(FDKey) of
        {OldIoDevice, Y, M, D} ->
            case is_process_alive(OldIoDevice) of
                true  -> OldIoDevice;
                false -> undefined
            end;
        {OldIoDevice, _, _, _} ->
            file:close(OldIoDevice),
            undefined;
        _ ->
            undefined
    end,
    FH =
    case IoDevice of
        undefined ->
            Pre = case config:get_cls_type() of
                1 -> "center_";
                _ -> "master_"
            end,
            LogPath = config:get_log_path(),
            File =
            case lists:member(TagLower, [<<"error">>, <<"info">>, <<"debug">>]) of
                true  -> util:fmt_bin("~ts/~ts~4..0w_~2..0w_~2..0w.txt", [LogPath, Pre, Y, M, D]);
                false -> util:fmt_bin("~ts/~ts~ts_~4..0w_~2..0w_~2..0w.txt", [LogPath, Pre, TagLower, Y, M, D])
            end,
            {ok, NewIoDevice} = file:open(File, [write, append, {encoding, utf8}]),
            put(FDKey, {NewIoDevice, Y, M, D}),
            NewIoDevice;
        _ ->
            IoDevice
    end,
    DateTime = io_lib:format("~4..0w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w", [Y, M, D, H, I, S]),
    case {Mod, Line} =:= {undefined, undefined} of
        true  ->
            NewFormat = unicode:characters_to_list(lists:concat(["#", Tag, " ~ts\n", Format, "\n\n"])),
            io:format(FH, NewFormat, [DateTime | Args]);
        false ->
            NewFormat = unicode:characters_to_list(lists:concat(["#", Tag, " ~ts[~w:~w]\n", Format, "\n\n"])),
            io:format(FH, NewFormat, [DateTime, Mod, Line | Args])
    end.

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

%% 带返回状态的for循环
%% @return {ok, State}
for(I, Max, _F, State) when Max<I -> {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).

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

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

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

%% term反序列化，string转换为term，e.g., "[{a},1]"  => [{a},1]
string_to_term(String) ->
    case erl_scan:string(String++".") of
        {ok, Tokens, _} ->
            case erl_parse:parse_term(Tokens) of
                {ok, Term} -> Term;
                _Err -> undefined
            end;
        _Error ->
            undefined
    end.

%% term反序列化，bitstring转换为term，e.g., <<"[{a},1]">>  => [{a},1]
bitstring_to_term(undefined) -> undefined;
bitstring_to_term(BitString) ->
    string_to_term(binary_to_list(BitString)).

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

check_length(Item, MinLen, MaxLen) ->
    case unicode:characters_to_list(Item) of
        {incomplete, _Encoded, _Rest} ->
            false;
        {error, _Encoded, _Rest} ->
            false;
        UnicodeList ->
            Len = string_width(UnicodeList),
            Len =< MaxLen andalso Len >= MinLen
    end.

%% 字符宽度
%% 1数字/字母=1, 1汉字=2
string_width(String) ->
    string_width(String, 0).

string_width([], Len) ->
    Len;
string_width([CharUnicode | T], Len) when CharUnicode > 255 ->
    string_width(T, Len + 2);
string_width([_ | T], Len) ->
    string_width(T, Len + 1).

%%=========================================================================
%% 辅助函数
%%=========================================================================

%%向上取整
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.

%% 保证列表中每个元素都是唯一的.unique_list()
ulist(List) ->
    F = fun(T, L) ->
            case lists:member(T, L) of
                true -> L;
                false -> [T|L]
            end
    end,
    lists:foldl(F, [], List).

%% -----------------------------------------------------------------
%% 确保字符串类型为二进制
%% -----------------------------------------------------------------
make_sure_binary(String) ->
    case is_binary(String) of
        true  -> String;
        false when is_list(String) -> unicode:characters_to_binary(String);
        false ->
            ?ERR("util:make_sure_binary: Error string=[~p] ~n", [String]),
            <<>>
    end.

%% -----------------------------------------------------------------
%% 确保字符串类型为列表
%% -----------------------------------------------------------------
make_sure_list(String) ->
    case is_list(String) of
        true  -> String;
        false when is_binary(String) -> unicode:characters_to_list(String);
        false ->
            ?ERR("util:make_sure_list: Error string=[~p]~n", [String]),
            []
    end.

%% -----------------------------------------------------------------
%% 过滤掉字符串中的特殊字符
%% -----------------------------------------------------------------
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]),
            <<>>
    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.

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

%% -----------------------------------------------------------------
%% 根据tuplelists的key值获得下标
%% -----------------------------------------------------------------
get_tuplelist_elem_index(Key, N, List) ->
    get_tuplelist_elem_index_helper(List, Key, N, 0).
get_tuplelist_elem_index_helper([], _Key, _N, _Index) ->
    0;
get_tuplelist_elem_index_helper([H|T], Key, N, Index) when is_tuple(H) ->
    if  element(N, H) =:= Key ->
            Index+1;
        true ->
            get_tuplelist_elem_index_helper(T, Key, N, Index+1)
    end;
get_tuplelist_elem_index_helper([_|T], Key, N, Index) ->
    get_tuplelist_elem_index_helper(T, Key, N, Index+1).

%% -----------------------------------------------------------------
%% 增加lists相应元素的值并获得新lists
%% -----------------------------------------------------------------
replace_list_elem(Index, NewElem, List) ->
    replace_list_elem_helper(List, Index, NewElem, 1, []).
replace_list_elem_helper([], _Index, _NewElem, _CurIndex, NewList) ->
    NewList;
replace_list_elem_helper([H|T], Index, NewElem, CurIndex, NewList) ->
    if  Index =:= CurIndex ->
            replace_list_elem_helper(T, Index, NewElem, CurIndex+1, NewList++[NewElem]);
        true ->
            replace_list_elem_helper(T, Index, NewElem, CurIndex+1, NewList++[H])
    end.

%% 获取开服时间(跨服中心不可用)
get_open_time() ->
    case ets:lookup(?SERVER_ENV, open_time) of
        [] -> 0;
        [S] -> S#server_env.value
    end.

%% 获取开服的具体时间(跨服中心不可用)
get_real_open_time() ->
    case ets:lookup(?SERVER_ENV, real_open_time) of
        [] -> 0;
        [S] -> S#server_env.value
    end.

%% 获取开服天数(跨服中心不可用)
get_open_day() ->
    Now = utime:unixtime(),
    get_open_day(Now).

%% 获取开服时间与Time相距的天数(跨服中心不可用)
get_open_day(Time) ->
    OpenTime = get_open_time(),
    utime:day_span(Time, OpenTime).

%% ---------------------------------------------------------------------------
%% @doc 计算开服天数
-spec calc_open_day(RealOpenTime) -> OpenDays when
    RealOpenTime:: integer(),
    OpenDays    :: integer().
%% ---------------------------------------------------------------------------
calc_open_day(RealOpenTime) ->
    utime:day_span(RealOpenTime).

%% 计算开服时间与Time相距的天数
calc_open_day(RealOpenTime, Time) ->
    utime:day_span(RealOpenTime, Time).

%% 判断开服天数是否在多少天内
check_open_day(Day) ->
    NowTime = utime:unixtime(),
    check_open_day(Day, NowTime).

check_open_day(Day, Unixtime) ->
    OpenTime = get_open_time(),
    DaySpan = utime:diff_day(OpenTime, Unixtime),
    DaySpan =< Day.

%% 是否合服过 true | false (跨服中心不可用)
is_merge_game() ->
    case get_merge_time() of
        0 -> false;
        Time when is_integer(Time), Time > 0 -> true;
        _ -> false
    end.

%% 获取合服准确时间(当前的合服准确时间,不一定是合服当天的开始时间) (跨服中心不可用)
%% “合服准确时间”与“合服准确时间零点”可能不一致,调用时注意
get_merge_time() ->
    lib_merge_server:get_time().

%% 获取现在是合服第几天 (跨服中心不可用)
get_merge_day() ->
    Now = utime:unixtime(),
    get_merge_day(Now).

get_merge_day(Now) ->
    case get_merge_day_start_time() of
        0 -> 0;
        MergeTime ->
            utime:day_span(Now, MergeTime)
    end.

%% 获取合服当天的开始时间  (跨服中心不可用)
get_merge_day_start_time() ->
    case get_merge_time() of
        0 -> 0;
        Time -> utime:unixdate(Time)
    end.

%% 获得合服的次数
get_merge_count() ->
    case ets:lookup(?SERVER_ENV, merge_count) of
        [] -> 0;
        [S] -> S#server_env.value
    end.

%% 是否跨服中心
is_cls() ->
    %% 0:单服 1:跨服中心
    config:get_cls_type() == 1.

%% 世界等级
get_world_lv() ->
    case is_cls() of
        false ->
            case ets:lookup(?SERVER_KV, ?SERVER_KV_WORLD_LV) of
                [] -> 1;
                [KV] -> KV#server_kv.value
            end;
        true -> 1
    end.

to_term(BinString) when is_list(BinString) -> BinString;
to_term(BinString) when is_binary(BinString) ->
    case util:bitstring_to_term(BinString) of
        undefined -> [];
        Term -> Term
    end;
to_term(_BinString) -> [].

boolean_to_integer(true) -> 1;
boolean_to_integer(false) -> 0.
integer_to_boolean(1) -> true;
integer_to_boolean(0) -> false.


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

%% 设定定时器
send_after(OldRef, Time, Pid, Msg) ->
    util:cancel_timer(OldRef),
    erlang:send_after(Time, Pid, Msg).


%% 取消定时器（自带判断）
cancel_timer(Ref) when is_reference(Ref) -> erlang:cancel_timer(Ref);
cancel_timer(_) -> false.

%% --------------------------------------------------------------------------------------
%% @doc 记录返回格式化
-spec record_return_form(Record, ResultForm) -> Result when
    Record      :: tuple(),                      %% 各种记录
    ResultForm  :: list() | integer() | all,     %% 属性组装列表或者单项属性
    %% ResultForm例如: [#ets_mon.xx1, ...] | {#ets_mon.xx1, ...} | all | #ets_mon.xx1
    Result      :: list() | value     | tuple(). %% 对应ResultForm的传入得出不同的参数
%% @end
%% --------------------------------------------------------------------------------------
record_return_form(Record, ResultForm) when is_list(ResultForm)->
    F = fun(Num) ->
            element(Num, Record)
    end,
    [F(Num) || Num <- ResultForm, is_integer(Num)];
%% @param {#ets_mon.xx1, #ets_mon.xx2...}
%% @return {Mon#ets_mon.xx1, Mon#ets_mon.xx2...}
record_return_form(Record, ResultForm) when is_tuple(ResultForm) ->
    ResultFormList = tuple_to_list(ResultForm),
    ResultList = record_return_form(Record, ResultFormList),
    list_to_tuple(ResultList);
record_return_form(Record, ResultForm) ->
    case ResultForm of
        all -> Record;
        _   when is_integer(ResultForm) -> element(ResultForm, Record);
        _   -> bad_result_form
    end.

%% list 转 record
%% @param List 必须长度跟rocrod一样
%% @param RecordAtom record的名字,是一个原子
%% @return RecordAtom类型的RECORD
list_to_record(List, RecordAtom) ->
    RecordList = [RecordAtom|List],
    erlang:list_to_tuple(RecordList).

%% record 转 list
%% @return 一个LIST 成员同 Record一样
record_to_list(Record) when erlang:is_tuple(Record)->
    RecordList = erlang:tuple_to_list(Record),
    [_|ListLeft] = RecordList,
    ListLeft;
record_to_list(Record) when is_list(Record) ->
    Record;
record_to_list(_Record)->
    [].

protocol(PlayerName, Cmd, Bin) ->
    %%{ok, PlayerName2} = asn1rt:utf8_binary_to_list(PlayerName),
    PlayerName2 = make_sure_list(PlayerName),
    case lib_player:get_role_id_by_name(PlayerName2) of
        null -> false;
        PlayerId ->
            case misc:get_player_process(PlayerId) of
                Pid when is_pid(Pid) ->
                    gen_server:call(Pid, {'SOCKET_EVENT', Cmd, Bin});
                _ ->
                    false
            end
    end.

%% List为元组列表，N为元组里的某个元素,该元素为概率值
find_ratio(List, N) ->
    F = fun(RatioInfo, Sum) ->
            element(N, RatioInfo) + Sum
    end,
    TotalRatio = lists:foldl(F, 0, List),
    Rand = urand:rand(1, TotalRatio),
    find_ratio(List, 0, Rand, N).
%% 查找匹配机率的值
find_ratio([], _, _, _) -> null;
find_ratio(InfoList, Start, Rand, N) ->
    [Info | List] = InfoList,
    End = Start + element(N, Info),
    case Rand > Start andalso Rand =< End of
        true -> Info;
        false -> find_ratio(List, End, Rand, N)
    end.

%% ---------------------------------------------------------------------------
%% @doc 合并KeyValueList(Key相同的项 Value值相加)
-spec combine_list(List) -> NewList when
    List   :: [{Key, Num}],
    NewList:: [{Key, Num}],
    Key    :: term(),
    Num    :: integer().
%% ---------------------------------------------------------------------------
combine_list(GoodsList)->
    combine_list(GoodsList,0).
combine_list(GoodsList,0) ->
    F = fun({Key, Value}, Result) ->
            case lists:keyfind(Key, 1, Result) of
                false -> [{Key, Value} | Result];
                {_, Value1} ->
                    lists:keystore(Key, 1, Result, {Key, Value + Value1})
            end
    end,
    lists:foldl(F, [], GoodsList);
combine_list(GoodsList,_) ->
    F = fun({T,K,V}, Result) ->
            case lists:keyfind(K, 2, Result) of
                {T,_,V1} ->
                    lists:keystore(K, 2, Result, {T,K, V + V1});
                _->
                    [{T,K,V} | Result]
            end
    end,
    lists:foldl(F, [], GoodsList).

%% KeyValList相减(MinusList中的Key存在于List中 则List对应的Val减去MinusList对应的Val)
minus_list(List, MinusList) ->
    F = fun({Key, Value}, AccList) ->
            case lists:keyfind(Key, 1, AccList) of
                false -> AccList;
                {_, Value1} ->
                    lists:keystore(Key, 1, AccList, {Key, Value1 - Value})
            end
    end,
    lists:foldl(F, List, MinusList).


%% 通知所有线路的玩家信息
%% @param Event mod_server_cast:handle_cast(Event, State)中的Event
cast_event_to_players(Event) ->
    Server = node_mgr:node_list(),
    F = fun(S) ->
            rpc:cast(S#node.node, ?MODULE, cast_event_to_players_helper, [Event])
    end,
    [F(S) || S <- Server].

cast_event_to_players_helper(Event) ->
    Pids = lib_player:get_online_pids(),
    [gen_server:cast(Pid, Event) || Pid <- Pids],
    ok.

%% 通知所有线路的玩家信息(每个玩家都间隔几秒)
%% @param Event mod_server_cast:handle_cast(Event, State)中的Event
%% @param SleepMs 毫秒
cast_event_to_players_delay(Event, SleepMs) ->
    Server = node_mgr:node_list(),
    F = fun(S) ->
            rpc:cast(S#node.node, ?MODULE, cast_event_to_players_helper_delay, [Event, SleepMs])
    end,
    [F(S) || S <- Server].

cast_event_to_players_helper_delay(Event, SleepMs) ->
    Pids = lib_player:get_online_pids(),
    spawn(fun() -> spawn_cast_event_to_players_helper_delay(Pids, Event, SleepMs) end),
    ok.

%% 开进程
spawn_cast_event_to_players_helper_delay([], _Event, _SleepMs) -> ok;
spawn_cast_event_to_players_helper_delay([Pid | Pids], Event, SleepMs) ->
    gen_server:cast(Pid, Event),
    timer:sleep(SleepMs),
    spawn_cast_event_to_players_helper_delay(Pids, Event, SleepMs).

%% 获得验证码,只有十位(验证码有可能相同)
get_validate_code() ->
    Timestamp = utime:longunixtime(),
    KeyRand = urand:rand(1, 10000),
    KeyTicket = string:to_upper(util:md5(lists:concat([KeyRand, Timestamp]))),
    %% 截取10位
    lists:sublist(KeyTicket, 10).

%% ---------------------------------------------------------------------------
%% @doc 按精度保留浮点数
-spec float_sub(Float, Sub) ->  Return when
    Float   :: float(),         %%
    Sub     :: integer(),       %% 列表
    Return  :: float().         %% 返回值
%% ---------------------------------------------------------------------------
float_sub(Float, Sub) when is_integer(Float) ->
    F = float_to_binary(float(Float), [{decimals, Sub}]),
    binary_to_float(F);
float_sub(Float, Sub) when is_float(Float) ->
    F = float_to_binary(Float, [{decimals, Sub}]),
    binary_to_float(F).

%% 替换字符串内容
replace(String, []) ->
    String;
replace(String, [{RE,Replacement}|Left]) ->
    NewString = replace(String, RE, Replacement),
    replace(NewString, Left).

replace(String, RE, Replacement) ->
    case re:run(String, RE) of
        nomatch -> String;
        _ ->
            NewString = re:replace(String, RE, Replacement, [{return,list}]),
            replace(NewString, RE, Replacement)
    end.

multiserver_delay(DelaySec, Module, Function, Args) ->
    SerId = config:get_server_id(),
    %% 至少延迟这么多时间
    EveryServerDelayTime = SerId rem 5 * 1000,
    Sleep = urand:rand(EveryServerDelayTime, EveryServerDelayTime + round(DelaySec * 1000)),
    timer:sleep(Sleep),
    apply(Module, Function, Args).

%% 延时1s内的创建进程处理
rand_time_to_delay(DelaySec, Module, Function, Args) ->
    RandTime = urand:rand(1, DelaySec),
    spawn(
        fun() -> timer:sleep(RandTime), apply(Module, Function, Args) end
    ).


%% ------------------------------------------------------------------------
%% 返回格式化的二进制字符串
-spec fmt_bin(Format, Args) -> binary() when
    Format :: list() | binary(),
    Args :: list().
fmt_bin(Format, Args) when is_list(Format) ->
    unicode:characters_to_binary(io_lib:format(Format, Args));
fmt_bin(Format, Args) when is_binary(Format) ->
    unicode:characters_to_binary(io_lib:format(unicode:characters_to_list(Format), Args)).

fmt_bin(Format) ->
    fmt_bin(Format, []).

%% 返回格式化的字符串
-spec fmt_list(Format, Args) -> list() when
    Format :: list() | binary(),
    Args :: list().
fmt_list(Format, Args) when is_list(Format) ->
    unicode:characters_to_list(io_lib:format(Format, Args));
fmt_list(Format, Args) when is_binary(Format) ->
    unicode:characters_to_list(io_lib:format(unicode:characters_to_list(Format), Args)).

fmt_list(Format) ->
    fmt_list(Format, []).

%% 将二进制形式的字符串列表以指定字符串分隔，组成一个二进制字符串(类似string:join/2)
-spec join_bitstring(BitStrings :: [binary()], Separator :: binary() | list()) -> BitString :: binary().
join_bitstring([], _) -> <<>>;
join_bitstring([HeadBitString | Others], Separator) when is_binary(Separator) ->
    list_to_bitstring([HeadBitString | lists:append([ [Separator, BitString] || BitString <- Others])]);
join_bitstring(BitStrings, Separator) when is_list(Separator) ->
    join_bitstring(BitStrings, list_to_bitstring(Separator)).

%% 执行erlang语句，注意，必须以.结尾，例如："erlang:time()."
eval(OldString) ->
    try
        String =
        case is_binary(OldString) of
            true  -> unicode:characters_to_list(OldString);
            false -> OldString
        end,
        {ok, Tokens, _} = erl_scan:string(String),
        {ok, ExprList} = erl_parse:parse_exprs(Tokens),
        {value, Value, _} = erl_eval:exprs(ExprList, []),
        Value
    catch
        ?EXCEPTION(_Class, _Reason, _Stack) ->
            {_Class, _Reason, ?GET_STACK(_Stack)}
    end.

%% 对MFA进行基本的验证
%% 不验证参数数量（某些地方会在Args中添加参数，不验证参数数量更方便）
valid_mfa(MFA) ->
    case MFA of
        {Module, Func, Args}-> is_atom(Module) andalso is_atom(Func) andalso is_list(Args);
        {Func, Args}        -> is_list(Args) andalso is_function(Func, Args);
        Func                -> is_function(Func)
    end.

%% 执行MFA
do_mfa({Module, Func, Args}) when is_atom(Module) andalso is_atom(Func) andalso is_list(Args) ->
    erlang:apply(Module, Func, Args);
do_mfa({Func, Args}) when is_list(Args) andalso is_function(Func, length(Args)) ->
    erlang:apply(Func, Args);
do_mfa(Func) when is_function(Func, 0) ->
    Func();
do_mfa(_) ->
    {error, badarg}.

%% 计算分页数据
%% PageNo    请求的页码
%% PageNum   请求的页尺寸
%% List      要分页的数据
%% LenOfList 要分页的列表长度
%% @spec count_page(PageNo, PageSize, List | LenOfList)  -> {Begin, Len, NewPageNo, TotalPage}
%% Begin     起始位置
%% Len       长度
%% NewPageNo 实际显示页数
%% TotalPage 总页数
%% 返回参数适用于lists:sublist/3
count_page(PageNo, PageSize, LenOfList) when is_integer(LenOfList) ->
    TotalPage = case LenOfList rem PageSize of
        0 -> LenOfList div PageSize;
        _ -> LenOfList div PageSize + 1
    end,
    NewPageNo = case PageNo > TotalPage of
        true -> max(1, TotalPage);
        _ -> max(1, PageNo)
    end,
    Begin = (NewPageNo - 1) * PageSize + 1,
    Len = case NewPageNo * PageSize > LenOfList of
        true ->
            LenOfList - (NewPageNo - 1) * PageSize;
        _ -> PageSize
    end,
    {Begin, Len, NewPageNo, TotalPage};
count_page(PageNo, PageSize, List) when is_list(List) ->
    TotalNum = length(List),
    count_page(PageNo, PageSize, TotalNum).

%% 大小写转换(多字节安全)，返回binary
-spec change_case(CaseType :: lower | upper, CharactersData :: iodata()) -> binary().
change_case(CaseType, CharactersData) ->
    ChangeFun =
    case CaseType of
        lower ->    % 转小写
            fun string:to_lower/1;
        _Upper ->   % 转大写
            fun string:to_upper/1
    end,
    unicode:characters_to_binary(ChangeFun(unicode:characters_to_list(CharactersData))).

%% 类似string:strip/1，去除多字节字符串两边冗余空格
-spec strip(CharactersData :: iodata()) -> binary().
strip(CharactersData) ->
    unicode:characters_to_binary(string:strip(unicode:characters_to_list(CharactersData))).

%% ---------------------------------------------------------------------------
%% @doc 条件区域取值（返回元组中指定一元素）
-spec term_area_value(L, N1, N2, ConValue) -> Res when
    L           :: [tuple()],           %% 条件区域元组列表
    N1          :: integer(),           %% 元组中条件字段的位置
    N2          :: integer(),           %% 目标值在元组中的位置
    ConValue    :: integer(),           %% 过滤条件值
    Res         :: term().              %% 返回目标值
%% ---------------------------------------------------------------------------
%% Usage: 查找当前服务器等级下(40级)，某物品的基准价格
%% term_area_value([{0,10}{30,100},{50,300}], 1, 2, 40)
term_area_value(L, N1, N2, ConValue) ->
    LSort = lists:keysort(N1, L),
    LSortR = lists:reverse(LSort),
    case term_area_value_helper(LSortR, N1, N2, ConValue) of
        [] -> 0;
        V -> V
    end.

term_area_value_helper([], _N1, _N2, _ConValue) -> [];
term_area_value_helper([Tuple|T], N1, N2, ConValue) ->
    Value1 = element(N1, Tuple),
    if
        ConValue>=Value1 orelse T == [] -> element(N2, Tuple);
        true -> term_area_value_helper(T, N1, N2, ConValue)
    end.

%% ---------------------------------------------------------------------------
%% @doc 条件区域取值（返回元组）
-spec term_area_elem(L, N, ConValue) -> Res when
    L           :: [tuple()],           %% 条件区域元组列表
    N           :: integer(),           %% 元组中条件字段的位置
    ConValue    :: integer(),           %% 过滤条件值
    Res         :: term().              %% 返回元组
%% ---------------------------------------------------------------------------

term_area_elem(L, N, ConValue) ->
    LSort = lists:keysort(N, L),
    LSortR = lists:reverse(LSort),
    case term_area_elem_helper(LSortR, N, ConValue) of
        [] -> 0;
        V -> V
    end.

term_area_elem_helper([], _N, _ConValue) -> [];
term_area_elem_helper([Tuple|T], N, ConValue) ->
    Value1 = element(N, Tuple),
    if
        ConValue>=Value1 orelse T == [] -> Tuple;
        true -> term_area_elem_helper(T, N, ConValue)
    end.

%% ---------------------------------------------------------------------------
%% @doc 累加tuplelist某一项的值
-spec additive_tuplelist_elem(TupleList, N1, N2, Key, Num) -> Res when
    TupleList   :: [tuple()],           %% 元组列表
    N1          :: integer(),           %% 元组中主键的位置
    N2          :: integer(),           %% 目标项在元组中的位置
    Key         :: term(),              %% 主键
    Num         :: integer(),           %% 增加的值
    Res         :: term().              %% 返回更新后元组列表
%% ---------------------------------------------------------------------------
%% Usage:
%% 给主键为1002的元组第二项增加20
%% TupleList = [{1001,10},{1002,1}]
%% additive_tuplelist_elem(TupleList, 1, 2, 1002, 20)
additive_tuplelist_elem(TupleList, N1, N2, Key, Num) ->
    NewTuple = case lists:keyfind(Key, N1, TupleList) of
        false -> {Key, Num};
        Tuple ->
            Value = element(N2, Tuple),
            NewNum = Num + Value,
            setelement(N2, Tuple, NewNum)
    end,
    lists:keystore(Key, N1, TupleList, NewTuple).

%% @doc 累减tuplelist某一项的值
minus_tuplelist_elem(TupleList, N1, N2, Key, Num) ->
    case lists:keyfind(Key, N1, TupleList) of
        false -> TupleList;
        Tuple ->
            Value = element(N2, Tuple),
            NewNum = max(Value - Num, 0),
            NewTuple = setelement(N2, Tuple, NewNum),
            lists:keystore(Key, N1, TupleList, NewTuple)
    end.

%% 检查函数
check_list([], _M) -> true;
check_list([{F, A}|L], M) ->
    case apply(M, F, A) of
        true -> check_list(L);
        {false, R} -> {false, R}
    end.

check_list([]) -> true;
check_list([F|L]) when is_function(F) ->
    case F() of
        true -> check_list(L);
        Result -> Result
    end;
check_list([{F, ErrCode}|L]) ->
    case F() of
        true -> check_list(L);
        _ -> {false, ErrCode}
    end;
check_list([{M, F, A}|L]) ->
    case apply(M, F, A) of
        true -> check_list(L);
        {false, ErrCode} -> {false, ErrCode}
    end;
check_list([{M, F, A, ErrCode}|L]) ->
    case apply(M, F, A) of
        true -> check_list(L);
        _ -> {false, ErrCode}
    end.

%% 连接字符
link_list(List) ->
    link_list(List, ",").

%% 连接字符
%% @param SpiltChar string()
link_list(List, SpiltChar) ->
    lists:concat(link_list(List, SpiltChar, [])).

%% 连接字符
link_list([], _SpiltChar, NewList) ->
    NewList;
link_list([L | []], _SpiltChar, NewList) ->
    NewList ++ [uio:thing_to_string(L)];
link_list([L | T], SpiltChar, NewList) ->
    link_list(T, SpiltChar, NewList ++ [uio:thing_to_string(L)] ++ [SpiltChar]).

%% 多个字符拼接成一个字符串
link_list_extra(List) ->
    lists:concat(link_list_extra(List, [])).

%% 连接字符
link_list_extra([], NewList) ->
    NewList;
link_list_extra([L | []], NewList) ->
    NewList ++ [uio:thing_to_string(L)];
link_list_extra([L | T], NewList) ->  %% 显式声明、为utf8
    link_list_extra(T, NewList ++ [uio:thing_to_string(L)] ++ [uio:thing_to_string(<<"、"/utf8>>)]).

%% 转化错误码
%% ErrorCode : is_integer()|{is_integer(), is_list()}
%% @return {integer(), string()}
parse_error_code(ErrorCode) ->
    case ErrorCode of
        ErrorCodeList when is_list(ErrorCodeList) -> ErrorCodeInt = ?FAIL, ErrorCodeArgs = [];
        ErrorCodeInt when is_integer(ErrorCodeInt) -> ErrorCodeArgs = [];
        {ErrorCodeInt, ErrorCodeArgs} -> skip
    end,
    {ErrorCodeInt, link_list(ErrorCodeArgs)}.

%% 组装错误码字符串
make_error_code_msg(ErrorCode) ->
    case is_list(ErrorCode) of
        true -> ErrorCode;
        false ->
            {ErrorCodeInt, ErrorCodeArgs} = make_error_code_msg_help(ErrorCode),
            case data_errorcode_msg:get(ErrorCodeInt) of
                [] -> "";
                #errorcode_msg{about = About} -> uio:format(About, ErrorCodeArgs)
            end
    end.

%% 转化错误码
make_error_code_msg_help(ErrorCode) ->
    case ErrorCode of
        ErrorCodeList when is_list(ErrorCodeList) -> ErrorCodeInt = ?FAIL, ErrorCodeArgs = [];
        ErrorCodeInt when is_integer(ErrorCodeInt) -> ErrorCodeArgs = [];
        {ErrorCodeInt, ErrorCodeArgs} -> skip
    end,
    {ErrorCodeInt, ErrorCodeArgs}.

%% 组装物品Str
make_goods_str(GoodsList) -> make_goods_str(GoodsList, "").

make_goods_str([], Str) -> Str;
make_goods_str([{_Type, GoodsTypeId, Num}|L], Str) ->
    case data_goods_type:get(GoodsTypeId) of
        [] -> GoodsName = "";
        #ets_goods_type{goods_name = GoodsName} -> ok
    end,
    case L == [] of
        true -> NewStr = lists:concat([Str, GoodsName, "*", Num]);
        false -> NewStr = lists:concat([Str, GoodsName, "*", Num, "，"])
    end,
    make_goods_str(L, NewStr);
make_goods_str([_|L], Str) ->
    make_goods_str(L, Str).

%% 语言拼接物品   名字*数量、名字*数量
pack_tv_goods(List) ->
    ResList
    = lists:foldl(
        fun({?TYPE_GOODS, GoodsId, Num}, ResList) -> util:additive_tuplelist_elem(ResList, 1, 2, GoodsId, Num);
            ({?TYPE_BIND_GOODS, GoodsId, Num}, ResList) -> util:additive_tuplelist_elem(ResList, 1, 2, GoodsId, Num);
            ({?TYPE_COIN, _Id, Num}, ResList) ->  util:additive_tuplelist_elem(ResList, 1, 2, ?GOODS_ID_COIN, Num);
            ({?TYPE_GOLD, _Id, Num}, ResList) -> util:additive_tuplelist_elem(ResList, 1, 2, ?GOODS_ID_GOLD, Num);
            ({?TYPE_BGOLD, _Id, Num}, ResList) -> util:additive_tuplelist_elem(ResList, 1, 2, ?GOODS_ID_BGOLD, Num);
            ({?TYPE_FGOLD, _Id, Num}, ResList) -> util:additive_tuplelist_elem(ResList, 1, 2, ?GOODS_ID_FGOLD, Num);
            ({?TYPE_EXP, _Id, Num}, ResList) -> util:additive_tuplelist_elem(ResList, 1, 2, ?GOODS_ID_EXP, Num);
            ({?TYPE_GFUNDS, _Id, Num}, ResList) -> util:additive_tuplelist_elem(ResList, 1, 2, ?GOODS_ID_GFUNDS, Num);
            ({?TYPE_GFAME, _Id, Num}, ResList) -> util:additive_tuplelist_elem(ResList, 1, 2, ?GOODS_ID_GFAME, Num);
            (_, ResList) ->
                ResList
        end, [], List),
    pack_tv_goods_helper(ResList, []).

pack_tv_goods_helper([], List) -> List;
pack_tv_goods_helper([{GoodsId, Num}|T], List) ->
    NewList = case List of
        [] -> lists:concat(["<a@goods@", GoodsId, ">", "</a>", "*", Num]);
        _  -> lists:concat([List, "、", "<a@goods@", GoodsId, ">", "</a>", "*", Num])
    end,
    pack_tv_goods_helper(T, NewList).

%% 条件验证
check_lists(_Mod, []) -> true;
check_lists(Mod, [H|L]) ->
    case Mod:check(H) of
        {false, Reason} -> {false, Reason};
        {ok, Reason} -> {ok, Reason};
        true ->
            check_lists(Mod, L)
    end.

%% 获取记录的key的下标
index_of_record(K, RecordInfo)->
    index_of_record(K, RecordInfo, 1).

index_of_record(_K, [], _N) -> 0;
index_of_record(K, [K|_RecordInfo], N) -> N;
index_of_record(K, [_K|RecordInfo], N) ->
    index_of_record(K, RecordInfo, N+1).

%% 游戏服名字
get_server_name()->
    case ets:lookup(?SERVER_ENV, server_name) of
        [] -> "one server";
        [S] -> S#server_env.value
    end.

%% PS: 经测试，record_plus/2, record_minus/2 与直接全部字段匹配出来进行加减操作对比，
%%  运行时间基本一致(#attr{}测算2亿次，无明显差别)，
%%  内存消耗方面直接匹配出来有明显优势（setelement/3会造成内存拷贝），约为setelement方式的1/4占用
%%  不过由于record的加减操作，内存消耗本身并不多，可以直接忽略，没有必要全部字段匹配出来进行操作
%%  通常直接调用record_plus及record_minus即可

%% 相同record对应字段相加，必须调用者保证所有字段都是数值型，否则会抛出异常
-spec record_plus(RecordA, RecordB) -> NewRecord when
    RecordA :: tuple(), RecordB :: tuple(), NewRecord :: tuple().
record_plus(RecordA, RecordB) ->
    Size = assert_same_record(RecordA, RecordB),
    record_plus(RecordA, RecordB, 2, Size, true).

%% 相同record对应字段相减，必须调用者保证所有字段都是数值型，否则会抛出异常
-spec record_minus(RecordA, RecordB) -> NewRecord when
    RecordA :: tuple(), RecordB :: tuple(), NewRecord :: tuple().
record_minus(RecordA, RecordB) ->
    Size = assert_same_record(RecordA, RecordB),
    record_plus(RecordA, RecordB, 2, Size, false).

%% 断言相同类型的record，如果是返回record的大小
assert_same_record(RecordA, RecordB) ->
    case element(1, RecordA) == element(1, RecordB) of
        true  ->
            Size = size(RecordA),
            case Size =:= size(RecordB) of
                true  ->
                    Size;
                false ->
                    throw(error)
            end;
        false ->
            throw(error)
    end.

record_plus(RecordA, RecordB, Index, Size, IsPlus) when Index =< Size ->
    ValueB = element(Index, RecordB),
    if
        ValueB == 0 ->
            record_plus(RecordA, RecordB, Index + 1, Size, IsPlus);
        true ->
            ValueA = element(Index, RecordA),
            NewRecordA =
            case IsPlus of
                true  -> setelement(Index, RecordA, ValueA + ValueB);
                false -> setelement(Index, RecordA, ValueA - ValueB)
            end,
            record_plus(NewRecordA, RecordB, Index + 1, Size, IsPlus)
    end;
record_plus(RecordA, _, _Index, _Size, _IsPlus) ->
    RecordA.

%% IP元组/字符串转二进制字符串
ip2bin({A, B, C, D}) ->
    list_to_binary([
            integer_to_binary(A), <<".">>, integer_to_binary(B), <<".">>,
            integer_to_binary(C), <<".">>, integer_to_binary(D)
        ]);
ip2bin({A, B, C, D, E, F, G, H}) ->
    list_to_binary([
            integer_to_binary(A, 16), ":", integer_to_binary(B, 16), ":",
            integer_to_binary(C, 16), ":", integer_to_binary(D, 16), ":",
            integer_to_binary(E, 16), ":", integer_to_binary(F, 16), ":",
            integer_to_binary(G, 16), ":", integer_to_binary(H, 16)
        ]);
ip2bin(IpBin) when is_binary(IpBin) ->
    IpBin;
ip2bin(IpStr) when is_list(IpStr) ->
    list_to_binary(IpStr).


%% 打印时显示当前时间戳：2016-8-4 19:53:12
debug_time_info() ->
    {{Y, M, D},{H, I, S}} = erlang:localtime(),
    MsgList = [
        integer_to_list(Y), "-", integer_to_list(M), "-", integer_to_list(D), " "
        ,integer_to_list(H), ":", integer_to_list(I), ":", integer_to_list(S)
    ],
    io_lib:format("## ~ts", [list_to_binary(MsgList)]).

%%快照在线玩家部分数据
snap(RoleID,Label)->
    Pid = misc:get_player_process(RoleID),
    Status = sys:get_state(Pid),
    Fields = record_info(fields,player_status),
    case ulists:index_of(Label, Fields) of
        0->
            ?ERR("print player all:~n~p~n",[Status]);
        Index->
            Data = erlang:element(Index+1,Status),
            io:format("~p data ~n:~p~n",[Label,Data])
    end.
