//! A [`Scoped`] [`Identifier`]
//!
//! This works on two different principles.
//!
//! The first is that HRTB (higher rank type bounds) produces
//! fresh lifetimes on each call, and they can't be unified
//! against each other across multiple calls to `Scoped::with`.
//!
//! This allows [`Scoped::with`] to prevent the following at
//! compile time:
//! ```rust,compile_fail
//! # use pui_core::Scoped;
//! Scoped::with(|a: Scoped<'_>| {
//!     Scoped::with(|b: Scoped<'_>| {
//!         assert_eq!(a, b);
//!     })
//! })
//! ```
//!
//! This means that all `Scoped` types created with `Scoped::with`
//! are guaranteed to be unique! If you can't compare them, then
//! they can never compare equal.
//!
//! The second principle is based off of
//! [`generativity`](https://crates.io/crates/generativity)
//! where macros are used to generate the boilerplate to create
//! unique lifetimes. You can read about the details in `generativity`'s
//! documentation.
//!
//! This allows [`scope`] to produce unique `Scoped`s.
//!
//! ```rust,compile_fail
//! # use pui_core::scope;
//! scope!(a);
//! scope!(b);
//! assert_eq!(a, b);
//! ```
//!
//! ## How to decide which to use
//!
//! `Scoped::with` generally produces better error messages, and tends
//! to be easier to reason about. So it should be the first choice.
//! However, if you are using some strange control flow or need early returns,
//! and it would be easier to embed a `Scoped` directly in the current scope,
//! then you should use the `scope` macro.
//!

use core::marker::PhantomData;

use crate::{Identifier, Token};

/// Create a new [`Scoped<'_>`](Scoped) with the given name
/// without using a closure
///
/// ```rust
/// pui_core::scope!(scope);
/// ```
///
/// Two calls to `scope` will never return the same type
/// (the difference is in the lifetimes)
///
/// ```rust, compile_fail
/// pui_core::scope!(a);
/// pui_core::scope!(b);
/// assert_eq!(a, b);
/// ```
///
/// ```rust
/// pui_core::scope!(a);
/// assert_eq!(a, a);
/// ```
#[macro_export]
macro_rules! scope {
    ($scope:ident) => {
        let $scope = unsafe { $crate::scoped::Scoped::new_unchecked() };

        let scope = ();
        let assert_unique_scope = $crate::scoped::export::AssertUniqueScope::new(&$scope);
        assert_unique_scope.bind(&scope);
    };
}

#[doc(hidden)]
pub mod export {
    use core::marker::PhantomData;

    pub struct AssertUniqueScope<'a>(PhantomData<super::Invariant<'a>>);

    impl Drop for AssertUniqueScope<'_> {
        #[inline(always)]
        fn drop(&mut self) {}
    }

    impl<'a> AssertUniqueScope<'a> {
        #[inline(always)]
        pub fn new(_: &super::Scoped<'a>) -> Self { Self(PhantomData) }

        #[inline(always)]
        pub fn bind(&self, _: &'a ()) {}
    }
}

struct Invariant<'a>(fn() -> *mut &'a ());

/// A scoped [`Identifier`]
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Scoped<'a> {
    invariant: PhantomData<Invariant<'a>>,
}

/// The [`Trivial`](crate::Trivial) [`Token`] generated by [`Scoped`]
#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ScopedToken<'a> {
    invariant: PhantomData<Invariant<'a>>,
}

impl<'scope> Scoped<'scope> {
    #[doc(hidden)]
    #[inline(always)]
    pub unsafe fn new_unchecked() -> Self { Self { invariant: PhantomData } }

    /// Call the given closure with a new `Scoped`
    #[inline]
    pub fn with<R, F: FnOnce(Scoped<'_>) -> R>(f: F) -> R { f(unsafe { Self::new_unchecked() }) }

    /// Create a new scoped token
    pub const fn token(&self) -> ScopedToken<'scope> { ScopedToken::new() }
}

impl ScopedToken<'_> {
    /// Create a new scoped token
    #[inline]
    pub const fn new() -> Self { Self { invariant: PhantomData } }
}

impl crate::Init for ScopedToken<'_> {
    const INIT: Self = Self { invariant: PhantomData };
}

impl crate::Trivial for ScopedToken<'_> {}
unsafe impl Token for ScopedToken<'_> {}
unsafe impl crate::OneShotIdentifier for Scoped<'_> {}
unsafe impl<'a> Identifier for Scoped<'a> {
    type Token = ScopedToken<'a>;

    #[inline]
    fn owns_token(&self, _: &Self::Token) -> bool { true }

    #[inline]
    fn token(&self) -> Self::Token { ScopedToken::new() }
}

use core::fmt;

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

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