use crate::Attachments;

use super::{
    super::{error::*, problem::*},
    cause::*,
    r#ref::*,
};

use std::error::*;

//
// Causes
//

/// Access to causes.
pub trait Causes {
    /// The problem that owns this causation chain.
    fn problem(&self) -> &Problem;

    /// Iterate causes in order of causation.
    ///
    /// You can call [iter_sources](IterateErrorSources::iter_sources) on each to further
    /// iterate the "branches" of the tree:
    ///
    /// ```
    /// for cause in problem.iter_causes() {
    ///     for error in error.iter_sources() {
    ///         println!("{}", error);
    ///     }
    /// }
    /// ```
    fn iter_causes(&self) -> impl Iterator<Item = &Cause>;

    /// Iterate causes with an error of a type.
    ///
    /// Will recurse into [source](Error::source).
    fn iter_causes_with_error_type<ErrorT>(&self) -> impl Iterator<Item = CauseRef<'_, ErrorT>>
    where
        ErrorT: 'static + Error,
    {
        self.iter_causes().enumerate().filter_map(|(depth, cause)| {
            cause
                .iter_errors_of_type()
                .next()
                .map(|error| CauseRef::new(self.problem(), depth, error, &cause.attachments))
        })
    }

    /// Iterate causes in order of causation until (but not including) a cause with an error of a
    /// type.
    ///
    /// Will recurse into [source](Error::source).
    fn iter_causes_until_error_type<ErrorT>(&self) -> impl Iterator<Item = &Cause>
    where
        ErrorT: 'static + Error,
    {
        self.iter_causes()
            .take_while(|cause| !cause.has_error_of_type::<ErrorT>())
    }

    /// Iterate causes in order of causation from a cause with an error of a type.
    ///
    /// Will recurse into [source](Error::source).
    fn iter_causes_from_error_type<ErrorT>(&self) -> impl Iterator<Item = &Cause>
    where
        ErrorT: 'static + Error,
    {
        self.iter_causes()
            .skip_while(|cause| !cause.has_error_of_type::<ErrorT>())
    }

    /// Iterate causes with the error.
    ///
    /// Will recurse into [source](Error::source).
    fn iter_causes_with_error<ErrorT>(
        &self,
        error: &ErrorT,
    ) -> impl Iterator<Item = CauseRef<'_, ErrorT>>
    where
        ErrorT: 'static + Error + PartialEq,
    {
        self.iter_causes().enumerate().filter_map(|(depth, cause)| {
            cause
                .error_for(error)
                .map(|error| CauseRef::new(self.problem(), depth, error, &cause.attachments))
        })
    }

    /// Iterate causes with an attachment of a type.
    fn iter_causes_with_attachment_type<AttachmentT>(
        &self,
    ) -> impl Iterator<Item = (&Cause, &AttachmentT)>
    where
        AttachmentT: 'static,
    {
        self.iter_causes()
            .filter_map(|cause| match cause.attachment_of_type() {
                Some(attachment) => Some((cause, attachment)),
                None => None,
            })
    }

    /// The first cause with an error of a type.
    ///
    /// Will recurse into [source](Error::source).
    fn cause_with_error_type<ErrorT>(&self) -> Option<CauseRef<'_, ErrorT>>
    where
        ErrorT: 'static + Error,
    {
        self.iter_causes_with_error_type().next()
    }

    /// The first cause with the error.
    ///
    /// Will recurse into [source](Error::source).
    fn cause_with_error<ErrorT>(&self, error: &ErrorT) -> Option<CauseRef<'_, ErrorT>>
    where
        ErrorT: 'static + Error + PartialEq,
    {
        self.iter_causes_with_error(error).next()
    }

    /// The first cause with an attachment of a type.
    fn cause_with_attachment_type<AttachmentT>(&self) -> Option<(&Cause, &AttachmentT)>
    where
        AttachmentT: 'static,
    {
        self.iter_causes_with_attachment_type().next()
    }

    /// Iterate errors in order of causation.
    ///
    /// Note that this will *not* include [source](Error::source), however you can call
    /// [iter_sources](IterateErrorSources::iter_sources) on each to further iterate the
    /// "branches" of the tree:
    ///
    /// ```
    /// for error in problem.iter_errors() {
    ///     for error in error.as_ref().iter_sources() {
    ///         println!("{}", error);
    ///     }
    /// }
    /// ```
    fn iter_errors(&self) -> impl Iterator<Item = &CapturedError> {
        self.iter_causes().map(|cause| &cause.error)
    }

    /// Iterate errors of a type in order of causation.
    ///
    /// Note that this will *not* include [source](Error::source).
    fn iter_errors_of_type<ErrorT>(&self) -> impl Iterator<Item = &ErrorT>
    where
        ErrorT: 'static + Error,
    {
        self.iter_errors()
            .filter_map(|error| error.downcast_ref::<ErrorT>())
    }

    /// The first error of a type.
    ///
    /// Note that this will *not* include [source](Error::source).
    fn error_of_type<ErrorT>(&self) -> Option<&ErrorT>
    where
        ErrorT: 'static + Error,
    {
        self.iter_errors_of_type().next()
    }

    /// Whether we have an error of a type in the causation chain.
    ///
    /// Will recurse into [source](Error::source).
    fn has_error_type<ErrorT>(&self) -> bool
    where
        ErrorT: 'static + Error,
    {
        self.cause_with_error_type::<ErrorT>().is_some()
    }

    /// Whether we have the error in the causation chain.
    ///
    /// Will recurse into [source](Error::source).
    fn has_error<ErrorT>(&self, error: &ErrorT) -> bool
    where
        ErrorT: 'static + Error + PartialEq,
    {
        self.cause_with_error(error).is_some()
    }
}
