%%% -*- erlang -*-
%%%
  This file is part of hackney released under the Apache 2 license .
%%% See the NOTICE for more information.
%%%
-module(hackney_util).

-export([filter_options/3]).
-export([set_option_default/3]).
-export([require/1]).
-export([maybe_apply_defaults/2]).
-export([is_ipv6/1]).
-export([privdir/0]).
-export([mod_metrics/0]).
-export([to_atom/1]).

%% random compatibility
-export([uniform/1]).

-export([merge_opts/2]).

-include("hackney.hrl").

%% @doc filter a proplists and only keep allowed keys
-spec filter_options([{atom(), any()} | {raw, any(), any(), any()}],
	[atom()], Acc) -> Acc when Acc :: [any()].
filter_options([], _, Acc) ->
	Acc;
filter_options([Opt = {Key, _}|Tail], AllowedKeys, Acc) ->
	case lists:member(Key, AllowedKeys) of
		true -> filter_options(Tail, AllowedKeys, [Opt|Acc]);
		false -> filter_options(Tail, AllowedKeys, Acc)
	end;
filter_options([Opt = {raw, _, _, _}|Tail], AllowedKeys, Acc) ->
	case lists:member(raw, AllowedKeys) of
		true -> filter_options(Tail, AllowedKeys, [Opt|Acc]);
		false -> filter_options(Tail, AllowedKeys, Acc)
	end;
filter_options([Opt|Tail], AllowedKeys, Acc) when is_atom(Opt) ->
	case lists:member(Opt, AllowedKeys) of
		true -> filter_options(Tail, AllowedKeys, [Opt|Acc]);
		false -> filter_options(Tail, AllowedKeys, Acc)
	end.


%% @doc set the default options in a proplists if not defined
-spec set_option_default(Opts, atom(), any())
	-> Opts when Opts :: [{atom(), any()}].
set_option_default(Opts, Key, Value) ->
	case lists:keymember(Key, 1, Opts) of
		true -> Opts;
		false -> [{Key, Value}|Opts]
	end.

%% @doc Start the given applications if they were not already started.
-spec require(list(module())) -> ok.
require([]) ->
	ok;
require([App|Rest]) ->
	case application:start(App) of
		ok -> ok;
		{error, {already_started, App}} -> ok
	end,
	require(Rest).

maybe_apply_defaults([], Options) ->
    Options;
maybe_apply_defaults([OptName | Rest], Options) ->
    case proplists:is_defined(OptName, Options) of
        true ->
            maybe_apply_defaults(Rest, Options);
        false ->
            {ok, Default} = application:get_env(hackney, OptName),
            maybe_apply_defaults(Rest, [{OptName, Default} | Options])
    end.

is_ipv6(Host) ->
    case inet_parse:address(Host) of
        {ok, {_, _, _, _, _, _, _, _}} ->
            true;
        {ok, {_, _, _, _}} ->
            false;
        _ ->
            case inet:getaddr(Host, inet) of
                {ok, _} ->
                    false;
                _ ->
                    case inet:getaddr(Host, inet6) of
                        {ok, _} ->
                            true;
                        _ ->
                            false
                    end
            end
    end.

privdir() ->
    case code:priv_dir(hackney) of
        {error, _} ->
            %% try to get relative priv dir. useful for tests.
            EbinDir = filename:dirname(code:which(?MODULE)),
            AppPath = filename:dirname(EbinDir),
            filename:join(AppPath, "priv");
        Dir -> Dir
    end.


mod_metrics() ->
    case application:get_env(hackney, mod_metrics) of
        {ok, folsom} -> metrics_folsom;
        {ok, exometer} -> metrics_exometer;
        {ok, dummy} -> metrics_dummy;
        {ok, Mod} -> Mod;
        _ -> metrics_dummy
    end.


to_atom(V) when is_list(V) ->
    try
        list_to_existing_atom(V)
    catch
        _:_ -> list_to_atom(V)
    end;
to_atom(V) when is_binary(V) ->
    to_atom(binary_to_list(V));
to_atom(V) when is_atom(V) ->
    V.

%% random compatibility

uniform(N) ->
  case have_rand() of
    true  -> rand:uniform(N);
    false -> (fun random:uniform/1)(N)
  end.

have_rand() -> (code:which(rand) /= non_existing).


merge_opts([], Options) -> Options;
merge_opts([Opt = {K, _}| Rest], Options) ->
	case lists:keymember(K, 1, Options) of
		true -> merge_opts(Rest, Options);
		false -> merge_opts(Rest, [Opt | Options])
	end;
merge_opts([Opt={raw, _, _, _} | Rest], Options) ->
	merge_opts(Rest, [Opt | Options]);
merge_opts([K | Rest], Options) when is_atom(K) ->
	case lists:member(K, Options) of
		true -> merge_opts(Rest, Options);
		false -> merge_opts(Rest, [K | Options])
	end;
