//! The `Notification` trait for specifying notification.

use crate::sync::atomic::{self, Ordering};
#[cfg(feature = "std")]
use core::fmt;

pub(crate) use __private::Internal;

/// The type of notification to use with an [`Event`].
///
/// This is hidden and sealed to prevent changes to this trait from being breaking.
///
/// [`Event`]: crate::Event
#[doc(hidden)]
pub trait NotificationPrivate {
    /// The tag data associated with a notification.
    type Tag;

    /// Emit a fence to ensure that the notification is visible to the listeners.
    fn fence(&self, internal: Internal);

    /// Whether or not the number of currently waiting listeners should be subtracted from `count()`.
    fn is_additional(&self, internal: Internal) -> bool;

    /// Get the number of listeners to wake.
    fn count(&self, internal: Internal) -> usize;

    /// Get a tag to be associated with a notification.
    ///
    /// This method is expected to be called `count()` times.
    fn next_tag(&mut self, internal: Internal) -> Self::Tag;
}

/// A notification that can be used to notify an [`Event`].
///
/// This type is used by the [`Event::notify()`] function to determine how many listeners to wake up, whether
/// or not to subtract additional listeners, and other properties. The actual internal data is hidden in a
/// private trait and is intentionally not exposed. This means that users cannot manually implement the
/// [`Notification`] trait. However, it also means that changing the underlying trait is not a semver breaking
/// change.
///
/// Users can create types that implement notifications using the combinators on the [`IntoNotification`] type.
/// Typical construction of a [`Notification`] starts with a numeric literal (like `3usize`) and then optionally
/// adding combinators.
///
/// # Example
///
/// ```
/// use event_listener::{Event, prelude::*};
///
/// fn notify(ev: &Event, notify: impl Notification<Tag = ()>) {
///     ev.notify(notify);
/// }
///
/// notify(&Event::new(), 1.additional());
/// ```
///
/// [`Event`]: crate::Event
pub trait Notification: NotificationPrivate {}
impl<N: NotificationPrivate + ?Sized> Notification for N {}

/// Notify a given number of unnotifed listeners.
#[derive(Debug, Clone)]
#[doc(hidden)]
pub struct Notify(usize);

impl Notify {
    /// Create a new `Notify` with the given number of listeners to notify.
    fn new(count: usize) -> Self {
        Self(count)
    }
}

impl NotificationPrivate for Notify {
    type Tag = ();

    fn is_additional(&self, _: Internal) -> bool {
        false
    }

    fn fence(&self, _: Internal) {
        full_fence();
    }

    fn count(&self, _: Internal) -> usize {
        self.0
    }

    fn next_tag(&mut self, _: Internal) -> Self::Tag {}
}

/// Make the underlying notification additional.
#[derive(Debug, Clone)]
#[doc(hidden)]
pub struct Additional<N: ?Sized>(N);

impl<N> Additional<N> {
    /// Create a new `Additional` with the given notification.
    fn new(inner: N) -> Self {
        Self(inner)
    }
}

impl<N> NotificationPrivate for Additional<N>
where
    N: Notification + ?Sized,
{
    type Tag = N::Tag;

    fn is_additional(&self, _: Internal) -> bool {
        true
    }

    fn fence(&self, i: Internal) {
        self.0.fence(i);
    }

    fn count(&self, i: Internal) -> usize {
        self.0.count(i)
    }

    fn next_tag(&mut self, i: Internal) -> Self::Tag {
        self.0.next_tag(i)
    }
}

/// Don't emit a fence for this notification.
#[derive(Debug, Clone)]
#[doc(hidden)]
pub struct Relaxed<N: ?Sized>(N);

impl<N> Relaxed<N> {
    /// Create a new `Relaxed` with the given notification.
    fn new(inner: N) -> Self {
        Self(inner)
    }
}

impl<N> NotificationPrivate for Relaxed<N>
where
    N: Notification + ?Sized,
{
    type Tag = N::Tag;

    fn is_additional(&self, i: Internal) -> bool {
        self.0.is_additional(i)
    }

    fn fence(&self, _: Internal) {
        // Don't emit a fence.
    }

    fn count(&self, i: Internal) -> usize {
        self.0.count(i)
    }

    fn next_tag(&mut self, i: Internal) -> Self::Tag {
        self.0.next_tag(i)
    }
}

/// Use a tag to notify listeners.
#[cfg(feature = "std")]
#[derive(Debug, Clone)]
#[doc(hidden)]
pub struct Tag<N: ?Sized, T> {
    tag: T,
    inner: N,
}

