src/swirl_utils.erl

-module(swirl_utils).
-include("swirl.hrl").

%% public
-export([
    lookup/2,
    lookup/3,
    new_timer/2,
    new_timer/3,
    proplist_to_record/2,
    record_to_proplist/1,
    safe_dict_fetch/2,
    safe_ets_delete/1,
    safe_ets_increment/3,
    safe_ets_lookup_element/2,
    tab2list/1,
    unix_tstamp_ms/0,
    update_op/1,
    uuid/0
]).

%% public
-spec lookup(term(), [{term(), term()}]) ->
    term().

lookup(Key, List) ->
    lookup(Key, List, undefined).

-spec lookup(term(), [{term(), term()}], term()) ->
    term().

lookup(Key, List, Default) ->
    case lists:keyfind(Key, 1, List) of
        false -> Default;
        {_, Value} -> Value
    end.

-spec maybe_tuple_to_list(tuple() | list()) ->
    list().

maybe_tuple_to_list(Tuple) when is_tuple(Tuple) ->
    tuple_to_list(Tuple);
maybe_tuple_to_list(List) when is_list(List) ->
    List.

-spec new_timer(pos_integer(), term()) ->
    erlang:reference().

new_timer(Time, Msg) ->
    new_timer(Time, Msg, false).

-spec new_timer(pos_integer(), term(), boolean()) ->
    erlang:reference().

new_timer(Time, Msg, true) ->
    Delta = unix_tstamp_ms() rem Time,
    erlang:send_after(Time - Delta, self(), Msg);
new_timer(Time, Msg, false) ->
    erlang:send_after(Time, self(), Msg).

-spec proplist_to_record([{atom(), term()}], atom()) ->
    tuple().

proplist_to_record(Proplist, Record) ->
    Fields = [lookup(Field, Proplist) || Field <- record_info(Record)],
    list_to_tuple([Record | Fields]).

record_to_proplist(#flow {} = Flow) ->
    lists:zip(record_info(flow), tl(tuple_to_list(Flow))).

safe_dict_fetch(Key, Dict) ->
    try dict:fetch(Key, Dict)
    catch
        error:badarg ->
            undefined
    end.

safe_ets_delete(TableId) ->
    try ets:delete(TableId)
    catch
        error:badarg ->
            ok
    end.

safe_ets_increment(TableId, Key, Counters) ->
    UpdateOp = update_op(Counters),
    try ets:update_counter(TableId, Key, UpdateOp)
    catch
        error:badarg ->
            safe_ets_insert(TableId, Key, Counters)
    end.

safe_ets_insert(TableId, Key, Counters) ->
    try
        New = list_to_tuple([Key] ++ maybe_tuple_to_list(Counters)),
        ets:insert(TableId, New)
    catch
        error:badarg ->
            ok
    end.

safe_ets_lookup_element(TableId, Key) ->
    try ets:lookup_element(TableId, Key, 2)
    catch
        error:badarg ->
            undefined
    end.

tab2list(Tid) ->
    lists:append(match_all(ets:match_object(Tid, '_', 500))).

unix_tstamp_ms() ->
    {Mega, Sec, Micro} = os:timestamp(),
    (Mega * 1000000000 + Sec * 1000) + trunc(Micro / 1000).

update_op(Counters) when is_tuple(Counters) ->
    update_op(tuple_to_list(Counters), 2);
update_op(Counters) when is_list(Counters) ->
    update_op(Counters, 2).

uuid() ->
    {Uuid, _UuidState} = uuid:get_v1(uuid:new(self(), os)),
    Uuid.

%% private
match_all('$end_of_table') ->
    [];
match_all({Match, Continuation}) ->
    [Match | match_all(ets:match_object(Continuation))].

record_info(flow) ->
    record_info(fields, flow).

update_op([], _Pos) ->
    [];
update_op([Counter | T], Pos) ->
    [{Pos, Counter} | update_op(T, Pos + 1)].