#![doc(hidden)]

/// Creates an enumeration and function for converting a representation into the
/// enumeration type.
///
/// The syntax is similar to the `c_bitfield` macro, but each value has the
/// syntax `value = enumeration`. `enum` is used instead of `struct`.
///
/// This will generate an `enum $t` as well as implement `TryFrom` on it which
/// will return `Result<$t, ReprError>`.
///
/// # Examples
///
/// ```
/// use maraiah::{c_enum, err::ReprError};
/// use std::convert::TryFrom;
///
/// c_enum! {
/// 	enum MyEnum: u16 {
/// 		Zero = 0,
/// 		One  = 1,
/// 		Two  = 2
/// 	}
/// }
///
/// assert_eq!(MyEnum::try_from(0), Ok(MyEnum::Zero));
/// assert_eq!(MyEnum::try_from(1), Ok(MyEnum::One));
/// assert_eq!(MyEnum::try_from(2), Ok(MyEnum::Two));
/// assert_eq!(MyEnum::try_from(3), Err(ReprError::new("MyEnum", 3)));
/// assert_eq!(MyEnum::try_from(4), Err(ReprError::new("MyEnum", 4)));
/// assert_eq!(MyEnum::try_from(5), Err(ReprError::new("MyEnum", 5)));
/// ```
#[macro_export]
macro_rules! c_enum
{
	(
		$(#[$outer:meta])*
		$vi:vis enum $t:ident: $ti:ident {
			$(
				$(#[$inner:meta])*
				$en:ident = $va:expr
			),+
			$(,)?
		}
	) => {
		$(#[$outer])*
		#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
		#[repr($ti)]
		$vi enum $t {
			$(
				$(#[$inner])*
				$en = $va,
			)+
		}

		#[allow(unused_qualifications)]
		impl std::convert::TryFrom<$ti> for $t
		{
			type Error = $crate::err::ReprError;

			/// Returns, if representable, the variant of `Self` from `n`.
			fn try_from(n: $ti) -> Result<Self, Self::Error>
			{
				match n {
					$($va => Ok($t::$en),)+
					n => Err(Self::Error::new(stringify!($t), n))
				}
			}
		}

		#[allow(unused_qualifications)]
		impl std::str::FromStr for $t
		{
			type Err = $crate::err::ParseEnumError;

			fn from_str(s: &str) -> Result<Self, Self::Err>
			{
				match s {
					$(
						stringify!($en) => Ok($t::$en),
					)+
					_ => Err(Self::Err::new(stringify!($t)))
				}
			}
		}
	};
}

#[cfg(test)]
mod test
{
	use crate::err::{ParseEnumError, ReprError};
	use std::{convert::TryFrom, str::FromStr};

	c_enum! {
		enum TestEnum: u16 {
			Zero = 0,
			One  = 1,
			Two  = 2,
		}
	}

	#[test]
	fn c_enum()
	{
		assert_eq!(TestEnum::try_from(0), Ok(TestEnum::Zero));
		assert_eq!(TestEnum::try_from(1), Ok(TestEnum::One));
		assert_eq!(TestEnum::try_from(2), Ok(TestEnum::Two));
		assert_eq!(TestEnum::try_from(3), Err(ReprError::new("TestEnum", 3)));
		assert_eq!(TestEnum::try_from(4), Err(ReprError::new("TestEnum", 4)));
		assert_eq!(TestEnum::try_from(5), Err(ReprError::new("TestEnum", 5)));
		assert_eq!(TestEnum::from_str("Zero"), Ok(TestEnum::Zero));
		assert_eq!(TestEnum::from_str("One"), Ok(TestEnum::One));
		assert_eq!(TestEnum::from_str("Two"), Ok(TestEnum::Two));
		assert_eq!(TestEnum::from_str("Three"),
		           Err(ParseEnumError::new("TestEnum")));
	}
}

// EOF
