mod mune;

use proc_macro::{
  Delimiter, Spacing, TokenStream,
  TokenTree::{self, *},
  token_stream::IntoIter,
};

/// Extend enums to provide additional functionality
///
/// # Added constants
///
/// ## <code>REPR: [Option]<&[str]></code>
///
/// The type declared in `#[repr]` if present, as a string
///
/// ## <code>NUM_VARIANTS: [usize]</code>
///
/// The number of variants in the enum
///
/// ## `FIRST_VARIANT: Self`
///
/// The first variant as defined in source order
///
/// ## `LAST_VARIANT: Self`
///
/// The last variant as defined in source order
///
/// ## `MAX_DISCRIMINANT: <repr>`
///
/// **Available only on numeric `#[repr]`s.**
///
/// The highest numerical discriminant value among all variants. The type is the same as the
/// `#[repr]` of the enum
///
/// # Features
///
/// ## `#[mune(deref_discriminant)]`
///
/// **Available only on numeric `#[repr]`s.**
///
/// Implements [`Deref`][::core::ops::Deref] with a [`Target`][::core::ops::Deref::Target] of the
/// `#[repr]` of the enum allowing you to simply do `*MyEnum::Variant` to get the value of the
/// discriminant
///
/// # Limitations
///
/// * Generic parameters and `where` clauses are currently unsupported
/// * Only unit enums are supported for now, and without explicit discriminants
/// * Only `#[repr]`s with the type are supported
///
/// # Examples
///
/// ```
/// #[pm2::mune]
/// #[derive(Debug, PartialEq)]
/// enum MyEnum {
///   A,
///   B,
///   C,
///   D,
/// }
///
/// assert_eq!(MyEnum::REPR, None);
/// assert_eq!(MyEnum::NUM_VARIANTS, 4);
/// assert_eq!(MyEnum::FIRST_VARIANT, MyEnum::A);
/// assert_eq!(MyEnum::LAST_VARIANT, MyEnum::D);
/// ```
///
/// ```
/// #[pm2::mune]
/// #[repr(u8)]
/// #[derive(Debug, PartialEq)]
/// enum MyEnum {
///   A,
///   B,
///   C,
///   D,
/// }
///
/// assert_eq!(MyEnum::REPR, Some("u8"));
/// ```
///
/// ```
/// #[pm2::mune(deref_discriminant)]
/// #[repr(u8)]
/// #[derive(Debug, PartialEq)]
/// enum MyEnum {
///   A,
///   B,
///   C,
///   D,
/// }
///
/// let a: u8 = *MyEnum::A;
/// assert_eq!(a, 0);
///
/// assert_eq!(*MyEnum::B, 1);
/// assert_eq!(*MyEnum::C, 2);
/// assert_eq!(*MyEnum::D, 3);
/// ```
#[proc_macro_attribute]
pub fn mune(attr: TokenStream, item: TokenStream) -> TokenStream {
  mune::run(attr, item)
}

fn seek_ident(s: &mut IntoIter, ident: &str) -> bool {
  for t in s {
    if let Ident(i) = t
      && i.to_string() == ident
    {
      return true;
    }
  }

  false
}

fn get_idents(tt: &[TokenTree]) -> Vec<String> {
  tt.iter()
    .filter_map(|t| {
      if let Ident(i) = t {
        Some(i.to_string())
      } else {
        None
      }
    })
    .collect()
}

fn get_repr(tt: &[TokenTree]) -> Option<String> {
  for (i, t) in tt.iter().enumerate() {
    if let Punct(p) = t
      && *p == '#'
      && p.spacing() == Spacing::Alone
      && i < tt.len() - 1
      && let Group(g) = &tt[i + 1]
      && g.delimiter() == Delimiter::Bracket
      && let mut s = g.stream().into_iter()
      && let Some(Ident(id)) = s.next()
      && id.to_string() == "repr"
      && let Some(Group(g)) = s.next()
      && g.delimiter() == Delimiter::Parenthesis
      && let mut s = g.stream().into_iter()
      && let Some(Ident(id)) = s.next()
    {
      return Some(id.to_string());
    }
  }

  None
}
