%%%-------------------------------------------------------------------
%%% @author licheng5
%%% @copyright (C) 2020, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 11. 12月 2020 上午10:57
%%%-------------------------------------------------------------------
-module(iot_util).
-author("licheng5").

%% API
-export([timestamp/0, number_format/2, current_time/0, timestamp_of_seconds/0, float_to_binary/2, int_format/2]).
-export([step/3, rand_bytes/1, uuid/0, md5/1, parse_mapper/1]).
-export([json_data/1, json_error/2, is_json/1]).
-export([queue_limited_in/3, assert_call/2, assert/2]).
-export([sha256/1]).

-spec is_json(Json :: term()) -> boolean().
is_json(Json) when is_binary(Json) ->
    case catch json:decode(Json) of
        Result when is_list(Result) orelse is_map(Result) ->
            true;
        _ ->
            false
    end;
is_json(_Json) ->
    false.

%% 时间，精确到毫秒
timestamp() ->
    {Mega, Seconds, Micro} = os:timestamp(),
    (Mega * 1000000 + Seconds) * 1000 + Micro div 1000.

current_time() ->
    {Mega, Seconds, _Micro} = os:timestamp(),
    Mega * 1000000 + Seconds.

timestamp_of_seconds() ->
    {Mega, Seconds, _Micro} = os:timestamp(),
    Mega * 1000000 + Seconds.

number_format(Num, _Decimals) when is_integer(Num) ->
    Num;
number_format(Float, Decimals) when is_float(Float) ->
    list_to_float(float_to_list(Float, [{decimals, Decimals}, compact])).

int_format(Num, Len) when is_integer(Num), Len > 0 ->
    S = integer_to_list(Num),
    case length(S) > Len of
        true ->
            list_to_integer(lists:sublist(S, 1, Len));
        false ->
            Num
    end.

step(Start, End, Step) when is_integer(Start), is_integer(End), is_integer(Step), Start < End, Step > 0 ->
    step(Start, End, Step, []).
step(Start, End, Step, Acc) when Start < End ->
    step(Start + Step, End, Step, [{Start, min(Start + Step, End)} | Acc]);
step(_, _, _, Acc) ->
    lists:reverse(Acc).

json_data(Data) ->
    iolist_to_binary(json:encode(#{
        <<"result">> => Data
    })).

json_error(ErrCode, ErrMessage) when is_integer(ErrCode) ->
    iolist_to_binary(json:encode(#{
        <<"error">> => #{
            <<"code">> => ErrCode,
            <<"message">> => ErrMessage
        }
    })).

uuid() ->
    rand_bytes(16).

-spec rand_bytes(Size :: integer()) -> string().
rand_bytes(Size) when is_integer(Size), Size > 0 ->
    Size1 = erlang:ceil(Size / 2),
    Bytes = crypto:strong_rand_bytes(Size1),
    S = lists:flatten([integer_to_list(E, 16) || <<E:4>> <= Bytes]),
    lists:sublist(string:to_lower(S), 1, Size).

queue_limited_in(Item, Q, Num) when is_integer(Num) ->
    case queue:len(Q) >= Num of
        true ->
            Q1 = queue:drop(Q),
            queue:in(Item, Q1);
        false ->
            queue:in(Item, Q)
    end.

assert_call(true, Fun) ->
    Fun();
assert_call(false, _) ->
    ok.

-spec sha256(Str :: string() | binary()) -> binary().
sha256(Str) when is_list(Str) ->
    sha256(unicode:characters_to_binary(Str));
sha256(Bin) when is_binary(Bin) ->
    HashBin = crypto:hash(sha256, Bin),
    HexStr = lists:flatten([io_lib:format("~2.16.0B", [B]) || B <- binary:bin_to_list(HashBin)]),
    list_to_binary(string:lowercase(HexStr)).

-spec md5(Str :: binary()) -> binary().
md5(Str) when is_binary(Str) ->
    list_to_binary(lists:flatten([hex(X) || <<X:4>> <= erlang:md5(Str)])).

hex(N) when N < 10 ->
    $0 + N;
hex(N) ->
    $a + (N - 10).

%% 转换映射器
-spec parse_mapper(Mapper :: binary() | string()) -> error | {ok, F :: fun((binary(), any()) -> any())}.
parse_mapper(_Mapper) ->
    %% Dynamic Erlang eval is intentionally disabled in runtime code.
    error.

-spec float_to_binary(Num :: number(), integer()) -> binary().
float_to_binary(V, _) when is_integer(V) ->
    integer_to_binary(V);
float_to_binary(V, Decimals) when is_float(V), is_integer(Decimals) ->
    S = float_to_list(V, [{decimals, Decimals}, compact]),
    list_to_binary(S).

assert(true, _) ->
    ok;
assert(false, F) when is_function(F) ->
    F();
assert(false, Msg) ->
    throw(Msg).
