extern crate proc_macro;
use proc_macro::{TokenStream, TokenTree};

#[proc_macro_attribute]
pub fn log(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let mut tokens = item.into_iter();

    let logger_function_name = "trace";
    let skip_types = &["&mut Cursive"];
    let log_id = utils::rng::random_numeric_string(3);

    let mut function_name = String::new();
    let mut do_function_name_check = true;
    let mut args: Vec<(String, String)> = Vec::new();

    let mut function_signature = String::new();
    let mut function_body = String::new();
    let mut token_start = String::new();
    let mut token_end = String::new();

    let mut in_block = false;

    while let Some(token) = tokens.next() {
        match &token {
            TokenTree::Group(group)
                if group.delimiter() == proc_macro::Delimiter::Brace && !in_block =>
            {
                let body = group.stream().to_string();

                let mut args_with_values = String::new();

                for (name, ty) in &args {
                    if skip_types.contains(&ty.as_str()) {
                        args_with_values.push_str(&format!("{name}: {ty}\n"));
                    } else {
                        args_with_values.push_str(&format!("{name}: {ty} = {{{name}:?}}\n"));
                    }
                }

                args_with_values = args_with_values.trim_end_matches("\n").to_string();

                token_start = format!(
                    r#"
    let args = format!("{args_with_values}");
    let log_instance_id = utils::rng::random_numeric_string(3);
    let message1 = format!("Running function call #{{log_instance_id}}\n{function_name}\n{{args}}");
    {logger_function_name}(&message1);"#
                );

                token_end = format!(
                    r#"
    let message2 = message1.replacen("Running", "Finished", 1);
    {logger_function_name}(&message2);"#
                );

                function_body = body;
                in_block = true;
            }
            TokenTree::Group(group) if group.delimiter() == proc_macro::Delimiter::Parenthesis => {
                let args_stream = group.stream().to_string();

                for arg in args_stream.split(", ") {
                    let Some((name, ty)) = arg.split_once(":") else {
                        continue;
                    };
                    args.push((name.trim().to_string(), ty.trim().to_string()));
                }
            }
            TokenTree::Ident(ident) if do_function_name_check => {
                if function_name == "fn" {
                    do_function_name_check = false;
                }
                function_name = ident.to_string();
            }

            _ => {}
        }

        if !in_block {
            function_signature.push_str(&token.to_string());
            function_signature.push(' ');
        }
    }

    function_signature = function_signature
        .replace("- >", "->")
        .replace(" : : ", "::")
        .replace(" < ", "<")
        .replace(" > ", ">")
        .replacen(&format!("{function_name} "), &function_name, 1);
    function_body = function_body.replace(";", ";\n");

    let mut arg_names_in_order = String::new();
    for (idx, (name, _)) in args.iter().enumerate() {
        arg_names_in_order.push_str(&name);

        if idx < args.len() - 1 {
            arg_names_in_order.push_str(", ");
        }
    }

    // The original function being wrapped
    let hidden_function_name = format!("hidden_{log_id}_{function_name}");
    let hidden_function_signature =
        function_signature.replacen(&function_name, &hidden_function_name, 1);

    let output = format!(
        r#"
{hidden_function_signature} {{
    {function_body}
}}

{function_signature} {{
    use logging::{logger_function_name};
    {token_start}
    let result = {hidden_function_name}({arg_names_in_order});
    {token_end}
    result
}}
    "#
    );

    output.parse().unwrap()
}
