use crate::{
    error::Error,
    interpreter::{
        generic_context::{GenericContext, MatchKind},
        interpreter_statics::Struct,
        scope::{AllScopeInfo, Scope, ScopedCollection},
        supported_type::{SupportedType, TypedStruct},
        Interpreter,
    },
};
use quote::ToTokens;
use std::{
    collections::HashMap,
    ops::{Deref, DerefMut},
};

#[derive(Debug, Clone, Copy)]
pub enum UnHandledType<'ast> {
    Impl {
        ty: &'ast syn::Type,
        scope: Scope<'ast>,
        generics: Option<&'ast syn::Generics>,
    },
    Struct(usize),
}

impl<'ast> UnHandledType<'ast> {
    pub fn new_impl(
        ty: &'ast syn::Type,
        scope: Scope<'ast>,
        generics: Option<&'ast syn::Generics>,
    ) -> Self {
        UnHandledType::Impl {
            ty,
            scope,
            generics,
        }
    }
    pub fn new_struct(ty: usize) -> Self {
        UnHandledType::Struct(ty)
    }
}

pub struct Typing;

#[derive(Debug, Clone)]
pub struct TypeAndGenerics<'ast> {
    pub ty: SupportedType,
    pub generics: Option<&'ast syn::Generics>,
}

impl<'ast> TypeAndGenerics<'ast> {
    pub fn new(ty: SupportedType, generics: Option<&'ast syn::Generics>) -> Self {
        TypeAndGenerics { ty, generics }
    }
    pub fn destructure(self) -> (SupportedType, Option<&'ast syn::Generics>) {
        (self.ty, self.generics)
    }
}

impl Deref for TypeAndGenerics<'_> {
    type Target = SupportedType;
    fn deref(&self) -> &Self::Target {
        &self.ty
    }
}

impl DerefMut for TypeAndGenerics<'_> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.ty
    }
}

impl<'ast> ProgressKind<'ast, UnHandledType<'ast>, TypeAndGenerics<'ast>> for Typing {
    fn get_contents<'a>(
        interpreter: &'a mut Interpreter<'ast>,
    ) -> &'a mut Vec<Progress<UnHandledType<'ast>, TypeAndGenerics<'ast>>> {
        &mut interpreter.types
    }

    fn transform(
        interpreter: &mut Interpreter<'ast>,
        input: UnHandledType<'ast>,
    ) -> Result<TypeAndGenerics<'ast>, Error> {
        let res = match input {
            UnHandledType::Impl {
                scope,
                ty,
                generics,
            } => interpreter.with_scope(scope, |interpreter| {
                interpreter
                    .type_from_syn_type(ty, None)
                    .map(|ty| TypeAndGenerics::new(ty, generics))
            })?,
            UnHandledType::Struct(a) => {
                let struct_id = Struct::get_from_index(interpreter, a, None)?;
                let v = struct_id.get_field_types().to_vec();
                let generics = struct_id
                    .get_generic_id()
                    .map(|t| interpreter.struct_generics[t]);
                let ty = SupportedType::Struct(TypedStruct::new(struct_id, v));
                TypeAndGenerics::new(ty, generics)
            }
        };
        Ok(res)
    }
}

#[derive(Debug, Clone, Copy)]
pub enum Namespace<'ast> {
    Scope(Scope<'ast>),
    Type(usize),
    Forbidden(&'static str),
}

#[derive(Debug, Clone)]
pub enum Progress<I, O> {
    Input(I),
    Computing,
    Output(O),
}

#[derive(Debug, Clone)]
pub struct NamespacedCollection<'ast, T> {
    scoped: ScopedCollection<'ast, T>,
    typed: HashMap<syn::Ident, Vec<(usize, T)>>,
}

impl<T> Default for NamespacedCollection<'_, T> {
    fn default() -> Self {
        NamespacedCollection {
            scoped: Default::default(),
            typed: Default::default(),
        }
    }
}