#[cfg(feature = "std")]
impl<N: ?Sized, T> Tag<N, T> {
    /// Create a new `Tag` with the given tag and notification.
    fn new(tag: T, inner: N) -> Self
    where
        N: Sized,
    {
        Self { tag, inner }
    }
}

#[cfg(feature = "std")]
impl<N, T> NotificationPrivate for Tag<N, T>
where
    N: Notification + ?Sized,
    T: Clone,
{
    type Tag = T;

    fn is_additional(&self, i: Internal) -> bool {
        self.inner.is_additional(i)
    }

    fn fence(&self, i: Internal) {
        self.inner.fence(i);
    }

    fn count(&self, i: Internal) -> usize {
        self.inner.count(i)
    }

    fn next_tag(&mut self, _: Internal) -> Self::Tag {
        self.tag.clone()
    }
}

/// Use a function to generate a tag to notify listeners.
#[cfg(feature = "std")]
#[doc(hidden)]
pub struct TagWith<N: ?Sized, F> {
    tag: F,
    inner: N,
}

#[cfg(feature = "std")]
impl<N: fmt::Debug, F> fmt::Debug for TagWith<N, F> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        struct Ellipses;

        impl fmt::Debug for Ellipses {
            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                f.write_str("..")
            }
        }

        f.debug_struct("TagWith")
            .field("tag", &Ellipses)
            .field("inner", &self.inner)
            .finish()
    }
}

#[cfg(feature = "std")]
impl<N, F> TagWith<N, F> {
    /// Create a new `TagFn` with the given tag function and notification.
    fn new(tag: F, inner: N) -> Self {
        Self { tag, inner }
    }
}

#[cfg(feature = "std")]
impl<N, F, T> NotificationPrivate for TagWith<N, F>
where
    N: Notification + ?Sized,
    F: FnMut() -> T,
{
    type Tag = T;

    fn is_additional(&self, i: Internal) -> bool {
        self.inner.is_additional(i)
    }

    fn fence(&self, i: Internal) {
        self.inner.fence(i);
    }

    fn count(&self, i: Internal) -> usize {
        self.inner.count(i)
    }

    fn next_tag(&mut self, _: Internal) -> Self::Tag {
        (self.tag)()
    }
}

/// A generic notification.
#[derive(Debug)]
pub(crate) struct GenericNotify<F> {
    /// Number of listeners to notify.
    count: usize,

    /// Whether this notification is additional.
    additional: bool,

    /// Generate tags.
    tags: F,
}

impl<T, F: TagProducer<Tag = T>> GenericNotify<F> {
    pub(crate) fn new(count: usize, additional: bool, tags: F) -> Self {
        Self {
            count,
            additional,
            tags,
        }
    }
}

impl<T, F: TagProducer<Tag = T>> NotificationPrivate for GenericNotify<F> {
    type Tag = T;

    fn is_additional(&self, _: Internal) -> bool {
        self.additional
    }

    fn fence(&self, _: Internal) {
        // Don't emit a fence.
    }

    fn count(&self, _: Internal) -> usize {
        self.count
    }

    fn next_tag(&mut self, _: Internal) -> Self::Tag {
        self.tags.next_tag()
    }
}

/// The producer for a generic notification.
pub(crate) trait TagProducer {
    type Tag;

    /// Get the next tag.
    fn next_tag(&mut self) -> Self::Tag;
}

impl<T, F: FnMut() -> T> TagProducer for F {
    type Tag = T;

    fn next_tag(&mut self) -> T {
        (self)()
    }
}

/// A value that can be converted into a [`Notification`].
///
/// This trait adds onto the [`Notification`] trait by providing combinators that can be applied to all
/// notification types as well as numeric literals. This transforms what would normally be:
///
/// ```
/// use event_listener::Event;
///
/// let event = Event::new();
///
/// // Note that each use case needs its own function, leading to bloat.
/// event.notify(1);
/// event.notify_additional(3);
/// event.notify_relaxed(5);
/// event.notify_additional_relaxed(2);
/// ```
///
/// into this:
///
/// ```
/// use event_listener::{Event, prelude::*};
///
/// let event = Event::new();
///
/// event.notify(1);
/// event.notify(3.additional());
/// event.notify(5.relaxed());
/// event.notify(2.additional().relaxed());
/// ```
///
/// This trait is implemented for all types that implement [`Notification`], as well as for non-floating-point
/// numeric literals (`usize`, `i32`, etc).
///
/// This function can be thought of as being analogous to [`std::iter::IntoIterator`], but for [`Notification`].
pub trait IntoNotification: __private::Sealed {
    /// The tag data associated with a notification.
    ///
    /// By default, most [`Event`]s will use the unit type, `()`. However, this can be used to pass data along to
    /// the listener.
    type Tag;

