use crate::{
    circuit_builder::CircuitBuilder,
    error::{Error, SpanRange},
    helper_cleaner::HelperCleaner,
    interpreter_builder::InterpreterBuilder,
};
use proc_macro2::TokenStream;
use quote::quote;

pub fn run_interpreter_on_module(module: &syn::ItemMod) -> (TokenStream, Vec<Error>) {
    let res = std::panic::catch_unwind(|| {
        let interpreter_builder = InterpreterBuilder::new(module);
        let mut interpreter = interpreter_builder.run();

        interpreter.handle_all_unhandled_items();
        if !interpreter.errors().is_empty() {
            let errors = interpreter.errors();
            return (quote! {}, errors);
        }
        let mut circuit_launcher = CircuitBuilder::new(interpreter);
        syn::visit::visit_item_mod(&mut circuit_launcher, module);
        if circuit_launcher.n_circuits == 0 {
            circuit_launcher.errors.push(Error::new(
                SpanRange::call_site(),
                "There should be an #[instruction] in every #[encrypted] module.",
            ));
        }
        circuit_launcher
            .write_post_build(module.ident.to_string())
            .expect("Failed to write post build (pre-profiles, types).");

        let tests = circuit_launcher.tests;
        let errors = circuit_launcher.errors;
        (tests, errors)
    });
    res.unwrap_or_else(|p| {
        (
            quote! {},
            vec![Error::new(
                SpanRange::call_site(),
                format!("The #[encrypted] macro panicked: {:?}.", p),
            )],
        )
    })
}

pub fn module_macro(input: TokenStream) -> TokenStream {
    let mut module = match syn::parse2(input.clone()) {
        Ok(module) => module,
        Err(e) => {
            let mut output = input;
            output.extend(e.to_compile_error());
            return output;
        }
    };

    let (tests, errors) = run_interpreter_on_module(&module);

    HelperCleaner::clean(&mut module);
    let mod_name = &module.ident;
    let mod_visibility = &module.vis;
    let content = module.content.unwrap();
    let mod_content = content.1.iter();
    //let mod_unsafety = &module.unsafety;
    //let mod_attrs = &module.attrs;
    quote! {
        #mod_visibility mod #mod_name {
            #(#mod_content)*
            #(#errors)*
            #[cfg(test)]
            mod tests {
                use super::*;
                #tests
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[ignore = "used for debugging only"]
    #[test]
    fn it_works() {
        let token_stream = quote! {
            mod blabla {
                #[instruction]
                fn gen_integers(max: u128) -> (u128, bool) {
                    ArcisRNG::gen_integer_in_range(0, max, 8)
                }
            }
        };

        let result = module_macro(token_stream);
        let syntax_tree = syn::parse_file(&result.to_string()).unwrap();
        let formatted = prettyplease::unparse(&syntax_tree);
        print!("{}", formatted);
    }
}