impl<'ast, T> NamespacedCollection<'ast, T> {
    fn get_from_scope(
        &self,
        ident: &syn::Ident,
        scope: Scope<'ast>,
        all_scope_info: &AllScopeInfo<'ast>,
    ) -> Result<&T, &'static str> {
        self.scoped.get(ident, scope, all_scope_info)
    }
    fn get_from_type(&self, ident: &syn::Ident) -> Result<&[(usize, T)], &'static str> {
        self.typed
            .get(ident)
            .map(|x| x.as_slice())
            .ok_or("no object with that name")
    }
    pub fn insert_with_scope(&mut self, ident: syn::Ident, scope: Scope<'ast>, item: T) {
        self.scoped.insert(ident, scope, item);
    }
    pub fn insert_with_type(&mut self, ident: syn::Ident, type_set: usize, item: T) {
        let v = self.typed.entry(ident).or_default();
        v.push((type_set, item));
    }
}

impl<I, O: Clone> Progress<I, O> {
    pub fn get(&mut self) -> Self {
        match self {
            Progress::Input(_) => std::mem::replace(self, Progress::Computing),
            Progress::Computing => Progress::Computing,
            Progress::Output(ref o) => Progress::Output(o.clone()),
        }
    }
    pub fn fulfill(&mut self, output: O) {
        assert!(matches!(self, Progress::Computing));
        *self = Progress::Output(output);
    }
}

pub struct Namespaces;

impl<'ast> NamespacedKind<'ast, Namespace<'ast>> for Namespaces {
    fn name() -> &'static str {
        "Namespaces"
    }

    fn get_index_mut<'a>(
        interpreter: &'a mut Interpreter<'ast>,
    ) -> &'a mut NamespacedCollection<'ast, Namespace<'ast>> {
        &mut interpreter.namespaces
    }

    fn get_index<'a>(
        interpreter: &'a Interpreter<'ast>,
    ) -> &'a NamespacedCollection<'ast, Namespace<'ast>> {
        &interpreter.namespaces
    }
}

pub fn find_namespace_of_prefix<'ast>(
    interpreter: &mut Interpreter<'ast>,
    generic_context: &mut GenericContext,
    path: &'ast syn::Path,
) -> Result<
    (
        Namespace<'ast>,
        Option<&'ast syn::AngleBracketedGenericArguments>,
    ),
    Error,
> {
    let mut namespace = Namespace::Scope(interpreter.scope_info.current_scope());
    let n = path.segments.len();
    let mut old_turbofish = None;
    for segment in path.segments.iter().take(n - 1) {
        namespace = Namespaces::get_from_namespace(
            interpreter,
            generic_context,
            &segment.ident,
            namespace,
            old_turbofish,
        )?;
        old_turbofish = if let syn::PathArguments::AngleBracketed(a) = &segment.arguments {
            Some(a)
        } else {
            None
        };
    }
    Ok((namespace, old_turbofish))
}
pub trait ProgressKind<'ast, I, O: Clone> {
    fn get_contents<'a>(interpreter: &'a mut Interpreter<'ast>) -> &'a mut Vec<Progress<I, O>>;
    fn transform(interpreter: &mut Interpreter<'ast>, input: I) -> Result<O, Error>;
    fn insert_inner(interpreter: &mut Interpreter<'ast>, item: Progress<I, O>) -> usize {
        let contents = Self::get_contents(interpreter);
        let n = contents.len();
        contents.push(item);
        n
    }
    fn get_from_index(
        interpreter: &mut Interpreter<'ast>,
        index: usize,
        path: Option<&syn::Path>,
    ) -> Result<O, Error> {
        let progress = Self::get_contents(interpreter)[index].get();
        match progress {
            Progress::Input(i) => {
                let res = Self::transform(interpreter, i)?;
                Self::get_contents(interpreter)[index].fulfill(res.clone());
                Ok(res)
            }
            Progress::Computing => {
                let err_str = match path {
                    None => "An object is impossible to compute: see other errors.".to_string(),
                    Some(p) => format!(
                        "Impossible to compute `{}`: recursive definition.",
                        p.to_token_stream()
                    ),
                };
                let e = Error::new_spanned(path, err_str);
                Err(e)
            }
            Progress::Output(o) => Ok(o),
        }
    }
    fn handle_all(interpreter: &mut Interpreter<'ast>) {
        let len = Self::get_contents(interpreter).len();
        for idx in 0..len {
            if let Err(e) = Self::get_from_index(interpreter, idx, None) {
                interpreter.errors.push(e)
            }
        }
    }
}

