src/nine_cowboy.erl

-module(nine_cowboy).

-export([generate/2]).

generate(ModuleName, Routes) ->
    [{attribute, 0, module, ModuleName},
     {attribute, 0, behavior, cowboy_middleware},
     {attribute, 0, export, [{execute, 2}]},
     erl_syntax:revert(codegen_execute()),
     erl_syntax:revert(codegen_handle_route(Routes))].

codegen_execute() ->
    erl_syntax:function(
        erl_syntax:atom("execute"),
        [erl_syntax:clause(
            [erl_syntax:variable("Req"),
             erl_syntax:variable("Env")],
            none,
            [codegen_get_path(),
             codegen_get_method(),
             codegen_try_handle_route()]
        )]
    ).

%% TODO: Change to nine_cowboy_util:get_path
%% Where get_path will get path and split on /
codegen_get_path() ->
    erl_syntax:match_expr(
        erl_syntax:variable("Path"),
        erl_syntax:application(
            erl_syntax:atom("nine_cowboy_util"),
            erl_syntax:atom("get_path"),
            [erl_syntax:variable("Req")]
        )
    ).


codegen_get_method() ->
    erl_syntax:match_expr(
        erl_syntax:variable("Method"),
        erl_syntax:application(
            erl_syntax:atom("cowboy_req"),
            erl_syntax:atom("method"),
            [erl_syntax:variable("Req")]
        )
    ).


codegen_try_handle_route() ->
    erl_syntax:try_expr(
        [codegen_handle_route_call()],
        [
         erl_syntax:clause(
            [erl_syntax:map_expr([
                erl_syntax:map_field_exact(
                    erl_syntax:atom("resp"),
                    erl_syntax:variable("Resp")
                ),
                erl_syntax:map_field_exact(
                    erl_syntax:atom("upgrade"),
                    erl_syntax:tuple([
                        erl_syntax:variable("UpgradeModule"),
                        erl_syntax:variable("UpgradeHandler"),
                        erl_syntax:variable("UpgradeHandlerOpts")
                    ])
                ),
                erl_syntax:map_field_exact(
                    erl_syntax:atom("state"),
                    erl_syntax:variable("State")
                )
            ])],
            [],
            [erl_syntax:application(
                erl_syntax:variable("UpgradeModule"),
                erl_syntax:atom("upgrade"),
                [erl_syntax:variable("Resp"),
                 erl_syntax:variable("Env"),
                 erl_syntax:variable("UpgradeHandler"),
                 erl_syntax:variable("State"),
                 erl_syntax:variable("UpgradeHandlerOpts")]
            )]  
         ),
         erl_syntax:clause(
            [erl_syntax:map_expr([
                erl_syntax:map_field_exact(
                    erl_syntax:atom("resp"),
                    erl_syntax:variable("Resp")
                ),
                erl_syntax:map_field_exact(
                    erl_syntax:atom("upgrade"),
                    erl_syntax:tuple([
                        erl_syntax:variable("UpgradeModule"),
                        erl_syntax:variable("UpgradeHandler"),
                        erl_syntax:variable("UpgradeHandlerOpts")
                    ])
                )
            ])],
            [],
            [erl_syntax:application(
                erl_syntax:variable("UpgradeModule"),
                erl_syntax:atom("upgrade"),
                [erl_syntax:variable("Resp"),
                 erl_syntax:variable("Env"),
                 erl_syntax:variable("UpgradeHandler"),
                 erl_syntax:map_expr([]),
                 erl_syntax:variable("UpgradeHandlerOpts")]
            )]
         ),
         erl_syntax:clause(
            [erl_syntax:map_expr([
                erl_syntax:map_field_exact(
                    erl_syntax:atom("resp"),
                    erl_syntax:variable("Resp")
                ),
                erl_syntax:map_field_exact(
                    erl_syntax:atom("upgrade"),
                    erl_syntax:tuple([
                        erl_syntax:variable("UpgradeModule"),
                        erl_syntax:variable("UpgradeHandler")
                    ])
                ),
                erl_syntax:map_field_exact(
                    erl_syntax:atom("state"),
                    erl_syntax:variable("State")
                )
            ])],
            [],
            [erl_syntax:application(
                erl_syntax:variable("UpgradeModule"),
                erl_syntax:atom("upgrade"),
                [erl_syntax:variable("Resp"),
                 erl_syntax:variable("Env"),
                 erl_syntax:variable("UpgradeHandler"),
                 erl_syntax:variable("State")]
            )]  
         ),
         erl_syntax:clause(
            [erl_syntax:map_expr([
                erl_syntax:map_field_exact(
                    erl_syntax:atom("resp"),
                    erl_syntax:variable("Resp")
                ),
                erl_syntax:map_field_exact(
                    erl_syntax:atom("upgrade"),
                    erl_syntax:tuple([
                        erl_syntax:variable("UpgradeModule"),
                        erl_syntax:variable("UpgradeHandler")
                    ])
                )
            ])],
            [],
            [erl_syntax:application(
                erl_syntax:variable("UpgradeModule"),
                erl_syntax:atom("upgrade"),
                [erl_syntax:variable("Resp"),
                 erl_syntax:variable("Env"),
                 erl_syntax:variable("UpgradeHandler"),
                 erl_syntax:map_expr([])]
            )]  
         ),
         erl_syntax:clause(
                [erl_syntax:map_expr([
                    erl_syntax:map_field_exact(
                        erl_syntax:atom("resp"),
                        erl_syntax:variable("Resp")
                    )
                ])],
                [],
                [codegen_tuple_response()]
         )
        ],
        [erl_syntax:clause(
            [erl_syntax:tuple([
                erl_syntax:variable("Class"),
                erl_syntax:variable("Reason"),
                erl_syntax:variable("Stacktrace")
            ])],
            [],
            [erl_syntax:application(
                erl_syntax:atom("erlang"),
                erl_syntax:atom("raise"),
                [erl_syntax:variable("Class"),
                 erl_syntax:variable("Reason"),
                 erl_syntax:variable("Stacktrace")]
            )]
        )]
    ).


