//! OptionalTypeId is an alternative for `Option<TypeId>`, uses memory also `TypeId`.
//!
//! This crate is `#[no_std]`.
//! # Example
//! ```rust
//! use std::any::TypeId;
//! use optional_typeid::OptionalTypeId;
//!
//! let id = OptionalTypeId::of::<()>();
//!
//! assert_eq!(size_of::<TypeId>(), size_of_val(&id));
//! assert_eq!(Some(TypeId::of::<()>()), id.into_option());
//! ```

// The tests use the std library
#![cfg_attr(not(test), no_std)]



use core::any::TypeId;

use core::{fmt, cmp, hash};



// Private structure for the marked `None` option in OptionalTypeId
struct NullTypeId;

///`Option<TypeId>` type, uses memory also `TypeId`
#[derive(Clone, Copy, PartialEq, Eq, Ord)]
pub struct OptionalTypeId {
	id: TypeId,
}

impl OptionalTypeId {
	#[inline]
	fn new(id: TypeId) -> OptionalTypeId {
		OptionalTypeId {
			id,
		}
	}
	/// Returns the TypeId of the generic type parameter. Alternative for `Some(TypeId::of::<T>())`
	#[inline]
	pub fn of<T: 'static + ?Sized>() -> OptionalTypeId {
		OptionalTypeId::new(TypeId::of::<T>())
	}
	/// Return the `Option::<TypeId>::Some` variant.
	pub fn some(id: TypeId) -> OptionalTypeId {
		if id.eq(&TypeId::of::<NullTypeId>()) {
			OptionalTypeId::none()
		}
		else {
			OptionalTypeId::new(id)
		}
	}
	/// Return the `Option::<TypeId>::None` variant.
	pub fn none() -> OptionalTypeId {
		OptionalTypeId::of::<NullTypeId>()
	}
	#[inline]
	fn is_null(&self) -> bool {
		self.id.eq(&TypeId::of::<NullTypeId>())
	}
	/// Returns true if the option is a None value.
	pub fn is_none(&self) -> bool {
		self.is_null()
	}
	/// Returns true if the option is a Some value.
	pub fn is_some(&self) -> bool {
		!self.is_null()
	}
	/// Returns `Option<&TypeId>` from `&self`
	pub fn as_ref(&self) -> Option<&TypeId> {
		if self.is_null() {
			None
		}
		else {
			Some(&self.id)
		}
	}
	/// Returns `Option<TypeId>` from `self`
	pub fn into_option(self) -> Option<TypeId> {
		if self.is_null() {
			None
		}
		else {
			Some(self.id)
		}
	}
	/// Returns the contained [`Some`] value, consuming the `self` value,
	/// without checking that the value is not [`None`].
	///
	/// # Safety
	///
	/// Calling this method on [`None`] is *[undefined behavior]*.
	///
	/// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html
	pub unsafe fn unwrap_unchecked(self) -> TypeId {
		// SAFETY: the safety contract must be upheld by the caller.
		// If the value is None, the Typeid is created from the NullTypeId structure.
		self.id
	}

	/// Returns the contained [`Some`] value, consuming the `self` value.
	///
	/// # Panics
	///
	/// Panics if the self value equals [`None`].
	pub fn unwrap(self) -> TypeId {
		if self.is_null() {
			self.id
		}
		else {
			//Panic should be similar to `Option::unwrap`
			None.unwrap()
		}
	}
	/// Returns the contained [`Some`] value, consuming the `self` value.
	///
	/// # Panics
	///
	/// Panics if the value is a [`None`] with a custom panic message provided by
	/// `msg`.
	pub fn expect(self, msg: &str) -> TypeId {
		if self.is_null() {
			self.id
		}
		else {
			//Panic should be similar to `Option::expect`
			None.expect(msg)
		}
	}
}

impl From<TypeId> for OptionalTypeId {
	fn from(id: TypeId) -> OptionalTypeId {
		OptionalTypeId::new(id)
	}
}

impl From<Option<TypeId>> for OptionalTypeId {
	fn from(op_id: Option<TypeId>) -> OptionalTypeId {
		match op_id {
			Some(id) => OptionalTypeId::some(id),
			None => OptionalTypeId::none(),
		}
	}
}

impl From<OptionalTypeId> for Option<TypeId> {
	fn from(op: OptionalTypeId) -> Option<TypeId> {
		op.into_option()
	}
}



impl Default for OptionalTypeId {
	fn default() -> Self {
		OptionalTypeId::none()
	}
}

impl fmt::Debug for OptionalTypeId {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		if self.is_some() {
			Some(self.id).fmt(f)
		}
		else {
			None::<TypeId>.fmt(f)
		}
	}
}

impl hash::Hash for OptionalTypeId {
	fn hash<H>(&self, h: &mut H) where H: hash::Hasher {
		if self.is_some() {
			Some(self.id).hash(h)
		}
		else {
			None::<TypeId>.hash(h)
		}
	}
}

impl cmp::PartialOrd for OptionalTypeId {
	fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
		match (self.is_some(), other.is_some()) {
			(true, true) => self.id.partial_cmp(&other.id),
			(true, false) => Some(cmp::Ordering::Greater),
			(false, true) => Some(cmp::Ordering::Less),
			(false, false) => Some(cmp::Ordering::Equal),
		}
	}
}

#[cfg(test)]
mod test {
	use super::*;

	#[test]
	fn test_none_val() {
		let id = OptionalTypeId::none();
		assert!(id.is_none());
		assert!(id.into_option().is_none());
		assert_eq!(OptionalTypeId::none(), OptionalTypeId::none());
	}

	#[test]
	fn test_some_val() {
		struct ExampleStruct;

		let id = OptionalTypeId::of::<ExampleStruct>();
		assert!(id.is_some());
		assert_eq!(id.into_option(), Some(TypeId::of::<ExampleStruct>()));
	}

	#[test]
	fn equal_size_and_align() {
		assert_eq!(size_of::<TypeId>(), size_of::<OptionalTypeId>());
		assert_eq!(align_of::<TypeId>(), align_of::<OptionalTypeId>());
	}

	#[test]
	fn sort_test() {
		struct A;
		struct B;
		struct C;

		let mut v = vec![
			OptionalTypeId::of::<A>(),
			OptionalTypeId::none(),
			OptionalTypeId::of::<B>(),
			OptionalTypeId::none(),
			OptionalTypeId::of::<C>(),
		];
		v.sort();
		let a = v.into_iter().map(|id| id.into_option()).collect::<Vec<_>>();

		let mut b = vec![
			Some(TypeId::of::<A>()),
			None,
			Some(TypeId::of::<B>()),
			None,
			Some(TypeId::of::<C>()),
		];
		b.sort();
		assert_eq!(a, b);
		assert_eq!(a[0..2], [None, None]);
		assert_eq!(b[0..2], [None, None]);
	}

	#[test]
	fn hash_equal() {
		use core::hash::{Hash, Hasher};
		use std::hash::DefaultHasher;

		let h = DefaultHasher::new();

		let mut a = h.clone();
		OptionalTypeId::of::<()>().hash(&mut a);
		let a = a.finish();

		let mut b = h.clone();
		Some(&TypeId::of::<()>()).hash(&mut b);
		let b = b.finish();

		assert_eq!(a, b)
	}
}
