src/nine.erl

-module(nine).

-export([compile/2]).

-type handler() :: {atom(), atom()}.
-type norm_route() :: {binary(), binary(), [handler()]}.
-type norm_routes() :: [norm_route()].

-spec compile(module(), map() | list()) -> ok.
compile(Module, RoutesConfig) ->
    Routes = normalize_routes(RoutesConfig),
    {ok, Forms} = normalized_routes_to_forms(Module, Routes),
    {ok, Module, Bin} = compile:forms(Forms),
    load_module_binary(Module, Bin).

-spec load_module_binary(module(), binary()) -> ok.
load_module_binary(Module, Bin) ->
    BeamFile = binary_to_list(list_to_binary([atom_to_list(Module), ".beam"])),
    {module, Module} = code:load_binary(Module, BeamFile, Bin),
    ok.

-spec normalize_routes(map() | list()) -> norm_routes().
normalize_routes(Config) ->
    sort_routes(config_to_routes(Config)).

-spec config_to_routes(map() | list()) -> norm_routes().
config_to_routes(Config) ->
    config_to_routes_builder(<<"">>, Config).

-spec config_to_routes_builder(binary(), map() | list()) -> norm_routes().
config_to_routes_builder(ParentPath, [_ | _] = Config) ->
    {MidBefore, Config2, MidAfter} = split_middleware_config(Config),
    config_to_routes_helper(ParentPath, Config2, MidBefore, MidAfter);
