%%%
%%% Copyright 2011, Boundary
%%%
%%% Licensed under the Apache License, Version 2.0 (the "License");
%%% you may not use this file except in compliance with the License.
%%% You may obtain a copy of the License at
%%%
%%%     http://www.apache.org/licenses/LICENSE-2.0
%%%
%%% Unless required by applicable law or agreed to in writing, software
%%% distributed under the License is distributed on an "AS IS" BASIS,
%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%%% See the License for the specific language governing permissions and
%%% limitations under the License.
%%%


%%%-------------------------------------------------------------------
%%% File:      cloudi_x_folsom_utils.erl
%%% @author    joe williams <j@boundary.com>
%%% @doc
%%% various util functions
%%% @end
%%%------------------------------------------------------------------
-module(cloudi_x_folsom_utils).

-export([
         to_atom/1,
         convert_tags/1,
         now_epoch/0,
         now_epoch/1,
         now_epoch_micro/0,
         timestamp/0,
         get_ets_size/1,
         update_counter/3,
         update_counter_no_exceptions/3,
         rand_uniform/1
        ]).

to_atom(Binary) when is_binary(Binary) ->
    list_to_atom(binary_to_list(Binary));
to_atom(List) when is_list(List) ->
    list_to_atom(List).

convert_tags(Tags) ->
    [to_atom(Tag) || Tag <- Tags].

now_epoch() ->
    now_epoch(os:timestamp()).

now_epoch({Mega, Sec, _}) ->
    (Mega * 1000000 + Sec).

now_epoch_micro() ->
    {Mega, Sec, Micro} = os:timestamp(),
    (Mega * 1000000 + Sec) * 1000000 + Micro.

%% useful because you can't meck os:timestamp for some reason
timestamp() ->
    os:timestamp().

get_ets_size(Tab) ->
    ets:info(Tab, size).

-ifdef(use_update_counter_4).

%% ets:update_counter/4 was introduced in OTP 18. When < 18 is no
%% longer supported all this code can be cleaned up along with
%% cloudi_x_folsom_metrics_spiral:new/2
update_counter(Tid, Key, Value) when is_integer(Value) ->
    ets:update_counter(Tid, Key, Value, {Key, 0}).
update_counter_no_exceptions(Tid, Key, Value) ->
    update_counter(Tid, Key, Value).

-else.

%% @doc
%% Same as {@link ets:update_counter/3} but inserts `{Key, Value}' if object
%% is missing in the table.
update_counter(Tid, Key, Value) when is_integer(Value) ->
    %% try to update the counter, will badarg if it doesn't exist
    try ets:update_counter(Tid, Key, Value) of
        Res ->
            Res
    catch
        error:badarg ->
            %% row didn't exist, create it
            %% use insert_new to avoid races
            case ets:insert_new(Tid, {Key, Value}) of
                true ->
                    Value;
                false ->
                    %% someone beat us to it
                    ets:update_counter(Tid, Key, Value)
            end
    end.

%% @doc
%% Same as {@link ets:update_counter/3} but inserts `{Key, Value}' if object
%% is missing in the table, avoiding exceptions by reading first.
%% Won't be required after https://github.com/erlang/otp/pull/362
update_counter_no_exceptions(Tid, Key, Value) when is_integer(Value) ->
    %% Read counter first to avoid an exception
    case ets:lookup(Tid, Key) of
        [] ->
            %% row didn't exist, create it
            %% use insert_new to avoid races
            case ets:insert_new(Tid, {Key, Value}) of
                true ->
                    Value;
                false ->
                    %% someone beat us to it
                    ets:update_counter(Tid, Key, Value)
            end;
        _ ->
            ets:update_counter(Tid, Key, Value)
    end.

-endif.


-ifdef(use_rand).

rand_uniform(N) ->
    rand:uniform(N).

-else.

rand_uniform(N) ->
    %% ensure seed is initialized
    %% simluating new `rand' module's behaviour
    case get(random_seed) of
        undefined ->
            random:seed(os:timestamp());
        {_, _, _} ->
            ok
    end,
    random:uniform(N).

-endif.
