// SPDX-License-Identifier: MIT OR Apache-2.0

//! This module provides tooling that facilitates dealing with C-style enums
//!
//! C-style enums and Rust-style enums are quite different. There are things
//! which one allows, but not the other, and vice versa. In an FFI context, two
//! aspects of C-style enums are particularly bothersome to us:
//!
//! - They allow a caller to send back an unknown enum variant. In Rust, the
//!   mere act of storing such a variant in a variable is undefined behavior.
//! - They have an implicit conversion to integers, which is often used as a
//!   more portable alternative to C bitfields or as a way to count the amount
//!   of variants of an enumerated type. Rust enums do not model this well.
//!
//! Therefore, in many cases, C enums are best modeled as newtypes of integers
//! featuring a large set of associated constants instead of as Rust enums. This
//! module provides facilities to simplify this kind of FFI.

/// Interface a C-style enum as an integer newtype.
///
/// This macro implements Debug for you, the way you would expect it to work on
/// Rust enums (printing the variant name instead of its integer value). It also
/// derives Clone, Copy, Eq, PartialEq, Ord, PartialOrd, and Hash, since that
/// always makes sense for C-style enums. If you want anything else
/// to be derived, you can ask for it by adding extra derives as shown in the
/// example below.
///
/// One minor annoyance is that since variants will be translated into
/// associated constants in a separate impl block, you need to discriminate
/// which attributes should go on the type and which should go on the impl
/// block. The latter should go on the right-hand side of the arrow operator.
///
/// Usage example:
/// ```
/// # use uefi_raw::newtype_enum;
/// newtype_enum! {
/// #[derive(Default)]
/// pub enum UnixBool: i32 => #[allow(missing_docs)] {
///     FALSE          =  0,
///     TRUE           =  1,
///     /// Nobody expects the Unix inquisition!
///     FILE_NOT_FOUND = -1,
/// }}
/// ```
#[macro_export]
macro_rules! newtype_enum {
    (
        $(#[$type_attrs:meta])*
        $visibility:vis enum $type:ident : $base_integer:ty => $(#[$impl_attrs:meta])* {
            $(
                $(#[$variant_attrs:meta])*
                $variant:ident = $value:expr,
            )*
        }
    ) => {
        $(#[$type_attrs])*
        #[repr(transparent)]
        #[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
        $visibility struct $type(pub $base_integer);

        $(#[$impl_attrs])*
        #[allow(unused)]
        impl $type {
            $(
                $(#[$variant_attrs])*
                pub const $variant: $type = $type($value);
            )*
        }

        impl core::fmt::Debug for $type {
            fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
                match *self {
                    // Display variants by their name, like Rust enums do
                    $(
                        $type::$variant => write!(f, stringify!($variant)),
                    )*

                    // Display unknown variants in tuple struct format
                    $type(unknown) => {
                        write!(f, "{}({})", stringify!($type), unknown)
                    }
                }
            }
        }
    }
}