    /// The notification type.
    ///
    /// Tells what kind of underlying type that the [`Notification`] is. You probably don't need to worry about
    /// this.
    type Notify: Notification<Tag = Self::Tag>;

    /// Convert this value into a notification.
    ///
    /// This allows the user to convert an [`IntoNotification`] into a [`Notification`].
    ///
    /// # Panics
    ///
    /// This function panics if the value represents a negative number of notifications.
    ///
    /// # Examples
    ///
    /// ```
    /// use event_listener::prelude::*;
    ///
    /// let _ = 3.into_notification();
    /// ```
    fn into_notification(self) -> Self::Notify;

    /// Convert this value into an additional notification.
    ///
    /// By default, notifications ignore listeners that are already notified. Generally, this happens when there
    /// is an [`EventListener`] that has been woken up, but hasn't been polled to completion or waited on yet.
    /// For instance, if you have three notified listeners and you call `event.notify(5)`, only two listeners
    /// will be woken up.
    ///
    /// This default behavior is generally desired. For instance, if you are writing a `Mutex` implementation
    /// powered by an [`Event`], you usually only want one consumer to be notified at a time. If you notified
    /// a listener when another listener is already notified, you would have unnecessary contention for your
    /// lock, as both listeners fight over the lock. Therefore, you would call `event.notify(1)` to make sure
    /// *at least* one listener is awake.
    ///
    /// Sometimes, this behavior is not desired. For instance, if you are writing an MPMC channel, it is desirable
    /// for multiple listeners to be reading from the underlying queue at once. In this case, you would instead
    /// call `event.notify(1.additional())`.
    ///
    /// # Examples
    ///
    /// ```
    /// use event_listener::{Event, prelude::*};
    ///
    /// let event = Event::new();
    ///
    /// let mut l1 = event.listen();
    /// let mut l2 = event.listen();
    ///
    /// // This will only wake up the first listener, as the second call observes that there is already a
    /// // notified listener.
    /// event.notify(1);
    /// event.notify(1);
    ///
    /// // This call wakes up the other listener.
    /// event.notify(1.additional());
    /// ```
    fn additional(self) -> Additional<Self::Notify>
    where
        Self: Sized,
    {
        Additional::new(self.into_notification())
    }

    /// Don't emit a fence for this notification.
    ///
    /// Usually, notifications emit a `SeqCst` atomic fence before any listeners are woken up. This ensures
    /// that notification state isn't inconsistent before any wakers are woken up. However, it may be
    /// desirable to omit this fence in certain cases.
    ///
    /// - You are running the [`Event`] on a single thread, where no synchronization needs to occur.
    /// - You are emitting the `SeqCst` fence yourself.
    ///
    /// In these cases, `relaxed()` can be used to avoid emitting the `SeqCst` fence.
    ///
    /// # Examples
    ///
    /// ```
    /// use event_listener::{Event, prelude::*};
    /// use std::sync::atomic::{self, Ordering};
    ///
    /// let event = Event::new();
    ///
    /// let listener1 = event.listen();
    /// let listener2 = event.listen();
    /// let listener3 = event.listen();
    ///
    /// // We should emit a fence manually when using relaxed notifications.
    /// atomic::fence(Ordering::SeqCst);
    ///
    /// // Notifies two listeners.
    /// //
    /// // Listener queueing is fair, which means `listener1` and `listener2`
    /// // get notified here since they start listening before `listener3`.
    /// event.notify(1.relaxed());
    /// event.notify(1.relaxed());
    /// ```
    fn relaxed(self) -> Relaxed<Self::Notify>
    where
        Self: Sized,
    {
        Relaxed::new(self.into_notification())
    }

