use rustc_type_ir::data_structures::DelayedMap;
use rustc_type_ir::inherent::*;
use rustc_type_ir::{
    self as ty, InferCtxtLike, Interner, TypeFoldable, TypeFolder, TypeSuperFoldable,
    TypeVisitableExt,
};

use crate::delegate::SolverDelegate;

///////////////////////////////////////////////////////////////////////////
// EAGER RESOLUTION

/// Resolves ty, region, and const vars to their inferred values or their root vars.
struct EagerResolver<'a, D, I = <D as SolverDelegate>::Interner>
where
    D: SolverDelegate<Interner = I>,
    I: Interner,
{
    delegate: &'a D,
    /// We're able to use a cache here as the folder does not have any
    /// mutable state.
    cache: DelayedMap<I::Ty, I::Ty>,
}

pub fn eager_resolve_vars<D: SolverDelegate, T: TypeFoldable<D::Interner>>(
    delegate: &D,
    value: T,
) -> T {
    if value.has_infer() {
        let mut folder = EagerResolver::new(delegate);
        value.fold_with(&mut folder)
    } else {
        value
    }
}

impl<'a, D: SolverDelegate> EagerResolver<'a, D> {
    fn new(delegate: &'a D) -> Self {
        EagerResolver { delegate, cache: Default::default() }
    }
}

impl<D: SolverDelegate<Interner = I>, I: Interner> TypeFolder<I> for EagerResolver<'_, D> {
    fn cx(&self) -> I {
        self.delegate.cx()
    }

    fn fold_ty(&mut self, t: I::Ty) -> I::Ty {
        match t.kind() {
            ty::Infer(ty::TyVar(vid)) => {
                let resolved = self.delegate.opportunistic_resolve_ty_var(vid);
                if t != resolved && resolved.has_infer() {
                    resolved.fold_with(self)
                } else {
                    resolved
                }
            }
            ty::Infer(ty::IntVar(vid)) => self.delegate.opportunistic_resolve_int_var(vid),
            ty::Infer(ty::FloatVar(vid)) => self.delegate.opportunistic_resolve_float_var(vid),
            _ => {
                if t.has_infer() {
                    if let Some(&ty) = self.cache.get(&t) {
                        return ty;
                    }
                    let res = t.super_fold_with(self);
                    assert!(self.cache.insert(t, res));
                    res
                } else {
                    t
                }
            }
        }
    }

    fn fold_region(&mut self, r: I::Region) -> I::Region {
        match r.kind() {
            ty::ReVar(vid) => self.delegate.opportunistic_resolve_lt_var(vid),
            _ => r,
        }
    }

    fn fold_const(&mut self, c: I::Const) -> I::Const {
        match c.kind() {
            ty::ConstKind::Infer(ty::InferConst::Var(vid)) => {
                let resolved = self.delegate.opportunistic_resolve_ct_var(vid);
                if c != resolved && resolved.has_infer() {
                    resolved.fold_with(self)
                } else {
                    resolved
                }
            }
            _ => {
                if c.has_infer() {
                    c.super_fold_with(self)
                } else {
                    c
                }
            }
        }
    }

    fn fold_predicate(&mut self, p: I::Predicate) -> I::Predicate {
        if p.has_infer() { p.super_fold_with(self) } else { p }
    }

    fn fold_clauses(&mut self, c: I::Clauses) -> I::Clauses {
        if c.has_infer() { c.super_fold_with(self) } else { c }
    }
}
