//! This module contains code to instantiate new values into a
//! `Canonical<'db, T>`.
//!
//! For an overview of what canonicalization is and how it fits into
//! rustc, check out the [chapter in the rustc dev guide][c].
//!
//! [c]: https://rust-lang.github.io/chalk/book/canonical_queries/canonicalization.html

use std::{fmt::Debug, iter};

use crate::next_solver::{
    BoundConst, BoundRegion, BoundTy, Canonical, CanonicalVarKind, CanonicalVarValues, Clauses,
    Const, ConstKind, DbInterner, GenericArg, ParamEnv, Predicate, Region, RegionKind, Ty, TyKind,
    fold::FnMutDelegate,
    infer::{
        InferCtxt, InferOk, InferResult,
        canonical::{QueryRegionConstraints, QueryResponse, canonicalizer::OriginalQueryValues},
        opaque_types::table::OpaqueTypeStorageEntries,
        traits::{ObligationCause, PredicateObligations},
    },
};
use rustc_hash::FxHashMap;
use rustc_index::{Idx as _, IndexVec};
use rustc_type_ir::{
    BoundVar, BoundVarIndexKind, GenericArgKind, TypeFlags, TypeFoldable, TypeFolder,
    TypeSuperFoldable, TypeVisitableExt, UniverseIndex,
    inherent::{GenericArg as _, IntoKind},
};
use tracing::{debug, instrument};

pub trait CanonicalExt<'db, V> {
    fn instantiate(&self, tcx: DbInterner<'db>, var_values: &CanonicalVarValues<'db>) -> V
    where
        V: TypeFoldable<DbInterner<'db>>;
    fn instantiate_projected<T>(
        &self,
        tcx: DbInterner<'db>,
        var_values: &CanonicalVarValues<'db>,
        projection_fn: impl FnOnce(&V) -> T,
    ) -> T
    where
        T: TypeFoldable<DbInterner<'db>>;
}

/// FIXME(-Znext-solver): This or public because it is shared with the
/// new trait solver implementation. We should deduplicate canonicalization.
impl<'db, V> CanonicalExt<'db, V> for Canonical<'db, V> {
    /// Instantiate the wrapped value, replacing each canonical value
    /// with the value given in `var_values`.
    fn instantiate(&self, tcx: DbInterner<'db>, var_values: &CanonicalVarValues<'db>) -> V
    where
        V: TypeFoldable<DbInterner<'db>>,
    {
        self.instantiate_projected(tcx, var_values, |value| value.clone())
    }

    /// Allows one to apply a instantiation to some subset of
    /// `self.value`. Invoke `projection_fn` with `self.value` to get
    /// a value V that is expressed in terms of the same canonical
    /// variables bound in `self` (usually this extracts from subset
    /// of `self`). Apply the instantiation `var_values` to this value
    /// V, replacing each of the canonical variables.
    fn instantiate_projected<T>(
        &self,
        tcx: DbInterner<'db>,
        var_values: &CanonicalVarValues<'db>,
        projection_fn: impl FnOnce(&V) -> T,
    ) -> T
    where
        T: TypeFoldable<DbInterner<'db>>,
    {
        assert_eq!(self.var_kinds.len(), var_values.len());
        let value = projection_fn(&self.value);
        instantiate_value(tcx, var_values, value)
    }
}

/// Instantiate the values from `var_values` into `value`. `var_values`
/// must be values for the set of canonical variables that appear in
/// `value`.
pub(super) fn instantiate_value<'db, T>(
    tcx: DbInterner<'db>,
    var_values: &CanonicalVarValues<'db>,
    value: T,
) -> T
where
    T: TypeFoldable<DbInterner<'db>>,
{
    if var_values.var_values.is_empty() {
        value
    } else {
        let delegate = FnMutDelegate {
            regions: &mut |br: BoundRegion<'db>| match var_values[br.var].kind() {
                GenericArgKind::Lifetime(l) => l,
                r => panic!("{br:?} is a region but value is {r:?}"),
            },
            types: &mut |bound_ty: BoundTy<'db>| match var_values[bound_ty.var].kind() {
                GenericArgKind::Type(ty) => ty,
                r => panic!("{bound_ty:?} is a type but value is {r:?}"),
            },
            consts: &mut |bound_ct: BoundConst<'db>| match var_values[bound_ct.var].kind() {
                GenericArgKind::Const(ct) => ct,
                c => panic!("{bound_ct:?} is a const but value is {c:?}"),
            },
        };

        let value = tcx.replace_escaping_bound_vars_uncached(value, delegate);
        value.fold_with(&mut CanonicalInstantiator {
            tcx,
            var_values: var_values.var_values.as_slice(),
            cache: Default::default(),
        })
    }
}

