use crate::infer::error_reporting::note_and_explain_region;
use crate::infer::{self, InferCtxt, SubregionOrigin};
use crate::middle::region;
use crate::ty::error::TypeError;
use crate::ty::{self, Region};
use rustc_errors::{struct_span_err, DiagnosticBuilder};

use rustc_error_codes::*;

impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
    pub(super) fn note_region_origin(
        &self,
        err: &mut DiagnosticBuilder<'_>,
        origin: &SubregionOrigin<'tcx>,
    ) {
        match *origin {
            infer::Subtype(ref trace) => {
                if let Some((expected, found)) = self.values_str(&trace.values) {
                    err.span_note(
                        trace.cause.span,
                        &format!("...so that the {}", trace.cause.as_requirement_str()),
                    );

                    err.note_expected_found(&"", expected, &"", found);
                } else {
                    // FIXME: this really should be handled at some earlier stage. Our
                    // handling of region checking when type errors are present is
                    // *terrible*.

                    err.span_note(
                        trace.cause.span,
                        &format!("...so that {}", trace.cause.as_requirement_str()),
                    );
                }
            }
            infer::Reborrow(span) => {
                err.span_note(span, "...so that reference does not outlive borrowed content");
            }
            infer::ReborrowUpvar(span, ref upvar_id) => {
                let var_name = self.tcx.hir().name(upvar_id.var_path.hir_id);
                err.span_note(span, &format!("...so that closure can access `{}`", var_name));
            }
            infer::InfStackClosure(span) => {
                err.span_note(span, "...so that closure does not outlive its stack frame");
            }
            infer::InvokeClosure(span) => {
                err.span_note(span, "...so that closure is not invoked outside its lifetime");
            }
            infer::DerefPointer(span) => {
                err.span_note(span, "...so that pointer is not dereferenced outside its lifetime");
            }
            infer::ClosureCapture(span, id) => {
                err.span_note(
                    span,
                    &format!(
                        "...so that captured variable `{}` does not outlive the \
                                        enclosing closure",
                        self.tcx.hir().name(id)
                    ),
                );
            }
            infer::IndexSlice(span) => {
                err.span_note(span, "...so that slice is not indexed outside the lifetime");
            }
            infer::RelateObjectBound(span) => {
                err.span_note(span, "...so that it can be closed over into an object");
            }
            infer::CallRcvr(span) => {
                err.span_note(span, "...so that method receiver is valid for the method call");
            }
            infer::CallArg(span) => {
                err.span_note(span, "...so that argument is valid for the call");
            }
            infer::CallReturn(span) => {
                err.span_note(span, "...so that return value is valid for the call");
            }
            infer::Operand(span) => {
                err.span_note(span, "...so that operand is valid for operation");
            }
            infer::AddrOf(span) => {
                err.span_note(span, "...so that reference is valid at the time of borrow");
            }
            infer::AutoBorrow(span) => {
                err.span_note(span, "...so that auto-reference is valid at the time of borrow");
            }
            infer::ExprTypeIsNotInScope(t, span) => {
                err.span_note(
                    span,
                    &format!(
                        "...so type `{}` of expression is valid during the \
                                        expression",
                        self.ty_to_string(t)
                    ),
                );
            }
            infer::BindingTypeIsNotValidAtDecl(span) => {
                err.span_note(span, "...so that variable is valid at time of its declaration");
            }
            infer::ParameterInScope(_, span) => {
                err.span_note(span, "...so that a type/lifetime parameter is in scope here");
            }
            infer::DataBorrowed(ty, span) => {
                err.span_note(
                    span,
                    &format!(
                        "...so that the type `{}` is not borrowed for too long",
                        self.ty_to_string(ty)
                    ),
                );
            }
            infer::ReferenceOutlivesReferent(ty, span) => {
                err.span_note(
                    span,
                    &format!(
                        "...so that the reference type `{}` does not outlive the \
                                        data it points at",
                        self.ty_to_string(ty)
                    ),
                );
            }
            infer::RelateParamBound(span, t) => {
                err.span_note(
                    span,
                    &format!(
                        "...so that the type `{}` will meet its required \
                                        lifetime bounds",
                        self.ty_to_string(t)
                    ),
                );
            }
            infer::RelateDefaultParamBound(span, t) => {
                err.span_note(
                    span,
                    &format!(
                        "...so that type parameter instantiated with `{}`, will \
                                        meet its declared lifetime bounds",
                        self.ty_to_string(t)
                    ),
                );
            }
            infer::RelateRegionParamBound(span) => {
                err.span_note(
                    span,
                    "...so that the declared lifetime parameter bounds are satisfied",
                );
            }
            infer::SafeDestructor(span) => {
                err.span_note(span, "...so that references are valid when the destructor runs");
            }
            infer::CompareImplMethodObligation { span, .. } => {
                err.span_note(
                    span,
                    "...so that the definition in impl matches the definition from the \
                               trait",
                );
            }
        }
    }

    pub(super) fn report_concrete_failure(
        &self,
        region_scope_tree: &region::ScopeTree,
        origin: SubregionOrigin<'tcx>,
        sub: Region<'tcx>,
        sup: Region<'tcx>,
    ) -> DiagnosticBuilder<'tcx> {
        match origin {
            infer::Subtype(box trace) => {
                let terr = TypeError::RegionsDoesNotOutlive(sup, sub);
                let mut err = self.report_and_explain_type_error(trace, &terr);
                note_and_explain_region(self.tcx, region_scope_tree, &mut err, "", sup, "...");
                note_and_explain_region(
                    self.tcx,
                    region_scope_tree,
                    &mut err,
                    "...does not necessarily outlive ",
                    sub,
                    "",
                );
                err
            }
            infer::Reborrow(span) => {
                let mut err = struct_span_err!(
                    self.tcx.sess,
                    span,
                    E0312,
                    "lifetime of reference outlives lifetime of \
                                                borrowed content..."
                );
                note_and_explain_region(
                    self.tcx,
                    region_scope_tree,
                    &mut err,
                    "...the reference is valid for ",
                    sub,
                    "...",
                );
                note_and_explain_region(
                    self.tcx,
                    region_scope_tree,
                    &mut err,
                    "...but the borrowed content is only valid for ",
                    sup,
                    "",
                );
                err
            }
            infer::ReborrowUpvar(span, ref upvar_id) => {
                let var_name = self.tcx.hir().name(upvar_id.var_path.hir_id);
                let mut err = struct_span_err!(
                    self.tcx.sess,
                    span,
                    E0313,
                    "lifetime of borrowed pointer outlives lifetime \
                                                of captured variable `{}`...",
                    var_name
                );
                note_and_explain_region(
                    self.tcx,
                    region_scope_tree,
                    &mut err,
                    "...the borrowed pointer is valid for ",
                    sub,
                    "...",
                );
                note_and_explain_region(
                    self.tcx,
                    region_scope_tree,
                    &mut err,
                    &format!("...but `{}` is only valid for ", var_name),
                    sup,
                    "",
                );
                err
            }
            infer::InfStackClosure(span) => {
                let mut err =
                    struct_span_err!(self.tcx.sess, span, E0314, "closure outlives stack frame");
                note_and_explain_region(
                    self.tcx,
                    region_scope_tree,
                    &mut err,
                    "...the closure must be valid for ",
                    sub,
                    "...",
                );
                note_and_explain_region(
                    self.tcx,
                    region_scope_tree,
                    &mut err,
                    "...but the closure's stack frame is only valid \
                                                  for ",
                    sup,
                    "",
                );
                err
            }
            infer::InvokeClosure(span) => {
                let mut err = struct_span_err!(
                    self.tcx.sess,
                    span,
                    E0315,
                    "cannot invoke closure outside of its lifetime"
                );
                note_and_explain_region(
                    self.tcx,
                    region_scope_tree,
                    &mut err,
                    "the closure is only valid for ",
                    sup,
                    "",
                );
                err
            }
            infer::DerefPointer(span) => {
                let mut err = struct_span_err!(
                    self.tcx.sess,
                    span,
                    E0473,
                    "dereference of reference outside its lifetime"
                );
                note_and_explain_region(
                    self.tcx,
                    region_scope_tree,
                    &mut err,
                    "the reference is only valid for ",
                    sup,
                    "",
                );
                err
            }
            infer::ClosureCapture(span, id) => {
                let mut err = struct_span_err!(
                    self.tcx.sess,
                    span,
                    E0474,
                    "captured variable `{}` does not outlive the \
                                                enclosing closure",
                    self.tcx.hir().name(id)
                );
                note_and_explain_region(
                    self.tcx,
                    region_scope_tree,
                    &mut err,
                    "captured variable is valid for ",
                    sup,
                    "",
                );
                note_and_explain_region(
                    self.tcx,
                    region_scope_tree,
                    &mut err,
                    "closure is valid for ",
                    sub,
                    "",
                );
                err
            }
            infer::IndexSlice(span) => {
                let mut err = struct_span_err!(
                    self.tcx.sess,
                    span,
                    E0475,
                    "index of slice outside its lifetime"
                );
                note_and_explain_region(
                    self.tcx,
                    region_scope_tree,
                    &mut err,
                    "the slice is only valid for ",
                    sup,
                    "",
                );
                err
            }
            infer::RelateObjectBound(span) => {
                let mut err = struct_span_err!(
                    self.tcx.sess,
                    span,
                    E0476,
                    "lifetime of the source pointer does not outlive \
                                                lifetime bound of the object type"
                );
                note_and_explain_region(
                    self.tcx,
                    region_scope_tree,
                    &mut err,
                    "object type is valid for ",
                    sub,
                    "",
                );
                note_and_explain_region(
                    self.tcx,
                    region_scope_tree,
                    &mut err,
                    "source pointer is only valid for ",
                    sup,
                    "",
                );
                err
            }
            infer::RelateParamBound(span, ty) => {
                let mut err = struct_span_err!(
                    self.tcx.sess,
                    span,
                    E0477,
                    "the type `{}` does not fulfill the required \
                                                lifetime",
                    self.ty_to_string(ty)
                );
                match *sub {
                    ty::ReStatic => note_and_explain_region(
                        self.tcx,
                        region_scope_tree,
                        &mut err,
                        "type must satisfy ",
                        sub,
                        "",
                    ),
                    _ => note_and_explain_region(
                        self.tcx,
                        region_scope_tree,
                        &mut err,
                        "type must outlive ",
                        sub,
                        "",
                    ),
                }
                err
            }
            infer::RelateRegionParamBound(span) => {
                let mut err =
                    struct_span_err!(self.tcx.sess, span, E0478, "lifetime bound not satisfied");
                note_and_explain_region(
                    self.tcx,
                    region_scope_tree,
                    &mut err,
                    "lifetime parameter instantiated with ",
                    sup,
                    "",
                );
                note_and_explain_region(
                    self.tcx,
                    region_scope_tree,
                    &mut err,
                    "but lifetime parameter must outlive ",
                    sub,
                    "",
                );
                err
            }
            infer::RelateDefaultParamBound(span, ty) => {
                let mut err = struct_span_err!(
                    self.tcx.sess,
                    span,
                    E0479,
                    "the type `{}` (provided as the value of a type \
                                                parameter) is not valid at this point",
                    self.ty_to_string(ty)
                );
                note_and_explain_region(
                    self.tcx,
                    region_scope_tree,
                    &mut err,
                    "type must outlive ",
                    sub,
                    "",
                );
                err
            }
            infer::CallRcvr(span) => {
                let mut err = struct_span_err!(
                    self.tcx.sess,
                    span,
                    E0480,
                    "lifetime of method receiver does not outlive the \
                                                method call"
                );
                note_and_explain_region(
                    self.tcx,
                    region_scope_tree,
                    &mut err,
                    "the receiver is only valid for ",
                    sup,
                    "",
                );
                err
            }
            infer::CallArg(span) => {
                let mut err = struct_span_err!(
                    self.tcx.sess,
                    span,
                    E0481,
                    "lifetime of function argument does not outlive \
                                                the function call"
                );
                note_and_explain_region(
                    self.tcx,
                    region_scope_tree,
                    &mut err,
                    "the function argument is only valid for ",
                    sup,
                    "",
                );
                err
            }
            infer::CallReturn(span) => {
                let mut err = struct_span_err!(
                    self.tcx.sess,
                    span,
                    E0482,
                    "lifetime of return value does not outlive the \
                                                function call"
                );
                note_and_explain_region(
                    self.tcx,
                    region_scope_tree,
                    &mut err,
                    "the return value is only valid for ",
                    sup,
                    "",
                );
                err
            }
            infer::Operand(span) => {
                let mut err = struct_span_err!(
                    self.tcx.sess,
                    span,
                    E0483,
                    "lifetime of operand does not outlive the \
                                                operation"
                );
                note_and_explain_region(
                    self.tcx,
                    region_scope_tree,
                    &mut err,
                    "the operand is only valid for ",
                    sup,
                    "",
                );
                err
            }
            infer::AddrOf(span) => {
                let mut err = struct_span_err!(
                    self.tcx.sess,
                    span,
                    E0484,
                    "reference is not valid at the time of borrow"
                );
                note_and_explain_region(
                    self.tcx,
                    region_scope_tree,
                    &mut err,
                    "the borrow is only valid for ",
                    sup,
                    "",
                );
                err
            }
            infer::AutoBorrow(span) => {
                let mut err = struct_span_err!(
                    self.tcx.sess,
                    span,
                    E0485,
                    "automatically reference is not valid at the time \
                                                of borrow"
                );
                note_and_explain_region(
                    self.tcx,
                    region_scope_tree,
                    &mut err,
                    "the automatic borrow is only valid for ",
                    sup,
                    "",
                );
                err
            }
            infer::ExprTypeIsNotInScope(t, span) => {
                let mut err = struct_span_err!(
                    self.tcx.sess,
                    span,
                    E0486,
                    "type of expression contains references that are \
                                                not valid during the expression: `{}`",
                    self.ty_to_string(t)
                );
                note_and_explain_region(
                    self.tcx,
                    region_scope_tree,
                    &mut err,
                    "type is only valid for ",
                    sup,
                    "",
                );
                err
            }
            infer::SafeDestructor(span) => {
                let mut err = struct_span_err!(
                    self.tcx.sess,
                    span,
                    E0487,
                    "unsafe use of destructor: destructor might be \
                                                called while references are dead"
                );
                // FIXME (22171): terms "super/subregion" are suboptimal
                note_and_explain_region(
                    self.tcx,
                    region_scope_tree,
                    &mut err,
                    "superregion: ",
                    sup,
                    "",
                );
                note_and_explain_region(
                    self.tcx,
                    region_scope_tree,
                    &mut err,
                    "subregion: ",
                    sub,
                    "",
                );
                err
            }
            infer::BindingTypeIsNotValidAtDecl(span) => {
                let mut err = struct_span_err!(
                    self.tcx.sess,
                    span,
                    E0488,
                    "lifetime of variable does not enclose its \
                                                declaration"
                );
                note_and_explain_region(
                    self.tcx,
                    region_scope_tree,
                    &mut err,
                    "the variable is only valid for ",
                    sup,
                    "",
                );
                err
            }
            infer::ParameterInScope(_, span) => {
                let mut err = struct_span_err!(
                    self.tcx.sess,
                    span,
                    E0489,
                    "type/lifetime parameter not in scope here"
                );
                note_and_explain_region(
                    self.tcx,
                    region_scope_tree,
                    &mut err,
                    "the parameter is only valid for ",
                    sub,
                    "",
                );
                err
            }
            infer::DataBorrowed(ty, span) => {
                let mut err = struct_span_err!(
                    self.tcx.sess,
                    span,
                    E0490,
                    "a value of type `{}` is borrowed for too long",
                    self.ty_to_string(ty)
                );
                note_and_explain_region(
                    self.tcx,
                    region_scope_tree,
                    &mut err,
                    "the type is valid for ",
                    sub,
                    "",
                );
                note_and_explain_region(
                    self.tcx,
                    region_scope_tree,
                    &mut err,
                    "but the borrow lasts for ",
                    sup,
                    "",
                );
                err
            }
            infer::ReferenceOutlivesReferent(ty, span) => {
                let mut err = struct_span_err!(
                    self.tcx.sess,
                    span,
                    E0491,
                    "in type `{}`, reference has a longer lifetime \
                                                than the data it references",
                    self.ty_to_string(ty)
                );
                note_and_explain_region(
                    self.tcx,
                    region_scope_tree,
                    &mut err,
                    "the pointer is valid for ",
                    sub,
                    "",
                );
                note_and_explain_region(
                    self.tcx,
                    region_scope_tree,
                    &mut err,
                    "but the referenced data is only valid for ",
                    sup,
                    "",
                );
                err
            }
            infer::CompareImplMethodObligation {
                span,
                item_name,
                impl_item_def_id,
                trait_item_def_id,
            } => self.report_extra_impl_obligation(
                span,
                item_name,
                impl_item_def_id,
                trait_item_def_id,
                &format!("`{}: {}`", sup, sub),
            ),
        }
    }

    pub(super) fn report_placeholder_failure(
        &self,
        region_scope_tree: &region::ScopeTree,
        placeholder_origin: SubregionOrigin<'tcx>,
        sub: Region<'tcx>,
        sup: Region<'tcx>,
    ) -> DiagnosticBuilder<'tcx> {
        // I can't think how to do better than this right now. -nikomatsakis
        match placeholder_origin {
            infer::Subtype(box trace) => {
                let terr = TypeError::RegionsPlaceholderMismatch;
                self.report_and_explain_type_error(trace, &terr)
            }

            _ => self.report_concrete_failure(region_scope_tree, placeholder_origin, sub, sup),
        }
    }
}