codegen_handle_route_call() ->
        erl_syntax:application(
            erl_syntax:atom("handle_route"),
            [erl_syntax:map_expr([
                erl_syntax:map_field_assoc(
                    erl_syntax:atom("path"),
                    erl_syntax:variable("Path")
                ),
                erl_syntax:map_field_assoc(
                    erl_syntax:atom("method"),
                    erl_syntax:variable("Method")
                ),
                erl_syntax:map_field_assoc(
                    erl_syntax:atom("req"),
                    erl_syntax:variable("Req")
                )
            ])]
        ).

codegen_tuple_response() ->
    erl_syntax:tuple([
        erl_syntax:atom("ok"),
        erl_syntax:variable("Resp"),
        erl_syntax:map_expr(
            erl_syntax:variable("Env"),
            [erl_syntax:map_field_assoc(
                erl_syntax:atom("result"),
                erl_syntax:atom("ok")
            )]
        )
    ]).

codegen_handle_route(Routes) ->
    erl_syntax:function(
        erl_syntax:atom("handle_route"),
        codegen_routes(Routes)
    ).

codegen_routes(Routes) ->
    lists:map(fun codegen_route/1, Routes).

codegen_route({Path, Method, Handlers}) ->
    TranslatedPath = translate_path(Path),
    Params = filter_path_params(TranslatedPath),
    erl_syntax:clause(
        [codegen_route_pattern(TranslatedPath, Method)],
        none,
        [codegen_path_params(Params) | codegen_handlers(Handlers)] %% codegen handler and middleware
    ).

   
codegen_route_pattern(Path, Method) ->
    erl_syntax:match_expr(
        erl_syntax:map_expr([
            erl_syntax:map_field_exact(
                erl_syntax:atom("path"),
                codegen_path(Path)
            ),
            erl_syntax:map_field_exact(
                erl_syntax:atom("method"),
                codegen_method(Method)
            )
        ]),
        erl_syntax:variable("Context0")
    ).

codegen_method(<<"_">>) ->
    erl_syntax:variable("_Method");