pub trait NamespacedKind<'ast, T: Clone> {
    fn name() -> &'static str;
    fn get_index_mut<'a>(
        interpreter: &'a mut Interpreter<'ast>,
    ) -> &'a mut NamespacedCollection<'ast, T>;
    fn get_index<'a>(interpreter: &'a Interpreter<'ast>) -> &'a NamespacedCollection<'ast, T>;

    fn insert_with_namespace(
        interpreter: &mut Interpreter<'ast>,
        ident: syn::Ident,
        namespace: Namespace<'ast>,
        item: T,
    ) {
        let v = Self::get_index_mut(interpreter);
        match namespace {
            Namespace::Scope(s) => {
                v.insert_with_scope(ident, s, item);
            }
            Namespace::Type(t) => {
                v.insert_with_type(ident, t, item);
            }
            Namespace::Forbidden(_) => {}
        }
    }
    fn get_from_current_scope<'a>(
        interpreter: &'a mut Interpreter<'ast>,
        ident: &syn::Ident,
    ) -> Result<T, Error> {
        let scope = interpreter.scope_info.current_scope();
        Self::get_from_namespace(
            interpreter,
            &mut Default::default(),
            ident,
            Namespace::Scope(scope),
            None,
        )
    }
    fn get_from_namespace<'a>(
        interpreter: &'a mut Interpreter<'ast>,
        generic_context: &mut GenericContext,
        ident: &syn::Ident,
        namespace: Namespace<'ast>,
        turbofish: Option<&'ast syn::AngleBracketedGenericArguments>,
    ) -> Result<T, Error> {
        let to_syn_error = |e: &str| -> Error {
            Error::new_spanned(
                ident,
                format!(
                    "could not find segment `{}` in {} because {e}.",
                    ident,
                    Self::name()
                ),
            )
        };
        let res = match namespace {
            Namespace::Scope(scope) => Self::get_index(interpreter)
                .get_from_scope(ident, scope, &interpreter.scope_info)
                .map_err(to_syn_error)?
                .clone(),
            Namespace::Type(t) => {
                let v = Self::get_index(interpreter)
                    .get_from_type(ident)
                    .map_err(to_syn_error)?;
                let v = v.to_vec();
                let t_set = Typing::get_from_index(interpreter, t, None)?;
                let (mut t_set, generics) = t_set.destructure();
                interpreter
                    .refine_type(&mut t_set, false)
                    .expect("`may_fail` is set to false.");
                let turbo = interpreter.evaluate_turbofish(turbofish, generics, turbofish)?;
                interpreter.with_turbofish(
                    turbo,
                    generics,
                    generic_context,
                    turbofish,
                    |interpreter| {
                        interpreter
                            .refine_type(&mut t_set, false)
                            .expect("`may_fail` is set to false again.");
                        Ok(())
                    },
                )?;

                let mut res = None;
                for (m, space) in v {
                    let m_set = Typing::get_from_index(interpreter, m, None)?;
                    if generic_context.includes_type(
                        &mut interpreter.memory,
                        &m_set,
                        &t_set,
                        MatchKind::NoDeref,
                    ) {
                        if res.is_some() {
                            return Err(to_syn_error("multiple objects apply"));
                        }
                        res = Some(space);
                    }
                }
                let Some(obj) = res else {
                    return Err(to_syn_error("no objects found"));
                };

                obj
            }
            Namespace::Forbidden(s) => return Err(to_syn_error(s)),
        };
        Ok(res)
    }
    fn find_in_index(
        interpreter: &mut Interpreter<'ast>,
        path: &'ast syn::Path,
    ) -> Result<(T, GenericContext), Error> {
        let mut generic_context = GenericContext::default();
        let (namespace, turbofish) =
            find_namespace_of_prefix(interpreter, &mut generic_context, path)?;
        let ident = &path.segments.last().expect("empty path").ident;
        let n = Self::get_from_namespace(
            interpreter,
            &mut generic_context,
            ident,
            namespace,
            turbofish,
        )?;
        Ok((n, generic_context))
    }
    fn exists(interpreter: &mut Interpreter<'ast>, ident: &syn::Ident) -> bool {
        Self::get_index(interpreter)
            .get_from_scope(
                ident,
                interpreter.scope_info.current_scope(),
                &interpreter.scope_info,
            )
            .is_ok()
    }
    fn find_with_ident_and_type(
        interpreter: &mut Interpreter<'ast>,
        ident: &syn::Ident,
        ty: &SupportedType,
    ) -> Result<(T, GenericContext), Error> {
        let v = Self::get_index(interpreter)
            .get_from_type(ident)
            .map_err(|_e| Error::new_spanned(ident, format!("Unknown method name `{}`.", ident)))?;
        let mut generic_context = GenericContext::default();
        let v = v.to_vec();
        let mut min_deref = usize::MAX;
        let mut res = None;
        for (m, space) in v {
            let m_set = Typing::get_from_index(interpreter, m, None)?;
            let mut n_deref = 0;
            if generic_context.includes_type(
                &mut interpreter.memory,
                &m_set,
                ty,
                MatchKind::CountDeref(&mut n_deref),
            ) {
                if n_deref < min_deref {
                    min_deref = n_deref;
                    res = Some(space);
                } else if n_deref == min_deref {
                    res = None;
                }
            }
        }
        let Some(obj) = res else {
            return if min_deref == usize::MAX {
                Err(Error::new_spanned(
                    ident,
                    format!("Methods known as `{}` do not apply to object.", ident),
                ))
            } else {
                Err(Error::new_spanned(
                    ident,
                    format!("Multiple methods known as `{}`.", ident),
                ))
            };
        };

        Ok((obj, generic_context))
    }
}