config_to_routes_builder(ParentPath, #{} = Config) ->
    config_to_routes_helper(ParentPath, Config, [], []).

-spec config_to_routes_helper(binary(), map(), list(), list()) -> norm_routes().
config_to_routes_helper(ParentPath, Config, MidBefore, MidAfter) ->
    lists:flatten(
        lists:map(fun({K, V}) ->
                     NewPath = list_to_binary([ParentPath, K]),
                     case is_path_method_config(V) of
                         true ->
                             case V of
                                 #{} = V2 ->
                                     extract_method_handlers(NewPath,
                                                             MidBefore ++ [V2] ++ MidAfter);
                                 [_ | _] = V2 ->
                                     extract_method_handlers(NewPath, MidBefore ++ V2 ++ MidAfter)
                             end;
                         false ->
                             case V of
                                 #{} = V2 ->
                                     config_to_routes_helper(NewPath, V2, MidBefore, MidAfter);
                                 [_ | _] = V2 ->
                                     {MB2, C2, MA2} = split_middleware_config(V2),
                                     config_to_routes_helper(NewPath,
                                                             C2,
                                                             MidBefore ++ MB2,
                                                             MA2 ++ MidAfter)
                             end
                     end
                  end,
                  maps:to_list(Config))).

-spec extract_method_handlers(binary(), list()) -> norm_routes().
extract_method_handlers(Path, PathConfig = [_ | _]) ->
    {MidBefore, Config, MidAfter} = split_middleware_config(PathConfig),
    lists:map(fun({M, MethodConfig}) ->
                 MethodConfig2 =
                     case MethodConfig of
                         [_ | _] = Mc ->
                             MidBefore ++ Mc ++ MidAfter;
                         {_Module, _Function} = Mc ->
                             MidBefore ++ [Mc] ++ MidAfter
                     end,
                 {Path, M, MethodConfig2}
              end,
              maps:to_list(Config)).

-spec is_path_method_config(map() | list()) -> boolean().
is_path_method_config(Config = #{}) ->
    lists:foldl(fun(K, Acc) ->
                   Result =
                       case K of
                           <<"GET">> ->
                               true;
                           <<"POST">> ->
                               true;
                           <<"PUT">> ->
                               true;
                           <<"PATCH">> ->
                               true;
                           <<"DELETE">> ->
                               true;
                           <<"_">> ->
                               true;
                           _ ->
                               false
                       end,
                   Result and Acc
                end,
                true,
                maps:keys(Config));
is_path_method_config(Config = [_ | _]) ->
    {_, Config2, _} = split_middleware_config(Config),
    is_path_method_config(Config2).

-spec split_middleware_config(list()) -> {list(), map(), list()}.
split_middleware_config(Config) ->
    split_middleware_config(Config, [], undefined, undefined).

-spec split_middleware_config(list(), list(), map() | undefined, list() | undefined) ->
                                 {list(), map(), list()}.
split_middleware_config([], Before, Config2, After) ->
    {lists:reverse(Before), Config2, lists:reverse(After)};
split_middleware_config([Item | Rest], Before, undefined, undefined) ->
    case Item of
        #{} = Item2 ->
            split_middleware_config(Rest, Before, Item2, []);
        _ = Item2 ->
            split_middleware_config(Rest, [Item2 | Before], undefined, undefined)
    end;
split_middleware_config([Item | Rest], Before, Config, After) ->
    split_middleware_config(Rest, Before, Config, [Item | After]).

%% true means A < B
-spec sort_routes(norm_routes()) -> norm_routes().
sort_routes(Routes) ->
    lists:sort(fun({A, AMethod, _}, {B, BMethod, _}) ->
                  case A == B of
                      true ->
                          AMethod < BMethod;
                      false ->
                          AContainsParams = contains_params(A),
                          BContainsParams = contains_params(B),
                          ASplat = contains_splat(A),
                          BSplat = contains_splat(B),
                          AIsSplat = A == <<"/*">>,
                          BIsSplat = B == <<"/*">>,
                          case {AContainsParams, BContainsParams, ASplat, BSplat} of
                              {true, true, true, true} ->
                                  case {AIsSplat, BIsSplat} of
                                      {true, false} ->
                                          false;
                                      {false, true} ->
                                          true;
                                      _ ->
                                          AMethod < BMethod
                                  end;
                              {true, true, true, false} ->
                                  false;
                              {true, true, false, true} ->
                                  true;
                              {true, true, false, false} ->
                                  A < B;
                              {true, false, true, true} ->
                                  case {AIsSplat, BIsSplat} of
                                      {true, false} ->
                                          false;
                                      {false, true} ->
                                          true;
                                      {false, false} ->
                                          false;
                                      {true, true} -> %% This isn't possible
                                          AMethod < BMethod
                                  end;
                              {true, false, true, false} ->
                                  false;
                              {true, false, false, true} ->
                                  true;
                              {true, false, false, false} ->
                                  false;
                              {false, true, true, true} ->
                                  case {AIsSplat, BIsSplat} of
                                      {true, false} ->
                                          false;
                                      {false, true} ->
                                          true;
                                      {false, false} ->
                                          true;
                                      {true, true} -> %% This isn't possible
                                          AMethod < BMethod
                                  end;
                              {false, true, true, false} ->
                                  false;
                              {false, true, false, true} ->
                                  true;
                              {false, true, false, false} ->
                                  true;
                              {false, false, true, true} ->
                                  case {AIsSplat, BIsSplat} of
                                      {true, false} ->
                                          false;
                                      {false, true} ->
                                          true;
                                      _ ->
                                          AMethod < BMethod
                                  end;
                              {false, false, true, false} ->
                                  false;
                              {false, false, false, true} ->
                                  true;
                              {false, false, false, false} ->
                                  A < B
                          end
                  end
               end,
               Routes).

-spec contains_params(iodata()) -> boolean().
contains_params(S) ->
    case string:find(S, ":") of
        nomatch ->
            false;
        _ ->
            true
    end.

-spec contains_splat(iodata()) -> boolean().
contains_splat(S) ->
    case string:find(S, "*") of
        nomatch ->
            false;
        _ ->
            true
    end.

-spec normalized_routes_to_forms(module(), norm_routes()) -> {ok, erl_parse:parse_tree()}.
normalized_routes_to_forms(Module, NormRoutes) ->
    {ok,
     [codegen_module_form(Module)] ++ elli_exports() ++ routes_map_to_methods(NormRoutes)}.

-spec codegen_module_form(module()) -> erl_parse:erl_parse_tree().
codegen_module_form(Module) ->
    {attribute, 0, module, Module}.

-spec elli_exports() -> erl_parse:erl_parse_tree().
elli_exports() ->
    [{attribute, 0, export, [{handle, 2}, {handle_event, 3}]}].

-spec routes_map_to_methods(list()) -> list().
routes_map_to_methods(NormRoutes) ->
    codegen_handle() ++ codegen_handle_event() ++ codegen_routes(NormRoutes).

-spec codegen_handle_event() -> erl_parse:erl_parse_tree().
codegen_handle_event() ->
    [{function,
      0,
      handle_event,
      3,
      [{clause,
        0,
        [{var, 0, '_Event'}, {var, 0, '_Data'}, {var, 0, '_Args'}],
        [],
        [{atom, 0, ok}]}]}].

-spec codegen_handle() -> erl_parse:erl_parse_tree().
codegen_handle() ->
    [{function,
      0,
      handle,
      2,
      [{clause,
        0,
        [{var, 0, 'Req'}, {var, 0, '_Args'}],
        [],
        [{call,
          0,
          {atom, 0, handle},
          [{map,
            0,
            [{map_field_assoc, 0, {atom, 0, req}, {var, 0, 'Req'}},
             {map_field_assoc, 0, {atom, 0, method}, codegen_get_method()},
             {map_field_assoc, 0, {atom, 0, path}, codegen_get_path()}]}]}]}]}].

-spec codegen_get_method() -> erl_parse:erl_parse_tree().
codegen_get_method() ->
    {call, 1, {remote, 1, {atom, 1, nine_util}, {atom, 1, get_method}}, [{var, 1, 'Req'}]}.

-spec codegen_get_path() -> erl_parse:erl_parse_tree().
codegen_get_path() ->
    {call, 0, {remote, 0, {atom, 0, elli_request}, {atom, 0, path}}, [{var, 0, 'Req'}]}.

-spec codegen_routes(list()) -> erl_parse:parse_tree().
codegen_routes(Routes) ->
    [{function, 0, handle, 1, lists:map(fun codegen_route/1, Routes)}].

-spec codegen_route({binary(), binary(), list()}) -> erl_parse:parse_tree().
codegen_route({Path0, Method, Middleware}) ->
    PathParsed = parse_path(Path0),
    Behavior = middleware_to_behavior(Middleware, filter_path_params(PathParsed)),
    Path = codegen_path(PathParsed),
    codegen_method_clause(Path, Method, Behavior).

-spec filter_path_params(list()) -> list().
filter_path_params(Path) ->
    lists:reverse(
        lists:foldl(fun(E, Acc) ->
                       case E of
                           all ->
                               Acc;
                           {all, Var} ->
                               [Var | Acc];
                           {_Prefix, Suffix} ->
                               [Suffix | Acc];
                           E2 when is_atom(E2) ->
                               [E2 | Acc];
                           _ ->
                               Acc
                       end
                    end,
                    [],
                    Path)).

-spec middleware_to_behavior(list(), list()) -> list().
middleware_to_behavior(Middleware, PathParams) ->
    Pp = codegen_path_params(PathParams),
    M = codegen_case_chain_init(enumerate_middleware(Middleware, 1)),
    [Pp, M].

-spec codegen_path_params(list()) -> erl_parse:parse_tree().
codegen_path_params(PathParams) ->
    {match,
     0,
     {var, 0, 'Req1'},
     {map,
      0,
      {var, 0, 'Req0'},
      [{map_field_assoc,
        0,
        {atom, 0, params},
        {map, 0, codegen_path_param_elements(PathParams)}}]}}.

-spec codegen_path_param_elements(list()) -> list().
codegen_path_param_elements(PathParams) ->
    lists:map(fun(P) ->
                 Lvar = binary_to_atom(string:lowercase(atom_to_binary(P))),
                 {map_field_assoc, 0, {atom, 0, Lvar}, {var, 0, P}}
              end,
              PathParams).

-spec enumerate_middleware([{atom(), atom()}], integer()) ->
                              [{atom(), atom(), integer()}].
enumerate_middleware(Middleware, Counter) ->
    enumerate_middleware(Middleware, [], Counter).

-spec enumerate_middleware([{atom(), atom()}],
                           [{atom(), atom(), integer()}],
                           integer()) ->
                              [{atom(), atom(), integer()}].
enumerate_middleware([], Acc, _Counter) ->
    Acc;
enumerate_middleware([{Module, Function} | Middleware], Acc, Counter) ->
    enumerate_middleware(Middleware, [{Module, Function, Counter} | Acc], Counter + 1).

-spec codegen_case_chain_init([{atom(), atom(), integer()}]) -> erl_parse:parse_tree().
codegen_case_chain_init([{Module, Function, Counter} | Rest]) ->
    codegen_case_chain(Rest,
                       codegen_case_wrapper(Module,
                                            Function,
                                            Counter,
                                            {var, 0, req_atom(Counter + 1)})).

-spec codegen_case_chain([{atom(), atom(), integer()}], erl_parse:parse_tree()) ->
                            erl_parse:parse_tree().
codegen_case_chain([], Acc) ->
    Acc;
codegen_case_chain([{Module, Function, Counter} | Mid], Acc) ->
    codegen_case_chain(Mid, codegen_case_wrapper(Module, Function, Counter, Acc)).

-spec codegen_case_wrapper(atom(), atom(), integer(), erl_parse:parse_tree()) ->
                              erl_parse:parse_tree().
codegen_case_wrapper(Module, Function, Counter, Behavior) ->
    {'case',
     0,
     codegen_handle_req(Module, Function, req_atom(Counter)),
     [{clause,
       0,
       [{map, 0, [{map_field_exact, 0, {atom, 0, response}, {var, 0, resp_atom(Counter)}}]}],
       [],
       [{var, 0, resp_atom(Counter)}]},
      {clause, 0, [{var, 0, req_atom(Counter + 1)}], [], [Behavior]}]}.

-spec req_atom(integer()) -> atom().
req_atom(Counter) ->
    binary_to_atom(list_to_binary(["Req", integer_to_list(Counter)])).

-spec resp_atom(integer()) -> atom().
resp_atom(Counter) ->
    binary_to_atom(list_to_binary(["Resp", integer_to_list(Counter)])).

-spec codegen_handle_req(atom(), atom(), atom()) -> erl_parse:parse_tree().
codegen_handle_req(Module, Function, Req) ->
    {call, 0, {remote, 0, {atom, 0, Module}, {atom, 0, Function}}, [{var, 0, Req}]}.

-spec parse_path(binary()) ->
                    [atom() | binary() | {binary(), atom()} | {all, atom()} | all].
parse_path(Path0) ->
    [_ | Rest] = string:split(Path0, "/", all),
    lists:map(fun translate_path_param/1, Rest).

-spec translate_path_param(binary()) ->
                              binary() | atom() | {binary(), atom()} | {all, atom()} | all.
translate_path_param(<<"*">>) ->
    all;
translate_path_param(<<"*", Name/binary>>) ->
    {all, binary_to_var(Name)};
translate_path_param(S) ->
    case string:split(S, ":") of
        [_] ->
            S;
        [<<>>, Suffix] ->
            binary_to_var(Suffix);
        [Prefix, Suffix] ->
            {Prefix, binary_to_var(Suffix)}
    end.

-spec binary_to_var(binary()) -> atom().
binary_to_var(B) ->
    binary_to_atom(string:titlecase(B)).

-spec codegen_path(list()) -> erl_parse:parse_tree().
codegen_path([]) ->
    {var, 0, '_'};
codegen_path([<<>>]) ->
    {nil, 0};
codegen_path(Path) ->
    Path2 = lists:reverse(Path),
    case Path2 of
        [{all, Var} | Rest] ->
            codegen_path(Rest, {var, 0, Var});
        [all | Rest] ->
            codegen_path(Rest, {var, 0, '_'});
        _ ->
            codegen_path(Path2, {nil, 0})
    end.

-spec codegen_path(list(), erl_parse:parse_tree()) -> erl_parse:parse_tree().
codegen_path([], Acc) ->
    Acc;
codegen_path([E | Rest], Acc) ->
    codegen_path(Rest, {cons, 0, codegen_path_element(E), Acc}).

-spec codegen_path_element(binary() | atom() | {binary(), atom()}) ->
                              erl_parse:parse_tree().
codegen_path_element(E) ->
    case E of
        all ->
            {var, 0, '_'};
        {B, Var} ->
            codegen_binary_var(B, Var);
        E when is_atom(E) ->
            {var, 0, E};
        E when is_binary(E) ->
            codegen_binary(E)
    end.

-spec codegen_binary(binary()) -> erl_parse:parse_tree().
codegen_binary(B) ->
    {bin, 0, [{bin_element, 0, {string, 0, binary_to_list(B)}, default, default}]}.

-spec codegen_binary_var(binary(), atom()) -> erl_parse:parse_tree().
codegen_binary_var(B, Var) ->
    {bin,
     0,
     [{bin_element, 0, {string, 0, binary_to_list(B)}, default, default},
      {bin_element, 0, {var, 0, Var}, default, [binary]}]}.

%% TODO: change name method to something else
-spec codegen_method_clause(erl_parse:tree(), binary(), erl_parse:parse_tree()) ->
                               erl_parse:parse_tree().
codegen_method_clause(Path, Method, Behavior) ->
    {clause,
     0,
     [{match, 0, {var, 0, 'Req0'}, {map, 0, codegen_method_clause_helper(Path, Method)}}],
     [],
     Behavior}.

-spec codegen_method_clause_helper(erl_parse:tree(), binary()) -> erl_parse:parse_tree().
codegen_method_clause_helper(Path, Method) ->
    [{map_field_exact, 0, {atom, 0, method}, codegen_method_value(translate_method(Method))},
     {map_field_exact, 0, {atom, 0, path}, Path}].

-spec codegen_method_value(atom()) ->
                              {var, integer(), '_Method'} | {atom, integer(), atom()}.
codegen_method_value('_Method') ->
    {var, 0, '_Method'};
codegen_method_value(Method) ->
    {atom, 0, Method}.

-spec translate_method(binary()) -> atom().
translate_method(<<"GET">>) ->
    'GET';
translate_method(<<"POST">>) ->
    'POST';
translate_method(<<"PUT">>) ->
    'PUT';
translate_method(<<"PATCH">>) ->
    'PATCH';
translate_method(<<"DELETE">>) ->
    'DELETE';
translate_method(<<"_">>) ->
    '_Method'.

-ifdef(TEST).

-include_lib("eunit/include/eunit.hrl").

contains_params_test() ->
    ?assert(contains_params(<<"/foo/:bar">>)),
    ?assertNot(contains_params(<<"/foo">>)).

contains_splat_test() ->
    ?assert(contains_splat(<<"/foo/*">>)),
    ?assert(contains_splat(<<"*">>)),
    ?assertNot(contains_splat(<<"/foo">>)).

is_path_method_config_test() ->
    ?assert(is_path_method_config(#{<<"GET">> => {foo, bar}})),
    ?assert(is_path_method_config([{foo, bar}, #{<<"GET">> => {foo, bar}}])),
    ?assert(is_path_method_config([{foo, bar}, #{<<"POST">> => {foo, bar}}])),
    ?assert(is_path_method_config([{foo, bar}, #{<<"PUT">> => {foo, bar}}])),
    ?assert(is_path_method_config([{foo, bar}, #{<<"PATCH">> => {foo, bar}}])),
    ?assert(is_path_method_config([{foo, bar}, #{<<"DELETE">> => {foo, bar}}])),
    ?assert(is_path_method_config(#{<<"GET">> => {foo, bar},
                                    <<"POST">> => {foo, bar},
                                    <<"PUT">> => {foo, bar},
                                    <<"PATCH">> => {foo, bar},
                                    <<"DELETE">> => {foo, bar},
                                    <<"_">> => {foo, bar}})),
    ?assertEqual(false, is_path_method_config(#{<<"/">> => foobar})),
    ?assertEqual(false,
                 is_path_method_config(#{<<"GET">> => {foo, bar}, <<"/">> => {foo, bar}})),
    ?assertEqual(false,
                 is_path_method_config(#{<<"GET">> => {foo, bar},
                                         <<"/stuff">> => {foo, bar},
                                         foobar => {foo, bar}})).

split_middleware_config_test() ->
    ?assertEqual({[1, 2, 3], #{foo => bar}, [4, 5, 6]},
                 split_middleware_config([1, 2, 3, #{foo => bar}, 4, 5, 6])).

extract_method_handlers_test() ->
    Expected = [{<<"/foo">>, <<"GET">>, [{barbar, foofoo}, {foo, bar}, {stuff, foo}]}],
    ?assertEqual(Expected,
                 extract_method_handlers(<<"/foo">>,
                                         [{barbar, foofoo},
                                          #{<<"GET">> => {foo, bar}},
                                          {stuff, foo}])),
    Expected2 =
        [{<<"/foo">>,
          <<"GET">>,
          [{barbar, foofoo}, {foo2, stoof}, {foo, bar}, {foo2, foots}, {stuff, foo}]}],
    ?assertEqual(Expected2,
                 extract_method_handlers(<<"/foo">>,
                                         [{barbar, foofoo},
                                          #{<<"GET">> =>
                                                [{foo2, stoof}, {foo, bar}, {foo2, foots}]},
                                          {stuff, foo}])).

sort_routes_test() ->
    ?assertEqual([{<<"/foo">>, <<"GET">>, null},
                  {<<"/foo">>, <<"POST">>, null},
                  {<<"/foo">>, <<"_">>, null}],
                 sort_routes([{<<"/foo">>, <<"GET">>, null},
                              {<<"/foo">>, <<"_">>, null},
                              {<<"/foo">>, <<"POST">>, null}])),

    ?assertEqual([{<<"/foo/bar">>, <<"GET">>, null}, {<<"/foo/:bar">>, <<"GET">>, null}],
                 sort_routes([{<<"/foo/:bar">>, <<"GET">>, null},
                              {<<"/foo/bar">>, <<"GET">>, null}])),

    ?assertEqual([{<<"/foo">>, <<"GET">>, null}, {<<"/*">>, <<"GET">>, null}],
                 sort_routes([{<<"/*">>, <<"GET">>, null}, {<<"/foo">>, <<"GET">>, null}])),

    %% This case would result in shadowing
    ?assertEqual([{<<"/foo/:bar">>, <<"GET">>, null}, {<<"/foo/*">>, <<"GET">>, null}],
                 sort_routes([{<<"/foo/*">>, <<"GET">>, null},
                              {<<"/foo/:bar">>, <<"GET">>, null}])),

    ?assertEqual([{<<"/baz/*stuff">>, <<"GET">>, null}, {<<"/*">>, <<"_">>, null}],
                 sort_routes([{<<"/*">>, <<"_">>, null}, {<<"/baz/*stuff">>, <<"GET">>, null}])),

    ?assertEqual([{<<"/stuff/*">>, <<"GET">>, null}, {<<"/*">>, <<"_">>, null}],
                 sort_routes([{<<"/*">>, <<"_">>, null}, {<<"/stuff/*">>, <<"GET">>, null}])),

    ?assertEqual([{<<"/bzz/:stuff/*segment">>, <<"GET">>, null}, {<<"/*">>, <<"_">>, null}],
                 sort_routes([{<<"/*">>, <<"_">>, null},
                              {<<"/bzz/:stuff/*segment">>, <<"GET">>, null}])).

config_to_routes_test() ->
    ?assertEqual([{<<"/foo">>, <<"GET">>, [{foo, bar}]}],
                 config_to_routes(#{<<"/foo">> => #{<<"GET">> => {foo, bar}}})),
    ?assertEqual([{<<"/foo">>, <<"GET">>, [{biz, baz}, {foo, bar}, {baz, biz}]}],
                 config_to_routes([{biz, baz},
                                   #{<<"/foo">> => #{<<"GET">> => {foo, bar}}},
                                   {baz, biz}])).

config_to_routes_builder_test() ->
    ?assertEqual([{<<"/foo">>, <<"GET">>, [{foo, bar}]}],
                 config_to_routes_builder(<<"">>, #{<<"/foo">> => #{<<"GET">> => {foo, bar}}})),
    ?assertEqual([{<<"/foo">>, <<"GET">>, [{biz, baz}, {foo, bar}, {baz, biz}]}],
                 config_to_routes_builder(<<"">>,
                                          [{biz, baz},
                                           #{<<"/foo">> => #{<<"GET">> => {foo, bar}}},
                                           {baz, biz}])).

config_to_routes_helper_test() ->
    ?assertEqual([{<<"/foo">>, <<"GET">>, [{foo, bar}]}],
                 config_to_routes_helper(<<"">>,
                                         #{<<"/foo">> => #{<<"GET">> => {foo, bar}}},
                                         [],
                                         [])),
    ?assertEqual([{<<"/foo">>, <<"GET">>, [{foo, baz}, {foo, bar}, {biz, baz}]}],
                 config_to_routes_helper(<<"">>,
                                         #{<<"/foo">> => #{<<"GET">> => {foo, bar}}},
                                         [{foo, baz}],
                                         [{biz, baz}])),
    ?assertEqual([{<<"/foo/bar">>, <<"GET">>, [{foo, bar}]}],
                 config_to_routes_helper(<<"">>,
                                         #{<<"/foo">> =>
                                               #{<<"/bar">> => #{<<"GET">> => {foo, bar}}}},
                                         [],
                                         [])),
    ?assertEqual([{<<"/foo">>, <<"GET">>, [{biz, baz}, {foo, bar}, {yip, yap}]}],
                 config_to_routes_helper(<<"">>,
                                         #{<<"/foo">> =>
                                               #{<<"GET">> =>
                                                     [{biz, baz}, {foo, bar}, {yip, yap}]}},
                                         [],
                                         [])),
    ?assertEqual([{<<"/foo">>, <<"GET">>, [{biz, baz}, {foo, bar}, {baz, biz}]}],
                 config_to_routes_helper(<<"">>,
                                         #{<<"/foo">> =>
                                               [{biz, baz},
                                                #{<<"GET">> => {foo, bar}},
                                                {baz, biz}]},
                                         [],
                                         [])),
    ?assertEqual([{<<"/foo/bar">>, <<"GET">>, [{biz, baz}, {foo, bar}, {baz, biz}]}],
                 config_to_routes_helper(<<"">>,
                                         #{<<"/foo">> =>
                                               [{biz, baz},
                                                #{<<"/bar">> => #{<<"GET">> => {foo, bar}}},
                                                {baz, biz}]},
                                         [],
                                         [])).

codegen_module_form_test() ->
    ?assertEqual({attribute, 0, module, foobar}, codegen_module_form(foobar)).

elli_exports_test() ->
    ?assertEqual([{attribute, 0, export, [{handle, 2}, {handle_event, 3}]}], elli_exports()).

codegen_handle_event_test() ->
    ?assertEqual([{function,
                   0,
                   handle_event,
                   3,
                   [{clause,
                     0,
                     [{var, 0, '_Event'}, {var, 0, '_Data'}, {var, 0, '_Args'}],
                     [],
                     [{atom, 0, ok}]}]}],
                 codegen_handle_event()).

codegen_get_method_test() ->
    ?assertEqual({call,
                  1,
                  {remote, 1, {atom, 1, nine_util}, {atom, 1, get_method}},
                  [{var, 1, 'Req'}]},
                 codegen_get_method()).

codegen_get_path_test() ->
    ?assertEqual({call,
                  0,
                  {remote, 0, {atom, 0, elli_request}, {atom, 0, path}},
                  [{var, 0, 'Req'}]},
                 codegen_get_path()).

codegen_handle_test() ->
    ?assertEqual([{function,
                   0,
                   handle,
                   2,
                   [{clause,
                     0,
                     [{var, 0, 'Req'}, {var, 0, '_Args'}],
                     [],
                     [{call,
                       0,
                       {atom, 0, handle},
                       [{map,
                         0,
                         [{map_field_assoc, 0, {atom, 0, req}, {var, 0, 'Req'}},
                          {map_field_assoc, 0, {atom, 0, method}, codegen_get_method()},
                          {map_field_assoc, 0, {atom, 0, path}, codegen_get_path()}]}]}]}]}],
                 codegen_handle()).

codegen_case_init_test() ->
    Expected = codegen_case_wrapper(foo, bar, 0, {var, 0, 'Req1'}),
    ?assertEqual(Expected, codegen_case_chain_init([{foo, bar, 0}])).

codegen_case_chain_test() ->
    ?assertEqual({nil, 0}, codegen_case_chain([], {nil, 0})),
    Expected = codegen_case_wrapper(foo, bar, 0, {nil, 0}),
    ?assertEqual(Expected, codegen_case_chain([{foo, bar, 0}], {nil, 0})).

codegen_case_wrapper_test() ->
    Expected =
        {'case',
         0,
         codegen_handle_req(foo, bar, 'Req0'),
         [{clause,
           0,
           [{map, 0, [{map_field_exact, 0, {atom, 0, response}, {var, 0, 'Resp0'}}]}],
           [],
           [{var, 0, 'Resp0'}]},
          {clause, 0, [{var, 0, 'Req1'}], [], [{nil, 0}]}]},
    ?assertEqual(Expected, codegen_case_wrapper(foo, bar, 0, {nil, 0})).

codegen_path_param_elements_test() ->
    Expected = [{map_field_assoc, 0, {atom, 0, foo}, {var, 0, 'Foo'}}],
    ?assertEqual(Expected, codegen_path_param_elements(['Foo'])).

codegen_path_params_test() ->
    Expected =
        {match,
         0,
         {var, 0, 'Req1'},
         {map,
          0,
          {var, 0, 'Req0'},
          [{map_field_assoc,
            0,
            {atom, 0, params},
            {map, 0, codegen_path_param_elements(['Foo'])}}]}},
    ?assertEqual(Expected, codegen_path_params(['Foo'])).

filter_path_params_test() ->
    ?assertEqual([foo, bar],
                 filter_path_params([<<"foo">>, foo, <<"bar">>, bar, 123, "foobar"])),
    ?assertEqual([foo, bar, baz],
                 filter_path_params([<<"foo">>,
                                     foo,
                                     <<"bar">>,
                                     {<<"bar">>, bar},
                                     {all, baz},
                                     all])).

enumerate_middleware_test() ->
    InputData = [{foo, bar}, {bar, foo}],
    OutputData = enumerate_middleware(InputData, 0),
    ?assertEqual([{bar, foo, 1}, {foo, bar, 0}], OutputData).

codegen_handle_req_test() ->
    Expected = {call, 0, {remote, 0, {atom, 0, foo}, {atom, 0, bar}}, [{var, 0, 'Req0'}]},
    ?assertEqual(Expected, codegen_handle_req(foo, bar, 'Req0')).

codegen_binary_test() ->
    Expected = {bin, 0, [{bin_element, 0, {string, 0, "foobar"}, default, default}]},
    ?assertEqual(Expected, codegen_binary(<<"foobar">>)).

codegen_binary_var_test() ->
    Expected =
        {bin,
         0,
         [{bin_element, 0, {string, 0, "foobar"}, default, default},
          {bin_element, 0, {var, 0, 'Var'}, default, [binary]}]},

    ?assertEqual(Expected, codegen_binary_var(<<"foobar">>, 'Var')).

codegen_path_element_test() ->
    ?assertEqual({var, 0, '_'}, codegen_path_element(all)),
    ?assertEqual({var, 0, foo}, codegen_path_element(foo)),
    ?assertEqual(codegen_binary(<<"foobar">>), codegen_path_element(<<"foobar">>)),
    ?assertEqual(codegen_binary_var(<<"foobar">>, 'Var'),
                 codegen_path_element({<<"foobar">>, 'Var'})).

codegen_path_test() ->
    ?assertEqual({var, 0, '_'}, codegen_path([])),
    ?assertEqual({nil, 0}, codegen_path([<<>>])),
    ?assertEqual({nil, 0}, codegen_path([], {nil, 0})),
    ?assertEqual({cons, 0, {var, 0, foo}, {nil, 0}}, codegen_path([foo], {nil, 0})),
    ?assertEqual({cons, 0, {var, 0, foo}, {nil, 0}}, codegen_path([foo])),
    ?assertEqual({cons, 0, {var, 0, foo}, {var, 0, bar}}, codegen_path([foo, {all, bar}])),
    ?assertEqual({cons, 0, {var, 0, foo}, {var, 0, '_'}}, codegen_path([foo, all])).

codegen_method_clause_helper_test() ->
    ?assertEqual([{map_field_exact, 0, {atom, 0, method}, {atom, 0, 'GET'}},
                  {map_field_exact, 0, {atom, 0, path}, {nil, 0}}],
                 codegen_method_clause_helper({nil, 0}, <<"GET">>)).

codegen_method_clause_test() ->
    ?assertEqual({clause,
                  0,
                  [{match,
                    0,
                    {var, 0, 'Req0'},
                    {map,
                     0,
                     [{map_field_exact, 0, {atom, 0, method}, {atom, 0, 'GET'}},
                      {map_field_exact, 0, {atom, 0, path}, {nil, 0}}]}}],
                  [],
                  {nil, 0}},
                 codegen_method_clause({nil, 0}, <<"GET">>, {nil, 0})).

translate_method_test() ->
    ?assertEqual('GET', translate_method(<<"GET">>)),
    ?assertEqual('POST', translate_method(<<"POST">>)),
    ?assertEqual('PUT', translate_method(<<"PUT">>)),
    ?assertEqual('PATCH', translate_method(<<"PATCH">>)),
    ?assertEqual('DELETE', translate_method(<<"DELETE">>)),
    ?assertEqual('_Method', translate_method(<<"_">>)).

codegen_method_value_test() ->
    ?assertEqual({var, 0, '_Method'}, codegen_method_value('_Method')),
    ?assertEqual({atom, 0, thing}, codegen_method_value(thing)),
    ?assertEqual({atom, 0, stuff}, codegen_method_value(stuff)).

parse_path_test() ->
    ?assertEqual([<<"foo">>, <<"bar">>], parse_path(<<"/foo/bar">>)),

    ?assertEqual([<<"foo">>, 'Bar'], parse_path(<<"/foo/:bar">>)),
    ?assertEqual([<<>>], parse_path(<<"/">>)),

    ?assertEqual([all], parse_path(<<"/*">>)),
    ?assertEqual([<<"api">>, {<<"v">>, 'Version'}, <<"pages">>, 'Id'],
                 parse_path(<<"/api/v:version/pages/:id">>)),
    ?assertEqual([<<"pages">>, {all, 'Page'}], parse_path(<<"/pages/*page">>)),
    ?assertEqual([<<"pages">>, {<<"he">>, 'Page'}, {all, 'Rest'}],
                 parse_path(<<"/pages/he:page/*rest">>)).

translate_path_param_test() ->
    ?assertEqual(<<"foo">>, translate_path_param(<<"foo">>)),
    ?assertEqual('Foo', translate_path_param(<<":foo">>)),
    ?assertEqual({<<"v">>, 'Version'}, translate_path_param(<<"v:version">>)),
    ?assertEqual({all, 'Splat'}, translate_path_param(<<"*splat">>)),
    ?assertEqual(all, translate_path_param(<<"*">>)).

resp_atom_test() ->
    ?assertEqual('Resp1', resp_atom(1)),
    ?assertEqual('Resp2', resp_atom(2)).

req_atom_test() ->
    ?assertEqual('Req1', req_atom(1)),
    ?assertEqual('Req2', req_atom(2)).

-endif.