use crate::{
    error::Error,
    interpreter::{
        inputs::InputsHandlingState,
        interpreter_conversion::ConversionKind,
        outputs,
        test_code::TestCode,
        Interpreter,
    },
    profile::Profile,
};
use arcis_compiler::core::{
    global_value::global_expr_store::{
        take_global_expr_store,
        with_global_expr_store_as_local,
        with_local_expr_store_as_global,
    },
    ir::IntermediateRepresentation,
};
use arcis_interface::{CircuitInterface, Value};
use proc_macro2::TokenStream;
use quote::ToTokens;

#[derive(Debug)]
pub struct InterpreterResult {
    ir: IntermediateRepresentation,
    test_body: TokenStream,
    inputs_ident: syn::Ident,
    outputs_ident: syn::Ident,
    interface: CircuitInterface,
    pre_profile: Profile,
}

impl InterpreterResult {
    fn new(
        ir: IntermediateRepresentation,
        test_body: TokenStream,
        inputs_ident: syn::Ident,
        outputs_ident: syn::Ident,
        interface: CircuitInterface,
        pre_profile: Profile,
    ) -> Self {
        Self {
            ir,
            test_body,
            inputs_ident,
            outputs_ident,
            interface,
            pre_profile,
        }
    }
    pub fn destructure(
        self,
    ) -> (
        IntermediateRepresentation,
        TokenStream,
        syn::Ident,
        syn::Ident,
        CircuitInterface,
        Profile,
    ) {
        (
            self.ir,
            self.test_body,
            self.inputs_ident,
            self.outputs_ident,
            self.interface,
            self.pre_profile,
        )
    }
}

impl<'ast> Interpreter<'ast> {
    pub fn start_and_run(
        &mut self,
        item_fn: &'ast syn::ItemFn,
    ) -> Result<InterpreterResult, Error> {
        with_global_expr_store_as_local(|global_expr_store| {
            let mut copy = global_expr_store.clone();
            let res = with_local_expr_store_as_global(
                || {
                    self.run_with_new_bindings(|interpreter| {
                        interpreter.actual_start_and_run(item_fn)
                    })
                },
                global_expr_store,
            );
            std::mem::swap(global_expr_store, &mut copy);
            res
        })
    }
    pub fn start_and_pass<T>(
        &mut self,
        item_fn: &'ast syn::ItemFn,
        func: &mut impl FnMut(&mut Interpreter<'ast>, &'ast T),
        extra_data: &'ast T,
    ) -> Result<(), Error> {
        with_global_expr_store_as_local(|global_expr_store| {
            let mut copy = global_expr_store.clone();
            let res = with_local_expr_store_as_global(
                || {
                    self.run_with_new_bindings(|interpreter| {
                        interpreter.deal_with_inputs(item_fn)?;
                        func(interpreter, extra_data);
                        Ok(())
                    })
                },
                global_expr_store,
            );
            std::mem::swap(global_expr_store, &mut copy);
            res
        })
    }
    fn deal_with_inputs(
        &mut self,
        item_fn: &'ast syn::ItemFn,
    ) -> Result<(TestCode, Vec<Value>), Error> {
        let inputs = &item_fn.sig.inputs;
        let mut input_handling_state = InputsHandlingState::new();
        let mut test_code = TestCode::new();
        let mut interface_inputs = Vec::with_capacity(inputs.len());
        for arg in inputs {
            let (pat, syn_ty, attrs) = self.handle_fn_arg(arg)?;
            let ty = self.type_from_syn_type(syn_ty, Some(attrs))?;

            let interface_value = ty.to_value().map_err(|x| {
                Error::new_spanned(
                    syn_ty,
                    format!("Cannot have {x} as instruction input type."),
                )
            })?;
            interface_inputs.push(interface_value);

            test_code.handle_input();

            let Some(pat) = pat else {
                return Err(Error::new_spanned(
                    arg,
                    "`self` is not allowed in instruction inputs.",
                ));
            };

            let address = input_handling_state
                .create_inputs(&mut self.memory, ty, pat.to_token_stream().to_string())
                .map_err(|e| Error::new_spanned(arg, e))?;

            self.apply_pat(pat, address.as_pointer(), false)?;
        }
        Ok((test_code, interface_inputs))
    }
    fn actual_start_and_run(
        &mut self,
        item_fn: &'ast syn::ItemFn,
    ) -> Result<InterpreterResult, Error> {
        let (test_code, interface_inputs) = self.deal_with_inputs(item_fn)?;
        let circuit_name = &item_fn.sig.ident;

        let block_output = self.run_block(&item_fn.block)?;
        let syn::ReturnType::Type(_, return_ty) = &item_fn.sig.output else {
            return Err(Error::new_spanned(
                &item_fn.sig,
                "Return type is required on instructions.",
            ));
        };
        let return_type = self.type_from_syn_type(return_ty, None)?;
        let func_output = self.conversion(
            block_output,
            &return_type,
            ConversionKind::Implicit,
            return_ty,
        )?;
        let ir_outputs = outputs::add_to_outputs(func_output, &mut self.memory, &item_fn.block)?;
        let expr_store = take_global_expr_store();
        let ir = expr_store.into_ir_with_new_tracking(ir_outputs, self.profile.sizes());
        let interface = CircuitInterface::new(
            circuit_name.to_string(),
            interface_inputs,
            vec![return_type.to_value().map_err(|x| {
                Error::new_spanned(
                    &item_fn.sig.output,
                    format!("Cannot have {x} as instruction return type."),
                )
            })?],
        );
        let (test_code, inputs_ident, return_value_ident) = test_code.finish(&item_fn.sig.ident);
        let pre_profile = std::mem::take(&mut self.profile);
        // println!("memory:\n{:#?}\nir:{ir}", self);
        Ok(InterpreterResult::new(
            ir,
            test_code,
            inputs_ident,
            return_value_ident,
            interface,
            pre_profile,
        ))
    }
}
