use crate::{
    error::Error,
    interpreter::{
        data::Data,
        data_store::{DataStore, MemoryLayer, SimpleAddress},
        fn_data::FnData,
        interpreter_statics::Functions,
        namespace::{NamespacedKind, ProgressKind},
        pointer::Pointer,
        Interpreter,
    },
};
use quote::ToTokens;
use std::collections::HashMap;
use syn::PathArguments;

impl<'ast> Interpreter<'ast> {
    pub fn new_binding_from_simple_address(&mut self, binding: syn::Ident, address: SimpleAddress) {
        self.bindings.insert(binding, address);
    }
    pub fn new_binding_from_pointer(
        &mut self,
        binding: &syn::Ident,
        pointer: Pointer,
    ) -> Result<(), Error> {
        let data = self.memory.compute(pointer)?;
        self.new_binding_from_data(binding, data);
        Ok(())
    }
    pub fn new_binding_from_data(&mut self, binding: &syn::Ident, data: Data) {
        let let_address = self.memory.push(data);
        self.new_binding_from_simple_address(binding.clone(), let_address);
    }
    pub fn run_but_keep_bindings<T>(&mut self, mut func: impl FnMut(&mut Self) -> T) -> T {
        let bindings = self.bindings.clone();
        let res = func(self);
        self.bindings = bindings;
        res
    }
    pub fn run_with_new_bindings<T>(&mut self, mut func: impl FnMut(&mut Self) -> T) -> T {
        let mut new_bindings = HashMap::new();
        std::mem::swap(&mut self.bindings, &mut new_bindings);
        let res = func(self);
        std::mem::swap(&mut self.bindings, &mut new_bindings);
        res
    }
    pub fn get_var(&mut self, var_name: &'ast syn::Path) -> Result<SimpleAddress, Error> {
        self.get_const(var_name).or_else(|e_consts| {
            if let Some(a) = var_name.get_ident().and_then(|x| self.bindings.get(x)) {
                Ok(*a)
            } else {
                Functions::find_in_index(self, var_name)
                    .and_then(|(f_id, ctx)| {
                        let f = Functions::get_from_index(self, f_id, Some(var_name))?;
                        let last_segment = var_name.segments.last().expect("This path was empty.");
                        let turbofish = match last_segment.arguments {
                            PathArguments::AngleBracketed(ref args) => Some(args),
                            _ => None,
                        };
                        let generics = f.generics();
                        let turbofish = self.evaluate_turbofish(turbofish, generics, var_name)?;
                        let addr = self
                            .memory
                            .push(Data::Fn(FnData::Simple(f_id, turbofish, ctx)));
                        Ok(addr)
                    })
                    .map_err(|e_functions| {
                        Error::new_spanned(
                            var_name,
                            format!(
                                "Could not find `{}`. Multiple reasons:\n{e_consts}\n{e_functions}",
                                var_name.to_token_stream()
                            ),
                        )
                    })
            }
        })
    }
    pub fn run_with_memory_layer<T>(
        &mut self,
        mut func: impl FnMut(&mut Self) -> T,
    ) -> (T, MemoryLayer) {
        let mut data_store: Box<dyn DataStore> = Box::new(Vec::new());
        self.memory.swap_backend(&mut data_store);
        let mut layered_data_store = data_store.add_layer();
        self.memory.swap_backend(&mut layered_data_store);
        let res = func(self);
        self.memory.swap_backend(&mut layered_data_store);
        let (mut data_store, layer) = layered_data_store.peel_layer();
        self.memory.swap_backend(&mut data_store);
        (res, layer)
    }

    pub fn handle_fn_arg(
        &self,
        arg: &'ast syn::FnArg,
    ) -> Result<
        (
            Option<&'ast syn::Pat>,
            &'ast syn::Type,
            &'ast [syn::Attribute],
        ),
        Error,
    > {
        match arg {
            syn::FnArg::Typed(syn::PatType { pat, ty, attrs, .. }) => {
                Ok((Some(pat.as_ref()), ty, attrs))
            }
            syn::FnArg::Receiver(r) => Ok((None, r.ty.as_ref(), &[])),
        }
    }
}
