#![doc(hidden)]

/// Creates an enumeration and function for converting a representation into
/// the enumeration type.
///
/// The syntax is similar to the `bitflags` 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, durandal::err::ReprError};
/// use std::convert::TryFrom;
///
/// c_enum! {
///    #[derive(Debug)]
///    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(3)));
/// assert_eq!(MyEnum::try_from(4), Err(ReprError::new(4)));
/// assert_eq!(MyEnum::try_from(5), Err(ReprError::new(5)));
/// ```
#[macro_export]
macro_rules! c_enum
{
   (
      $(#[$outer:meta])*
      $vi:vis enum $t:ident: $ti:ident
      {
         $($en:ident = $va:expr),+ $(,)?
      }
   ) => {
      $(#[$outer])*
      #[derive(Copy, Clone, Eq, Ord, PartialEq, PartialOrd)]
      #[repr($ti)]
      $vi enum $t
      {
         $($en = $va,)+
      }

      #[allow(unused_qualifications)]
      impl std::convert::TryFrom<$ti> for $t
      {
         type Error = $crate::durandal::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(n))
            }
         }
      }
   };
}

#[cfg(test)]
mod test
{
   use crate::durandal::err::ReprError;
   use std::convert::TryFrom;

   c_enum! {
      #[derive(Debug)]
      enum TestEnum: u16
      {
         Zero = 0,
         One  = 1,
         Two  = 2,
      }
   }

   #[test]
   fn c_enum_should_be_ok()
   {
      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(3)));
      assert_eq!(TestEnum::try_from(4), Err(ReprError::new(4)));
      assert_eq!(TestEnum::try_from(5), Err(ReprError::new(5)));
   }

   #[test]
   #[should_panic]
   fn c_enum_should_error() {TestEnum::try_from(3).unwrap();}
}

// EOF
