use super::{
    super::{attachment::*, error::*, implementation::*, problem::*},
    comparison::*,
    equality::*,
    r#ref::*,
};

use std::{cmp::*, error::*};

//
// Cause
//

/// A link in a [Problem]'s causation chain.
#[derive(Debug)]
pub struct Cause {
    /// Error.
    pub error: CapturedError,

    /// Attachments.
    pub attachments: Vector<CapturedAttachment>,
}

impl Cause {
    /// Constructor.
    pub fn new(error: CapturedError) -> Self {
        Self {
            error,
            attachments: Default::default(),
        }
    }

    /// To a [CauseRef].
    pub fn to_ref<'problem>(
        &'problem self,
        problem: &'problem Problem,
        depth: usize,
    ) -> CauseRef<'problem, CapturedError> {
        CauseRef::new(problem, depth, &self.error, &self.attachments)
    }
}

impl Errors for Cause {
    fn iter_errors_of_type<'this, ErrorT>(&'this self) -> impl Iterator<Item = &'this ErrorT>
    where
        ErrorT: 'static + Error,
    {
        self.iter_sources()
            .filter_map(|error| error.downcast_ref::<ErrorT>())
    }
}

impl IterateErrorSources for Cause {
    fn iter_sources<'this>(&'this self) -> ErrorSourceIterator<'this> {
        ErrorSourceIterator::new(self.error.as_ref())
    }
}

impl Attachments for Cause {
    fn attachments(&self) -> impl Iterator<Item = &CapturedAttachment> {
        self.attachments.iter()
    }
}

impl AttachmentsMut for Cause {
    fn attachments_mut(&mut self) -> impl Iterator<Item = &mut CapturedAttachment> {
        self.attachments.iter_mut()
    }

    fn attach<AttachmentT>(&mut self, attachment: AttachmentT)
    where
        AttachmentT: 'static + Send + Sync,
    {
        self.attachments.push(Box::new(attachment));
    }
}

impl PartialEq for Cause {
    fn eq(&self, other: &Self) -> bool {
        if let Some(equality) = self.attachment_of_type::<CauseEquality>() {
            equality.eq(self, other)
        } else {
            self.partial_cmp(other) == Some(Ordering::Equal)
        }
    }
}

impl Eq for Cause {}

impl PartialOrd for Cause {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        self.attachment_of_type::<CauseComparison>()
            .and_then(|comparison| comparison.compare(self, other))
    }
}

impl<'this> IntoIterator for &'this Cause {
    type Item = &'this (dyn 'static + Error);
    type IntoIter = ErrorSourceIterator<'this>;

    fn into_iter(self) -> Self::IntoIter {
        self.iter_sources()
    }
}

impl<ErrorT> From<ErrorT> for Cause
where
    ErrorT: 'static + Error + Send + Sync,
{
    fn from(error: ErrorT) -> Self {
        Self::new(error.into())
    }
}
