//! A [`Type`] based [`Identifier`]
//!
//! This works by making it `unsafe` to create a [`Type`].
//! Then by providing macro that safely creates a new type
//! and a `Type` identifier at the same time. Guaranteeing
//! that there is no duplication.
//!
//! You can also create a new `Type`, but then the burden
//! is on you to ensure uniqueness.

use core::{
    fmt,
    hash::{Hash, Hasher},
    marker::PhantomData,
};

use crate::{Identifier, Init, OneShotIdentifier, Token, Trivial};

struct Invariant<T>(fn() -> *mut T);
/// A type based [`Identifier`]
pub struct Type<T> {
    invariant: PhantomData<Invariant<T>>,
}

/// The [`Trivial`] [`Token`] generated by [`Type`]
pub struct TypeToken<T> {
    invariant: PhantomData<Invariant<T>>,
}

/// Create a new [`Type<T>`](Type)
///
/// Two calls to `scope` will never return the same type
/// (the difference is in the types)
///
/// ```rust, compile_fail
/// let a = pui_core::make_type_id!();
/// let b = pui_core::make_type_id!();
/// assert_eq!(a, b);
/// ```
///
/// ```rust
/// let a = pui_core::make_type_id!();
/// assert_eq!(a, a);
/// ```
///
/// You can also declare your own unit struct that
/// that will be used to generate a `Type`. Note:
/// this can only be used in positions where `let`
/// bindings are valid.
///
/// ```rust
/// pui_core::make_type_id! {
///     /// Documentation for Foo
///     #[derive(Debug, Clone, Copy)]
///     pub let Foo;
/// }
///
/// let token = Foo.token();
/// ```
///
/// Or in positions where `static` is valid.
///
/// ```rust
/// pui_core::make_type_id! {
///     /// Documentation for Foo
///     #[derive(Debug, Clone, Copy)]
///     pub static Foo;
/// }
///
/// let token = Foo.token();
/// ```
#[macro_export]
macro_rules! make_type_id {
    () => {
        unsafe { struct Anonomous; $crate::ty::Type::__internal_macro_new__(&Anonomous) }
    };
    (
        $(#[$meta:meta])*
        $v:vis let $name:ident;
    ) => {
        $(#[$meta])*
        #[allow(non_camel_case_types)]
        $v struct $name {}
        let $name = unsafe { $crate::ty::Type::__internal_macro_new__(&$name {}) };
    };
    (
        $(#[$meta:meta])*
        $v:vis static $name:ident;
    ) => {
        $(#[$meta])*
        #[allow(non_camel_case_types)]
        $v struct $name {}
        static $name: $crate::ty::Type<$name> = unsafe { $crate::ty::Type::__internal_macro_new__(&$name {}) };
    };
}

impl<T> Default for TypeToken<T> {
    fn default() -> Self { Self { invariant: PhantomData } }
}

impl<T> Type<T> {
    #[doc(hidden)]
    pub const unsafe fn __internal_macro_new__(_: &T) -> Self { Self::new_unchecked() }

    /// Create a new `Type<T>`
    ///
    /// # Safety
    ///
    /// There should be only one `Type` created for any given `T`
    pub const unsafe fn new_unchecked() -> Self { Self { invariant: PhantomData } }

    /// Create a new token
    #[inline]
    pub const fn token(&self) -> TypeToken<T> { TypeToken::new() }
}

impl<T> TypeToken<T> {
    /// Create a new token
    pub const fn new() -> Self { Self { invariant: PhantomData } }
}

impl<T> Trivial for TypeToken<T> {}
unsafe impl<T> Token for TypeToken<T> {}
unsafe impl<T> OneShotIdentifier for Type<T> {}
unsafe impl<T> Identifier for Type<T> {
    type Token = TypeToken<T>;

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

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

impl<T> Init for TypeToken<T> {
    const INIT: Self = Self { invariant: PhantomData };
}

impl<T> fmt::Debug for TypeToken<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "TypeToken<{}>", core::any::type_name::<T>()) }
}

impl<T> Copy for TypeToken<T> {}
impl<T> Clone for TypeToken<T> {
    fn clone(&self) -> Self { *self }
}

impl<T> Eq for TypeToken<T> {}
impl<T> PartialEq for TypeToken<T> {
    fn eq(&self, _: &Self) -> bool { true }
}

impl<T> PartialOrd for TypeToken<T> {
    fn partial_cmp(&self, _: &Self) -> Option<core::cmp::Ordering> { Some(core::cmp::Ordering::Equal) }
}
impl<T> Ord for TypeToken<T> {
    fn cmp(&self, _: &Self) -> core::cmp::Ordering { core::cmp::Ordering::Equal }
}

impl<T> Hash for TypeToken<T> {
    fn hash<H: Hasher>(&self, state: &mut H) { ().hash(state) }
}

impl<T> fmt::Debug for Type<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Type<{}>", core::any::type_name::<T>()) }
}

impl<T> Eq for Type<T> {}
impl<T> PartialEq for Type<T> {
    fn eq(&self, _: &Self) -> bool { true }
}

impl<T> PartialOrd for Type<T> {
    fn partial_cmp(&self, _: &Self) -> Option<core::cmp::Ordering> { Some(core::cmp::Ordering::Equal) }
}
impl<T> Ord for Type<T> {
    fn cmp(&self, _: &Self) -> core::cmp::Ordering { core::cmp::Ordering::Equal }
}

impl<T> Hash for Type<T> {
    fn hash<H: Hasher>(&self, state: &mut H) { ().hash(state) }
}
