use crate::stdlib::pin::Pin;
use crate::stdlib::task::{Context, Poll};
use crate::stdlib::{future::Future, marker::Sized};
use crate::{
    dispatcher::{self, Dispatch},
    span::Span,
};
use pin_project_lite::pin_project;

/// Attaches spans to a [`std::future::Future`].
///
/// Extension trait allowing futures to be
/// instrumented with a `tracing` [span].
///
/// [span]: super::Span
pub trait Instrument: Sized {
    /// Instruments this type with the provided [`Span`], returning an
    /// `Instrumented` wrapper.
    ///
    /// The attached [`Span`] will be [entered] every time the instrumented
    /// [`Future`] is polled.
    ///
    /// # Examples
    ///
    /// Instrumenting a future:
    ///
    /// ```rust
    /// use tracing::Instrument;
    ///
    /// # async fn doc() {
    /// let my_future = async {
    ///     // ...
    /// };
    ///
    /// my_future
    ///     .instrument(tracing::info_span!("my_future"))
    ///     .await
    /// # }
    /// ```
    ///
    /// The [`Span::or_current`] combinator can be used in combination with
    /// `instrument` to ensure that the [current span] is attached to the
    /// future if the span passed to `instrument` is [disabled]:
    ///
    /// ```
    /// use tracing::Instrument;
    /// # mod tokio {
    /// #     pub(super) fn spawn(_: impl std::future::Future) {}
    /// # }
    ///
    /// let my_future = async {
    ///     // ...
    /// };
    ///
    /// let outer_span = tracing::info_span!("outer").entered();
    ///
    /// // If the "my_future" span is enabled, then the spawned task will
    /// // be within both "my_future" *and* "outer", since "outer" is
    /// // "my_future"'s parent. However, if "my_future" is disabled,
    /// // the spawned task will *not* be in any span.
    /// tokio::spawn(
    ///     my_future
    ///         .instrument(tracing::debug_span!("my_future"))
    /// );
    ///
    /// // Using `Span::or_current` ensures the spawned task is instrumented
    /// // with the current span, if the new span passed to `instrument` is
    /// // not enabled. This means that if the "my_future"  span is disabled,
    /// // the spawned task will still be instrumented with the "outer" span:
    /// # let my_future = async {};
    /// tokio::spawn(
    ///    my_future
    ///         .instrument(tracing::debug_span!("my_future").or_current())
    /// );
    /// ```
    ///
    /// [entered]: super::Span::enter()
    /// [`Span::or_current`]: super::Span::or_current()
    /// [current span]: super::Span::current()
    /// [disabled]: super::Span::is_disabled()
    /// [`Future`]: std::future::Future
    fn instrument(self, span: Span) -> Instrumented<Self> {
        Instrumented { inner: self, span }
    }

    /// Instruments this type with the [current] [`Span`], returning an
    /// `Instrumented` wrapper.
    ///
    /// The attached [`Span`] will be [entered] every time the instrumented
    /// [`Future`] is polled.
    ///
    /// This can be used to propagate the current span when spawning a new future.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use tracing::Instrument;
    ///
    /// # mod tokio {
    /// #     pub(super) fn spawn(_: impl std::future::Future) {}
    /// # }
    /// # async fn doc() {
    /// let span = tracing::info_span!("my_span");
    /// let _enter = span.enter();
    ///
    /// // ...
    ///
    /// let future = async {
    ///     tracing::debug!("this event will occur inside `my_span`");
    ///     // ...
    /// };
    /// tokio::spawn(future.in_current_span());
    /// # }
    /// ```
    ///
    /// [current]: super::Span::current()
    /// [entered]: super::Span::enter()
    /// [`Span`]: crate::Span
    /// [`Future`]: std::future::Future
    #[inline]
    fn in_current_span(self) -> Instrumented<Self> {
        self.instrument(Span::current())
    }
}