merge_opts([_ | Rest], Options) ->
	merge_opts(Rest, Options).


 -*- erlang -*-

 See the NOTICE for more information.

 random compatibility
 @doc filter a proplists and only keep allowed keys
 @doc set the default options in a proplists if not defined
 @doc Start the given applications if they were not already started.
 try to get relative priv dir. useful for tests.
 random compatibility
  This file is part of hackney released under the Apache 2 license .
-module(hackney_util).

-export([filter_options/3]).
-export([set_option_default/3]).
-export([require/1]).
-export([maybe_apply_defaults/2]).
-export([is_ipv6/1]).
-export([privdir/0]).
-export([mod_metrics/0]).
-export([to_atom/1]).

-export([uniform/1]).

-export([merge_opts/2]).

-include("hackney.hrl").

-spec filter_options([{atom(), any()} | {raw, any(), any(), any()}],
	[atom()], Acc) -> Acc when Acc :: [any()].
filter_options([], _, Acc) ->
	Acc;
filter_options([Opt = {Key, _}|Tail], AllowedKeys, Acc) ->
	case lists:member(Key, AllowedKeys) of
		true -> filter_options(Tail, AllowedKeys, [Opt|Acc]);
		false -> filter_options(Tail, AllowedKeys, Acc)
	end;
filter_options([Opt = {raw, _, _, _}|Tail], AllowedKeys, Acc) ->
	case lists:member(raw, AllowedKeys) of
		true -> filter_options(Tail, AllowedKeys, [Opt|Acc]);
		false -> filter_options(Tail, AllowedKeys, Acc)
	end;
filter_options([Opt|Tail], AllowedKeys, Acc) when is_atom(Opt) ->
	case lists:member(Opt, AllowedKeys) of
		true -> filter_options(Tail, AllowedKeys, [Opt|Acc]);
		false -> filter_options(Tail, AllowedKeys, Acc)
	end.


-spec set_option_default(Opts, atom(), any())
	-> Opts when Opts :: [{atom(), any()}].
set_option_default(Opts, Key, Value) ->
	case lists:keymember(Key, 1, Opts) of
		true -> Opts;
		false -> [{Key, Value}|Opts]
	end.

-spec require(list(module())) -> ok.
require([]) ->
	ok;
require([App|Rest]) ->
	case application:start(App) of
		ok -> ok;
		{error, {already_started, App}} -> ok
	end,
	require(Rest).

maybe_apply_defaults([], Options) ->
    Options;
maybe_apply_defaults([OptName | Rest], Options) ->
    case proplists:is_defined(OptName, Options) of
        true ->
            maybe_apply_defaults(Rest, Options);
        false ->
            {ok, Default} = application:get_env(hackney, OptName),
            maybe_apply_defaults(Rest, [{OptName, Default} | Options])
    end.

is_ipv6(Host) ->
    case inet_parse:address(Host) of
        {ok, {_, _, _, _, _, _, _, _}} ->
            true;
        {ok, {_, _, _, _}} ->
            false;
        _ ->
            case inet:getaddr(Host, inet) of
                {ok, _} ->
                    false;
                _ ->
                    case inet:getaddr(Host, inet6) of
                        {ok, _} ->
                            true;
                        _ ->
                            false
                    end
            end
    end.

privdir() ->
    case code:priv_dir(hackney) of
        {error, _} ->
            EbinDir = filename:dirname(code:which(?MODULE)),
            AppPath = filename:dirname(EbinDir),
            filename:join(AppPath, "priv");
        Dir -> Dir
    end.


mod_metrics() ->
    case application:get_env(hackney, mod_metrics) of
        {ok, folsom} -> metrics_folsom;
        {ok, exometer} -> metrics_exometer;
        {ok, dummy} -> metrics_dummy;
        {ok, Mod} -> Mod;
        _ -> metrics_dummy
    end.


to_atom(V) when is_list(V) ->
    try
        list_to_existing_atom(V)
    catch
        _:_ -> list_to_atom(V)
    end;
to_atom(V) when is_binary(V) ->
    to_atom(binary_to_list(V));
to_atom(V) when is_atom(V) ->
    V.


uniform(N) ->
  case have_rand() of
    true  -> rand:uniform(N);
    false -> (fun random:uniform/1)(N)
  end.

have_rand() -> (code:which(rand) /= non_existing).


merge_opts([], Options) -> Options;
merge_opts([Opt = {K, _}| Rest], Options) ->
	case lists:keymember(K, 1, Options) of
		true -> merge_opts(Rest, Options);
		false -> merge_opts(Rest, [Opt | Options])
	end;
merge_opts([Opt={raw, _, _, _} | Rest], Options) ->
	merge_opts(Rest, [Opt | Options]);
merge_opts([K | Rest], Options) when is_atom(K) ->
	case lists:member(K, Options) of
		true -> merge_opts(Rest, Options);
		false -> merge_opts(Rest, [K | Options])
	end;
merge_opts([_ | Rest], Options) ->
	merge_opts(Rest, Options).