    /// Use a tag with this notification.
    ///
    /// In many cases, it is desired to send additional information to the listener of the [`Event`]. For instance,
    /// it is possible to optimize a `Mutex` implementation by locking directly on the next listener, without
    /// needing to ever unlock the mutex at all.
    ///
    /// The tag provided is cloned to provide the tag for all listeners. In cases where this is not flexible
    /// enough, use [`IntoNotification::with_tag()`] instead.
    ///
    /// Tagging functions cannot be implemented efficiently for `no_std`, so this is only available
    /// when the `std` feature is enabled.
    ///
    /// # Examples
    ///
    /// ```
    /// use event_listener::{prelude::*, Event};
    ///
    /// let event = Event::<bool>::with_tag();
    ///
    /// let mut listener1 = event.listen();
    /// let mut listener2 = event.listen();
    ///
    /// // Notify with `true` then `false`.
    /// event.notify(1.additional().tag(true));
    /// event.notify(1.additional().tag(false));
    ///
    /// assert_eq!(listener1.as_mut().wait(), true);
    /// assert_eq!(listener2.as_mut().wait(), false);
    /// ```
    #[cfg(feature = "std")]
    fn tag<T: Clone>(self, tag: T) -> Tag<Self::Notify, T>
    where
        Self: Sized + IntoNotification<Tag = ()>,
    {
        Tag::new(tag, self.into_notification())
    }

    /// Use a function to generate a tag with this notification.
    ///
    /// In many cases, it is desired to send additional information to the listener of the [`Event`]. For instance,
    /// it is possible to optimize a `Mutex` implementation by locking directly on the next listener, without
    /// needing to ever unlock the mutex at all.
    ///
    /// Tagging functions cannot be implemented efficiently for `no_std`, so this is only available
    /// when the `std` feature is enabled.
    ///
    /// # Examples
    ///
    /// ```
    /// use event_listener::{prelude::*, Event};
    ///
    /// let event = Event::<bool>::with_tag();
    ///
    /// let mut listener1 = event.listen();
    /// let mut listener2 = event.listen();
    ///
    /// // Notify with `true` then `false`.
    /// event.notify(1.additional().tag_with(|| true));
    /// event.notify(1.additional().tag_with(|| false));
    ///
    /// assert_eq!(listener1.as_mut().wait(), true);
    /// assert_eq!(listener2.as_mut().wait(), false);
    /// ```
    #[cfg(feature = "std")]
    fn tag_with<T, F>(self, tag: F) -> TagWith<Self::Notify, F>
    where
        Self: Sized + IntoNotification<Tag = ()>,
        F: FnMut() -> T,
    {
        TagWith::new(tag, self.into_notification())
    }
}

impl<N: Notification> IntoNotification for N {
    type Tag = N::Tag;
    type Notify = N;

    fn into_notification(self) -> Self::Notify {
        self
    }
}

macro_rules! impl_for_numeric_types {
    ($($ty:ty)*) => {$(
        impl IntoNotification for $ty {
            type Tag = ();
            type Notify = Notify;

            #[allow(unused_comparisons)]
            fn into_notification(self) -> Self::Notify {
                if self < 0 {
                    panic!("negative notification count");
                }

                Notify::new(self.try_into().expect("overflow"))
            }
        }

        impl __private::Sealed for $ty {}
    )*};
}

impl_for_numeric_types! { usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 }

/// Equivalent to `atomic::fence(Ordering::SeqCst)`, but in some cases faster.
#[inline]
pub(super) fn full_fence() {
    #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), not(miri), not(loom)))]
    {
        use core::{arch::asm, cell::UnsafeCell};
        // HACK(stjepang): On x86 architectures there are two different ways of executing
        // a `SeqCst` fence.
        //
        // 1. `atomic::fence(SeqCst)`, which compiles into a `mfence` instruction.
        // 2. A `lock <op>` instruction.
        //
        // Both instructions have the effect of a full barrier, but empirical benchmarks have shown
        // that the second one is sometimes a bit faster.
        let a = UnsafeCell::new(0_usize);
        // It is common to use `lock or` here, but when using a local variable, `lock not`, which
        // does not change the flag, should be slightly more efficient.
        // Refs: https://www.felixcloutier.com/x86/not
        unsafe {
            #[cfg(target_pointer_width = "64")]
            asm!("lock not qword ptr [{0}]", in(reg) a.get(), options(nostack, preserves_flags));
            #[cfg(target_pointer_width = "32")]
            asm!("lock not dword ptr [{0:e}]", in(reg) a.get(), options(nostack, preserves_flags));
        }
        return;
    }
    #[allow(unreachable_code)]
    {
        atomic::fence(Ordering::SeqCst);
    }
}

mod __private {
    /// Make sure the NotificationPrivate trait can't be implemented outside of this crate.
    #[doc(hidden)]
    #[derive(Debug)]
    pub struct Internal(());

    impl Internal {
        pub(crate) fn new() -> Self {
            Self(())
        }
    }

    #[doc(hidden)]
    pub trait Sealed {}
    impl<N: super::NotificationPrivate + ?Sized> Sealed for N {}
}