/// Extension trait allowing futures to be instrumented with
/// a `tracing` [`Subscriber`](crate::Subscriber).
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub trait WithSubscriber: Sized {
    /// Attaches the provided [`Subscriber`] to this type, returning a
    /// [`WithDispatch`] wrapper.
    ///
    /// The attached [`Subscriber`] will be set as the [default] when the returned
    /// [`Future`] is polled.
    ///
    /// # Examples
    ///
    /// ```
    /// # use tracing::subscriber::NoSubscriber as MySubscriber;
    /// # use tracing::subscriber::NoSubscriber as MyOtherSubscriber;
    /// # async fn docs() {
    /// use tracing::instrument::WithSubscriber;
    ///
    /// // Set the default `Subscriber`
    /// let _default = tracing::subscriber::set_default(MySubscriber::default());
    ///
    /// tracing::info!("this event will be recorded by the default `Subscriber`");
    ///
    /// // Create a different `Subscriber` and attach it to a future.
    /// let other_subscriber = MyOtherSubscriber::default();
    /// let future = async {
    ///     tracing::info!("this event will be recorded by the other `Subscriber`");
    ///     // ...
    /// };
    ///
    /// future
    ///     // Attach the other `Subscriber` to the future before awaiting it
    ///     .with_subscriber(other_subscriber)
    ///     .await;
    ///
    /// // Once the future has completed, we return to the default `Subscriber`.
    /// tracing::info!("this event will be recorded by the default `Subscriber`");
    /// # }
    /// ```
    ///
    /// [`Subscriber`]: super::Subscriber
    /// [default]: crate::dispatcher#setting-the-default-subscriber
    /// [`Future`]: std::future::Future
    fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
    where
        S: Into<Dispatch>,
    {
        WithDispatch {
            inner: self,
            dispatcher: subscriber.into(),
        }
    }

    /// Attaches the current [default] [`Subscriber`] to this type, returning a
    /// [`WithDispatch`] wrapper.
    ///
    /// The attached `Subscriber` will be set as the [default] when the returned
    /// [`Future`] is polled.
    ///
    /// This can be used to propagate the current dispatcher context when
    /// spawning a new future that may run on a different thread.
    ///
    /// # Examples
    ///
    /// ```
    /// # mod tokio {
    /// #     pub(super) fn spawn(_: impl std::future::Future) {}
    /// # }
    /// # use tracing::subscriber::NoSubscriber as MySubscriber;
    /// # async fn docs() {
    /// use tracing::instrument::WithSubscriber;
    ///
    /// // Using `set_default` (rather than `set_global_default`) sets the
    /// // default `Subscriber` for *this* thread only.
    /// let _default = tracing::subscriber::set_default(MySubscriber::default());
    ///
    /// let future = async {
    ///     // ...
    /// };
    ///
    /// // If a multi-threaded async runtime is in use, this spawned task may
    /// // run on a different thread, in a different default `Subscriber`'s context.
    /// tokio::spawn(future);
    ///
    /// // However, calling `with_current_subscriber` on the future before
    /// // spawning it, ensures that the current thread's default `Subscriber` is
    /// // propagated to the spawned task, regardless of where it executes:
    /// # let future = async { };
    /// tokio::spawn(future.with_current_subscriber());
    /// # }
    /// ```
    /// [`Subscriber`]: super::Subscriber
    /// [default]: crate::dispatcher#setting-the-default-subscriber
    /// [`Future`]: std::future::Future
    #[inline]
    fn with_current_subscriber(self) -> WithDispatch<Self> {
        WithDispatch {
            inner: self,
            dispatcher: crate::dispatcher::get_default(|default| default.clone()),
        }
    }
}

pin_project! {
    /// A [`Future`] that has been instrumented with a `tracing` [`Subscriber`].
    ///
    /// This type is returned by the [`WithSubscriber`] extension trait. See that
    /// trait's documentation for details.
    ///
    /// [`Future`]: std::future::Future
    /// [`Subscriber`]: crate::Subscriber
    #[derive(Clone, Debug)]
    #[must_use = "futures do nothing unless you `.await` or poll them"]
    #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
    pub struct WithDispatch<T> {
        #[pin]
        inner: T,
        dispatcher: Dispatch,
    }
}

pin_project! {
    /// A [`Future`] that has been instrumented with a `tracing` [`Span`].
    ///
    /// This type is returned by the [`Instrument`] extension trait. See that
    /// trait's documentation for details.
    ///
    /// [`Future`]: std::future::Future
    /// [`Span`]: crate::Span
    #[derive(Debug, Clone)]
    #[must_use = "futures do nothing unless you `.await` or poll them"]
    pub struct Instrumented<T> {
        #[pin]
        inner: T,
        span: Span,
    }
}

// === impl Instrumented ===

impl<T: Future> Future for Instrumented<T> {
    type Output = T::Output;

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        let this = self.project();
        let _enter = this.span.enter();
        this.inner.poll(cx)
    }
}

impl<T: Sized> Instrument for T {}

impl<T> Instrumented<T> {
    /// Borrows the `Span` that this type is instrumented by.
    pub fn span(&self) -> &Span {
        &self.span
    }

    /// Mutably borrows the `Span` that this type is instrumented by.
    pub fn span_mut(&mut self) -> &mut Span {
        &mut self.span
    }

    /// Borrows the wrapped type.
    pub fn inner(&self) -> &T {
        &self.inner
    }

    /// Mutably borrows the wrapped type.
    pub fn inner_mut(&mut self) -> &mut T {
        &mut self.inner
    }

    /// Get a pinned reference to the wrapped type.
    pub fn inner_pin_ref(self: Pin<&Self>) -> Pin<&T> {
        self.project_ref().inner
    }

    /// Get a pinned mutable reference to the wrapped type.
    pub fn inner_pin_mut(self: Pin<&mut Self>) -> Pin<&mut T> {
        self.project().inner
    }

    /// Consumes the `Instrumented`, returning the wrapped type.
    ///
    /// Note that this drops the span.
    pub fn into_inner(self) -> T {
        self.inner
    }
}

// === impl WithDispatch ===

#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
impl<T: Future> Future for WithDispatch<T> {
    type Output = T::Output;

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        let this = self.project();
        let dispatcher = this.dispatcher;
        let future = this.inner;
        let _default = dispatcher::set_default(dispatcher);
        future.poll(cx)
    }
}

#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
impl<T: Sized> WithSubscriber for T {}

#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
impl<T> WithDispatch<T> {
    /// Borrows the [`Dispatch`] that is entered when this type is polled.
    pub fn dispatcher(&self) -> &Dispatch {
        &self.dispatcher
    }

    /// Borrows the wrapped type.
    pub fn inner(&self) -> &T {
        &self.inner
    }

    /// Mutably borrows the wrapped type.
    pub fn inner_mut(&mut self) -> &mut T {
        &mut self.inner
    }

    /// Get a pinned reference to the wrapped type.
    pub fn inner_pin_ref(self: Pin<&Self>) -> Pin<&T> {
        self.project_ref().inner
    }

    /// Get a pinned mutable reference to the wrapped type.
    pub fn inner_pin_mut(self: Pin<&mut Self>) -> Pin<&mut T> {
        self.project().inner
    }

    /// Consumes the `Instrumented`, returning the wrapped type.
    ///
    /// Note that this drops the span.
    pub fn into_inner(self) -> T {
        self.inner
    }
}
