%% ====================================================================
%% @author Gavin M. Roy <gavinmroy@gmail.com>
%% @copyright 2016
%% @doc urilib is a RFC-3986 URI Library for Erlang
%%      https://github.com/gmr/urilib
%% @end
%% ====================================================================
-module(rabbitmq_aws_urilib).

-export([build/1,
         build_query_string/1,
         parse/1,
         parse_userinfo/1,
         parse_userinfo_result/1
        ]).

%% Export all for unit tests
-ifdef(TEST).
-compile(export_all).
-endif.

-include("rabbitmq_aws.hrl").

-spec build(#uri{}) -> string().
%% @doc Build a URI string
%% @end
build(URI) ->
  {UserInfo, Host, Port} = URI#uri.authority,
  UriMap = #{
    scheme => to_list(URI#uri.scheme),
    host => Host
  },
  UriMap1 = case UserInfo of
    undefined         -> UriMap;
    {User, undefined} -> maps:put(userinfo, User, UriMap);
    {User, Password}  -> maps:put(userinfo, User ++ ":" ++ Password, UriMap)
  end,
  UriMap2 = case Port of
    undefined -> UriMap1;
    Value1    -> maps:put(port, Value1, UriMap1)
  end,
  UriMap3 = case URI#uri.path of
    undefined -> maps:put(path, "", UriMap2);
    Value2    ->
      PrefixedPath = case string:slice(Value2, 0, 1) of
        "/" -> Value2;
        _   -> "/" ++ Value2
      end,
      maps:put(path, PrefixedPath, UriMap2)
  end,
  UriMap4 = case URI#uri.query of
    undefined -> UriMap3;
    ""        -> UriMap3;
    Value3    -> maps:put(query, build_query_string(Value3), UriMap3)
  end,
  UriMap5 = case URI#uri.fragment of
    undefined -> UriMap4;
    Value4    -> maps:put(fragment, Value4, UriMap4)
  end,
  uri_string:recompose(UriMap5).

-spec parse(string()) -> #uri{} | {error, any()}.
%% @doc Parse a URI string returning a record with the parsed results
%% @end
parse(Value) ->
  UriMap = uri_string:parse(Value),
  Scheme = maps:get(scheme, UriMap, "https"),
  Host = maps:get(host, UriMap),

  DefaultPort = case Scheme of
    "http"  -> 80;
    "https" -> 443;
    _       -> undefined
  end,
  Port = maps:get(port, UriMap, DefaultPort),
  UserInfo = parse_userinfo(maps:get(userinfo, UriMap, undefined)),
  Path = maps:get(path, UriMap),
  Query = maps:get(query, UriMap, ""),
  #uri{scheme = Scheme,
       authority = {parse_userinfo(UserInfo), Host, Port},
       path = Path,
       query = uri_string:dissect_query(Query),
       fragment = maps:get(fragment, UriMap, undefined)
  }.


-spec parse_userinfo(string() | undefined)
    -> {username() | undefined, password() | undefined} | undefined.
parse_userinfo(undefined) -> undefined;
parse_userinfo([]) -> undefined;
parse_userinfo({User, undefined}) -> {User, undefined};
parse_userinfo({User, Password})  -> {User, Password};
parse_userinfo(Value) ->
  parse_userinfo_result(string:tokens(Value, ":")).


-spec parse_userinfo_result(list())
    -> {username() | undefined, password() | undefined} | undefined.
parse_userinfo_result([User, Password]) -> {User, Password};
parse_userinfo_result([User]) -> {User, undefined};
parse_userinfo_result({User, undefined}) -> {User, undefined};
parse_userinfo_result([]) -> undefined;
parse_userinfo_result(User) -> {User, undefined}.

%% @spec build_query(proplist()) -> string()
%% @doc Build the query parameters string from a proplist
%% @end
%%

-spec build_query_string([{any(), any()}]) -> string().

build_query_string(Args) when is_list(Args) ->
  Normalized = [{to_list(K), to_list(V)} || {K, V} <- Args],
  uri_string:compose_query(Normalized).

-spec to_list(Val :: integer() | list() | binary() | atom() | map()) -> list().
to_list(Val) when is_list(Val)    -> Val;
to_list(Val) when is_map(Val)     -> maps:to_list(Val);
to_list(Val) when is_atom(Val)    -> atom_to_list(Val);
to_list(Val) when is_binary(Val)  -> binary_to_list(Val);
to_list(Val) when is_integer(Val) -> integer_to_list(Val).
