use crate::reaction::{Reaction, ReactionDetail};
use enum_map::Enum;
use enumset::{enum_set, EnumSet, EnumSetType};
use Element::*;

#[repr(u8)]
#[derive(Enum, EnumSetType, Debug)]
pub enum Element {
  OMNI,
  PYRO,
  HYDRO,
  ANEMO,
  ELECTRO,
  DENDRO,
  CRYO,
  GEO,
  PHYSICAL,
  PIERCING,
  ANY,
}

impl Element {
  pub fn is_pure(self) -> bool {
    !matches!(self, OMNI | PHYSICAL | PIERCING | ANY)
  }
}


pub const PURE_ELEMENTS: ElementSet = elem_set!(PYRO, HYDRO, ANEMO, ELECTRO, DENDRO, CRYO, GEO);
pub const ORDERED_AURA_ELEMENTS: [Element; 5] = [PYRO, HYDRO, ELECTRO, CRYO, DENDRO];
pub const AURA_ELEMENTS: ElementSet = elem_set!(PYRO, HYDRO, ELECTRO, CRYO, DENDRO);
pub const ORDERED_LEGAL_ACTUAL_DICE_ELEMENTS: [Element; 8] =
  [OMNI, CRYO, HYDRO, PYRO, ELECTRO, GEO, DENDRO, ANEMO];
pub const LEGAL_ACTUAL_DICE_ELEMENTS: ElementSet =
  elem_set!(OMNI, PYRO, HYDRO, ANEMO, ELECTRO, DENDRO, CRYO, GEO);
pub const LEGAL_ABSTRACT_DICE_ELEMENTS: ElementSet =
  elem_set!(OMNI, PYRO, HYDRO, ANEMO, ELECTRO, DENDRO, CRYO, GEO, ANY);

#[derive(Clone, Copy, PartialEq, Debug)]
pub struct ElementalAura(ElementSet);

impl ElementalAura {
  pub fn aurable(elem: Element) -> bool {
    AURA_ELEMENTS.contains(elem)
  }

  pub fn peek(self) -> Option<Element> {
    for elem in ORDERED_AURA_ELEMENTS.iter() {
      if self.0.contains(*elem) {
        return Some(*elem);
      }
    }
    None
  }

  pub fn remove(self, elem: Element) -> ElementalAura {
    assert!(Self::aurable(elem));
    ElementalAura(self.0 - elem)
  }

  pub fn add(self, elem: Element) -> ElementalAura {
    assert!(Self::aurable(elem));
    ElementalAura(self.0 | elem)
  }

  pub fn contains(self, elem: Element) -> bool {
    assert!(Self::aurable(elem));
    self.0.contains(elem)
  }

  pub fn has_aura(self) -> bool {
    !self.0.is_empty()
  }

  pub fn elem_auras(self) -> ElementSet {
    self.0
  }

  pub fn consult_reaction(self, income: Element) -> Option<ReactionDetail> {
    for elem in ORDERED_AURA_ELEMENTS.into_iter() {
      if self.contains(elem) {
        if let Some(detail) = Reaction::consult_reaction_detail(elem, income) {
          return Some(detail);
        }
      }
    }
    None
  }
}

pub type ElementSet = EnumSet<Element>;

#[macro_export]
macro_rules! _elem_set {
  ($(|)*) => { { use enumset::enum_set; enum_set!() } };
  ($value:path $(,)*) => { { use enumset::enum_set; enum_set!($value) } };
  ($value:path , $($rest:path),* $(,)*) => { { use enumset::enum_set; enum_set!($value $(| $rest)*) } };
}

pub(crate) use _elem_set as elem_set;