codegen_method(Method) ->
    binary_syntax(binary_to_list(Method)).

translate_path(Path) ->
    SplitPath = string:split(Path, "/", all),
    lists:map(fun translate_path_param/1, SplitPath).

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

-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)).

codegen_path(PathElements) ->
    do_codegen_path(lists:reverse(PathElements)).

do_codegen_path([all | Rest]) ->
    do_codegen_path(erl_syntax:variable("_"), Rest);
do_codegen_path([{all, Var} | Rest]) ->
    do_codegen_path(
        erl_syntax:variable(string:titlecase(atom_to_list(Var))),
        Rest
    );
do_codegen_path(PathElements) ->
    do_codegen_path(erl_syntax:nil(), PathElements).

do_codegen_path(Init, PathElements) ->
    lists:foldl(fun (PathElement, Acc) ->
        erl_syntax:cons(
            codegen_path_element(PathElement),
            Acc
        )
    end, Init, PathElements).

codegen_path_element(<<>>) ->
    erl_syntax:binary([]);
codegen_path_element({B, Var}) when is_binary(B) andalso is_atom(Var) ->
    erl_syntax:binary([
        erl_syntax:binary_field(erl_syntax:string(binary_to_list(B))),
        erl_syntax:binary_field(erl_syntax:variable(string:titlecase(atom_to_list(Var))))
    ]);
codegen_path_element(PathElement) when is_atom(PathElement) ->
    erl_syntax:variable(string:titlecase(atom_to_list(PathElement)));
codegen_path_element(PathElement) when is_binary(PathElement) ->
    binary_syntax(binary_to_list(PathElement)).


codegen_path_params(Params) ->
    erl_syntax:match_expr(
        erl_syntax:variable("Context1"),
        erl_syntax:map_expr(
            erl_syntax:variable("Context0"),
            [erl_syntax:map_field_assoc(
                erl_syntax:atom("params"),
                erl_syntax:map_expr(
                    lists:map(fun codegen_path_param/1, Params)
                )
            )]
        )
    ).

codegen_path_param(Param) ->
    erl_syntax:map_field_assoc(
        erl_syntax:atom(atom_to_list(Param)),
        erl_syntax:variable(string:titlecase(atom_to_list(Param)))
    ).

codegen_handlers(Handlers) ->
    {EnumHandlersTemp, _} = enumerate_handlers(Handlers),
    [{InitHandler, InitN} | EnumHandlers] = EnumHandlersTemp, 
    lists:foldl(
        fun codegen_handler_case/2,
        [codegen_handler_call(InitHandler, InitN)],
        EnumHandlers
    ).

codegen_handler_case({{HandlerModule, HandlerFunction}, N}, Acc) ->
    [erl_syntax:case_expr(
        codegen_handler_call({HandlerModule, HandlerFunction}, N),
        [erl_syntax:clause(
            [erl_syntax:match_expr(
                erl_syntax:map_expr([
                    erl_syntax:map_field_exact(
                        erl_syntax:atom("resp"),
                        erl_syntax:variable("_Resp")
                    )   
                ]),
                erl_syntax:variable("Context" ++ integer_to_list(N + 1)))],
            [],
            [erl_syntax:variable("Context" ++ integer_to_list(N + 1))]
        ),
        erl_syntax:clause(
            [erl_syntax:variable("Context" ++ integer_to_list(N + 1))],
            [],
            Acc
        )
    ])].


codegen_handler_call({HandlerModule, HandlerFunction}, N) ->
    erl_syntax:application(
        erl_syntax:atom(atom_to_list(HandlerModule)),
        erl_syntax:atom(atom_to_list(HandlerFunction)),
        [erl_syntax:variable("Context" ++ integer_to_list(N))]
    ).

enumerate_handlers(Handlers) ->
    lists:foldl(fun (Handler, {Acc, Count}) ->
        {[{Handler, Count} | Acc], Count + 1}
    end, {[], 1}, Handlers).

binary_syntax(S) ->
    erl_syntax:binary([
       erl_syntax:binary_field(erl_syntax:string(S)) 
    ]).