use super::{attachments::*, captured::*};

use std::panic::*;

//
// AttachmentsMut
//

/// Mutable access to attachments.
pub trait AttachmentsMut
where
    Self: Attachments,
{
    /// Iterate attachments.
    fn attachments_mut(&mut self) -> impl Iterator<Item = &mut CapturedAttachment>;

    /// Iterate attachments of a type.
    fn attachments_of_type_mut<'this, AttachmentT>(
        &'this mut self,
    ) -> impl Iterator<Item = &'this mut AttachmentT>
    where
        AttachmentT: 'static,
    {
        self.attachments_mut()
            .filter_map(|attachment| attachment.downcast_mut())
    }

    /// First attachment of a type.
    fn attachment_of_type_mut<AttachmentT>(&mut self) -> Option<&mut AttachmentT>
    where
        AttachmentT: 'static,
    {
        self.attachments_of_type_mut().next()
    }

    /// First attachment of a type.
    ///
    /// If an attachment of the type is not found then a default will be attached.
    fn must_attachment_of_type_mut<AttachmentT, DefaultT>(
        &mut self,
        default: DefaultT,
    ) -> &mut AttachmentT
    where
        AttachmentT: 'static + Send + Sync,
        DefaultT: FnOnce() -> AttachmentT,
    {
        if self.attachment_of_type::<AttachmentT>().is_none() {
            self.attach(default());
        }

        self.attachment_of_type_mut().expect("attachment")
    }

    /// Add an attachment.
    fn attach<AttachmentT>(&mut self, attachment: AttachmentT)
    where
        AttachmentT: 'static + Send + Sync;

    /// Add an attachment only if we don't already have one of its type.
    fn attach_once<AttachmentT>(&mut self, attachment: AttachmentT)
    where
        AttachmentT: 'static + Send + Sync,
    {
        if self.attachment_of_type::<AttachmentT>().is_none() {
            self.attach(attachment)
        }
    }

    /// Add a [Location] (via [Location::caller]) attachment if we don't already have one.
    #[track_caller]
    fn attach_location_once(&mut self) {
        if self.attachment_of_type::<Location>().is_none() {
            self.attach(Location::caller().clone())
        }
    }

    /// Add a backtrace attachment if we don't already have one.
    #[cfg(feature = "backtrace-external")]
    fn attach_backtrace_once(&mut self) {
        use backtrace::*;
        if self.attachment_of_type::<Backtrace>().is_none() {
            self.attach(Backtrace::new())
        }
    }

    /// Add a backtrace attachment if we don't already have one.
    #[cfg(not(feature = "backtrace-external"))]
    fn attach_backtrace_once(&mut self) {
        use std::backtrace::*;
        if self.attachment_of_type::<Backtrace>().is_none() {
            self.attach(Backtrace::force_capture())
        }
    }
}