/// Replaces the bound vars in a canonical binder with var values.
struct CanonicalInstantiator<'db, 'a> {
    tcx: DbInterner<'db>,

    // The values that the bound vars are being instantiated with.
    var_values: &'a [GenericArg<'db>],

    // Because we use `BoundVarIndexKind::Canonical`, we can cache
    // based only on the entire ty, not worrying about a `DebruijnIndex`
    cache: FxHashMap<Ty<'db>, Ty<'db>>,
}

impl<'db, 'a> TypeFolder<DbInterner<'db>> for CanonicalInstantiator<'db, 'a> {
    fn cx(&self) -> DbInterner<'db> {
        self.tcx
    }

    fn fold_ty(&mut self, t: Ty<'db>) -> Ty<'db> {
        match t.kind() {
            TyKind::Bound(BoundVarIndexKind::Canonical, bound_ty) => {
                self.var_values[bound_ty.var.as_usize()].expect_ty()
            }
            _ => {
                if !t.has_type_flags(TypeFlags::HAS_CANONICAL_BOUND) {
                    t
                } else if let Some(&t) = self.cache.get(&t) {
                    t
                } else {
                    let res = t.super_fold_with(self);
                    assert!(self.cache.insert(t, res).is_none());
                    res
                }
            }
        }
    }

    fn fold_region(&mut self, r: Region<'db>) -> Region<'db> {
        match r.kind() {
            RegionKind::ReBound(BoundVarIndexKind::Canonical, br) => {
                self.var_values[br.var.as_usize()].expect_region()
            }
            _ => r,
        }
    }

    fn fold_const(&mut self, ct: Const<'db>) -> Const<'db> {
        match ct.kind() {
            ConstKind::Bound(BoundVarIndexKind::Canonical, bound_const) => {
                self.var_values[bound_const.var.as_usize()].expect_const()
            }
            _ => ct.super_fold_with(self),
        }
    }

    fn fold_predicate(&mut self, p: Predicate<'db>) -> Predicate<'db> {
        if p.has_type_flags(TypeFlags::HAS_CANONICAL_BOUND) { p.super_fold_with(self) } else { p }
    }

    fn fold_clauses(&mut self, c: Clauses<'db>) -> Clauses<'db> {
        if !c.has_type_flags(TypeFlags::HAS_CANONICAL_BOUND) {
            return c;
        }

        // FIXME: We might need cache here for perf like rustc
        c.super_fold_with(self)
    }
}

impl<'db> InferCtxt<'db> {
    /// A version of `make_canonicalized_query_response` that does
    /// not pack in obligations, for contexts that want to drop
    /// pending obligations instead of treating them as an ambiguity (e.g.
    /// typeck "probing" contexts).
    ///
    /// If you DO want to keep track of pending obligations (which
    /// include all region obligations, so this includes all cases
    /// that care about regions) with this function, you have to
    /// do it yourself, by e.g., having them be a part of the answer.
    pub fn make_query_response_ignoring_pending_obligations<T>(
        &self,
        inference_vars: CanonicalVarValues<'db>,
        answer: T,
        prev_entries: OpaqueTypeStorageEntries,
    ) -> Canonical<'db, QueryResponse<'db, T>>
    where
        T: TypeFoldable<DbInterner<'db>>,
    {
        // While we ignore region constraints and pending obligations,
        // we do return constrained opaque types to avoid unconstrained
        // inference variables in the response. This is important as we want
        // to check that opaques in deref steps stay unconstrained.
        //
        // This doesn't handle the more general case for non-opaques as
        // ambiguous `Projection` obligations have same the issue.
        let opaque_types = self
            .inner
            .borrow_mut()
            .opaque_type_storage
            .opaque_types_added_since(prev_entries)
            .map(|(k, v)| (k, v.ty))
            .collect();

        self.canonicalize_response(QueryResponse {
            var_values: inference_vars,
            region_constraints: QueryRegionConstraints::default(),
            opaque_types,
            value: answer,
        })
    }

    /// Given the (canonicalized) result to a canonical query,
    /// instantiates the result so it can be used, plugging in the
    /// values from the canonical query. (Note that the result may
    /// have been ambiguous; you should check the certainty level of
    /// the query before applying this function.)
    ///
    /// To get a good understanding of what is happening here, check
    /// out the [chapter in the rustc dev guide][c].
    ///
    /// [c]: https://rust-lang.github.io/chalk/book/canonical_queries/canonicalization.html#processing-the-canonicalized-query-result
    pub fn instantiate_query_response_and_region_obligations<R>(
        &self,
        cause: &ObligationCause,
        param_env: ParamEnv<'db>,
        original_values: &OriginalQueryValues<'db>,
        query_response: &Canonical<'db, QueryResponse<'db, R>>,
    ) -> InferResult<'db, R>
    where
        R: TypeFoldable<DbInterner<'db>>,
    {
        let InferOk { value: result_args, obligations } =
            self.query_response_instantiation(cause, param_env, original_values, query_response)?;

        for predicate in &query_response.value.region_constraints.outlives {
            let predicate = instantiate_value(self.interner, &result_args, *predicate);
            self.register_outlives_constraint(predicate);
        }

        for assumption in &query_response.value.region_constraints.assumptions {
            let assumption = instantiate_value(self.interner, &result_args, *assumption);
            self.register_region_assumption(assumption);
        }

        let user_result: R =
            query_response
                .instantiate_projected(self.interner, &result_args, |q_r| q_r.value.clone());

        Ok(InferOk { value: user_result, obligations })
    }

    /// Given the original values and the (canonicalized) result from
    /// computing a query, returns an instantiation that can be applied
    /// to the query result to convert the result back into the
    /// original namespace.
    ///
    /// The instantiation also comes accompanied with subobligations
    /// that arose from unification; these might occur if (for
    /// example) we are doing lazy normalization and the value
    /// assigned to a type variable is unified with an unnormalized
    /// projection.
    fn query_response_instantiation<R>(
        &self,
        cause: &ObligationCause,
        param_env: ParamEnv<'db>,
        original_values: &OriginalQueryValues<'db>,
        query_response: &Canonical<'db, QueryResponse<'db, R>>,
    ) -> InferResult<'db, CanonicalVarValues<'db>>
    where
        R: Debug + TypeFoldable<DbInterner<'db>>,
    {
        debug!(
            "query_response_instantiation(original_values={:#?}, query_response={:#?})",
            original_values, query_response,
        );

        let mut value = self.query_response_instantiation_guess(
            cause,
            param_env,
            original_values,
            query_response,
        )?;

        value.obligations.extend(
            self.unify_query_response_instantiation_guess(
                cause,
                param_env,
                original_values,
                &value.value,
                query_response,
            )?
            .into_obligations(),
        );

        Ok(value)
    }

    /// Given the original values and the (canonicalized) result from
    /// computing a query, returns a **guess** at an instantiation that
    /// can be applied to the query result to convert the result back
    /// into the original namespace. This is called a **guess**
    /// because it uses a quick heuristic to find the values for each
    /// canonical variable; if that quick heuristic fails, then we
    /// will instantiate fresh inference variables for each canonical
    /// variable instead. Therefore, the result of this method must be
    /// properly unified
    #[instrument(level = "debug", skip(self, param_env))]
    fn query_response_instantiation_guess<R>(
        &self,
        cause: &ObligationCause,
        param_env: ParamEnv<'db>,
        original_values: &OriginalQueryValues<'db>,
        query_response: &Canonical<'db, QueryResponse<'db, R>>,
    ) -> InferResult<'db, CanonicalVarValues<'db>>
    where
        R: Debug + TypeFoldable<DbInterner<'db>>,
    {
        // For each new universe created in the query result that did
        // not appear in the original query, create a local
        // superuniverse.
        let mut universe_map = original_values.universe_map.clone();
        let num_universes_in_query = original_values.universe_map.len();
        let num_universes_in_response = query_response.max_universe.as_usize() + 1;
        for _ in num_universes_in_query..num_universes_in_response {
            universe_map.push(self.create_next_universe());
        }
        assert!(!universe_map.is_empty()); // always have the root universe
        assert_eq!(universe_map[UniverseIndex::ROOT.as_usize()], UniverseIndex::ROOT);

        // Every canonical query result includes values for each of
        // the inputs to the query. Therefore, we begin by unifying
        // these values with the original inputs that were
        // canonicalized.
        let result_values = &query_response.value.var_values;
        assert_eq!(original_values.var_values.len(), result_values.len());

        // Quickly try to find initial values for the canonical
        // variables in the result in terms of the query. We do this
        // by iterating down the values that the query gave to each of
        // the canonical inputs. If we find that one of those values
        // is directly equal to one of the canonical variables in the
        // result, then we can type the corresponding value from the
        // input. See the example above.
        let mut opt_values: IndexVec<BoundVar, Option<GenericArg<'db>>> =
            IndexVec::from_elem_n(None, query_response.var_kinds.len());

        for (original_value, result_value) in iter::zip(&original_values.var_values, result_values)
        {
            match result_value.kind() {
                GenericArgKind::Type(result_value) => {
                    // We disable the instantiation guess for inference variables
                    // and only use it for placeholders. We need to handle the
                    // `sub_root` of type inference variables which would make this
                    // more involved. They are also a lot rarer than region variables.
                    if let TyKind::Bound(index_kind, b) = result_value.kind()
                        && !matches!(
                            query_response.var_kinds.as_slice()[b.var.as_usize()],
                            CanonicalVarKind::Ty { .. }
                        )
                    {
                        // We only allow a `Canonical` index in generic parameters.
                        assert!(matches!(index_kind, BoundVarIndexKind::Canonical));
                        opt_values[b.var] = Some(*original_value);
                    }
                }
                GenericArgKind::Lifetime(result_value) => {
                    if let RegionKind::ReBound(index_kind, b) = result_value.kind() {
                        // We only allow a `Canonical` index in generic parameters.
                        assert!(matches!(index_kind, BoundVarIndexKind::Canonical));
                        opt_values[b.var] = Some(*original_value);
                    }
                }
                GenericArgKind::Const(result_value) => {
                    if let ConstKind::Bound(index_kind, b) = result_value.kind() {
                        // We only allow a `Canonical` index in generic parameters.
                        assert!(matches!(index_kind, BoundVarIndexKind::Canonical));
                        opt_values[b.var] = Some(*original_value);
                    }
                }
            }
        }

        // Create result arguments: if we found a value for a
        // given variable in the loop above, use that. Otherwise, use
        // a fresh inference variable.
        let interner = self.interner;
        let variables = query_response.var_kinds;
        let var_values =
            CanonicalVarValues::instantiate(interner, variables, |var_values, kind| {
                if kind.universe() != UniverseIndex::ROOT {
                    // A variable from inside a binder of the query. While ideally these shouldn't
                    // exist at all, we have to deal with them for now.
                    self.instantiate_canonical_var(cause.span(), kind, var_values, |u| {
                        universe_map[u.as_usize()]
                    })
                } else if kind.is_existential() {
                    match opt_values[BoundVar::new(var_values.len())] {
                        Some(k) => k,
                        None => {
                            self.instantiate_canonical_var(cause.span(), kind, var_values, |u| {
                                universe_map[u.as_usize()]
                            })
                        }
                    }
                } else {
                    // For placeholders which were already part of the input, we simply map this
                    // universal bound variable back the placeholder of the input.
                    opt_values[BoundVar::new(var_values.len())]
                        .expect("expected placeholder to be unified with itself during response")
                }
            });

        let mut obligations = PredicateObligations::new();

        // Carry all newly resolved opaque types to the caller's scope
        for &(a, b) in &query_response.value.opaque_types {
            let a = instantiate_value(self.interner, &var_values, a);
            let b = instantiate_value(self.interner, &var_values, b);
            debug!(?a, ?b, "constrain opaque type");
            // We use equate here instead of, for example, just registering the
            // opaque type's hidden value directly, because the hidden type may have been an inference
            // variable that got constrained to the opaque type itself. In that case we want to equate
            // the generic args of the opaque with the generic params of its hidden type version.
            obligations.extend(
                self.at(cause, param_env)
                    .eq(Ty::new_opaque(self.interner, a.def_id.0, a.args), b)?
                    .obligations,
            );
        }

        Ok(InferOk { value: var_values, obligations })
    }

    /// Given a "guess" at the values for the canonical variables in
    /// the input, try to unify with the *actual* values found in the
    /// query result. Often, but not always, this is a no-op, because
    /// we already found the mapping in the "guessing" step.
    ///
    /// See also: [`Self::query_response_instantiation_guess`]
    fn unify_query_response_instantiation_guess<R>(
        &self,
        cause: &ObligationCause,
        param_env: ParamEnv<'db>,
        original_values: &OriginalQueryValues<'db>,
        result_args: &CanonicalVarValues<'db>,
        query_response: &Canonical<'db, QueryResponse<'db, R>>,
    ) -> InferResult<'db, ()>
    where
        R: Debug + TypeFoldable<DbInterner<'db>>,
    {
        // A closure that yields the result value for the given
        // canonical variable; this is taken from
        // `query_response.var_values` after applying the instantiation
        // by `result_args`.
        let instantiated_query_response = |index: BoundVar| -> GenericArg<'db> {
            query_response
                .instantiate_projected(self.interner, result_args, |v| v.var_values[index])
        };

        // Unify the original value for each variable with the value
        // taken from `query_response` (after applying `result_args`).
        self.unify_canonical_vars(cause, param_env, original_values, instantiated_query_response)
    }

    /// Given two sets of values for the same set of canonical variables, unify them.
    /// The second set is produced lazily by supplying indices from the first set.
    fn unify_canonical_vars(
        &self,
        cause: &ObligationCause,
        param_env: ParamEnv<'db>,
        variables1: &OriginalQueryValues<'db>,
        variables2: impl Fn(BoundVar) -> GenericArg<'db>,
    ) -> InferResult<'db, ()> {
        let mut obligations = PredicateObligations::new();
        for (index, value1) in variables1.var_values.iter().enumerate() {
            let value2 = variables2(BoundVar::new(index));

            match (value1.kind(), value2.kind()) {
                (GenericArgKind::Type(v1), GenericArgKind::Type(v2)) => {
                    obligations.extend(self.at(cause, param_env).eq(v1, v2)?.into_obligations());
                }
                (GenericArgKind::Lifetime(re1), GenericArgKind::Lifetime(re2))
                    if re1.is_erased() && re2.is_erased() =>
                {
                    // no action needed
                }
                (GenericArgKind::Lifetime(v1), GenericArgKind::Lifetime(v2)) => {
                    self.inner.borrow_mut().unwrap_region_constraints().make_eqregion(v1, v2);
                }
                (GenericArgKind::Const(v1), GenericArgKind::Const(v2)) => {
                    let ok = self.at(cause, param_env).eq(v1, v2)?;
                    obligations.extend(ok.into_obligations());
                }
                _ => {
                    panic!("kind mismatch, cannot unify {:?} and {:?}", value1, value2,);
                }
            }
        }
        Ok(InferOk { value: (), obligations })
    }
}