pub trait ScopedProgressKind<'ast, I, O: Clone>:
    ProgressKind<'ast, I, O> + NamespacedKind<'ast, usize>
{
    fn insert_unhandled(interpreter: &mut Interpreter<'ast>, ident: syn::Ident, item: I) -> usize {
        let n = Self::insert_inner(interpreter, Progress::Input(item));
        let scope = interpreter.scope_info.current_scope();
        Self::get_index_mut(interpreter).insert_with_scope(ident, scope, n);
        n
    }
    fn insert_unhandled_in_type(
        interpreter: &mut Interpreter<'ast>,
        ident: syn::Ident,
        item: I,
        ty: usize,
    ) -> usize {
        let n = Self::insert_inner(interpreter, Progress::Input(item));
        Self::get_index_mut(interpreter).insert_with_type(ident, ty, n);
        n
    }
    fn insert_handled_in_type(
        interpreter: &mut Interpreter<'ast>,
        ident: syn::Ident,
        item: O,
        ty: usize,
    ) -> usize {
        let n = Self::insert_inner(interpreter, Progress::Output(item));
        Self::get_index_mut(interpreter).insert_with_type(ident, ty, n);
        n
    }
    fn insert_handled(interpreter: &mut Interpreter<'ast>, ident: syn::Ident, item: O) -> usize {
        let n = Self::insert_inner(interpreter, Progress::Output(item));
        let scope = interpreter.scope_info.current_scope();
        Self::get_index_mut(interpreter).insert_with_scope(ident, scope, n);
        n
    }

    fn get(
        interpreter: &mut Interpreter<'ast>,
        path: &'ast syn::Path,
    ) -> Result<(O, GenericContext), Error> {
        let (n, ctx) = Self::find_in_index(interpreter, path)?;
        match Self::get_from_index(interpreter, n, Some(path)) {
            Ok(obj) => Ok((obj, ctx)),
            Err(e) => Err(e),
        }
    }
    fn get_with_ident_and_type(
        interpreter: &mut Interpreter<'ast>,
        ident: &syn::Ident,
        ty: &SupportedType,
    ) -> Result<(O, GenericContext), Error> {
        let (n, ctx) = Self::find_with_ident_and_type(interpreter, ident, ty)?;
        match Self::get_from_index(interpreter, n, None) {
            Ok(obj) => Ok((obj, ctx)),
            Err(e) => Err(e),
        }
    }
}

impl<'ast, I, O: Clone, T: ProgressKind<'ast, I, O> + NamespacedKind<'ast, usize>>
    ScopedProgressKind<'ast, I, O> for T
{
}
