use enumset::EnumSetType;
use Reaction::*;

#[repr(u8)]
#[derive(EnumSetType, Debug)]
pub enum Reaction {
  BLOOM,
  BURNING,
  CRYSTALLIZE,
  ELECTROCHARGED,
  FROZEN,
  MELT,
  OVERLOADED,
  QUICKEN,
  SUPERCONDUCT,
  SWIRL,
  VAPORIZE,
}

use crate::element::Element::{self, *};
use crate::element::ElementSet;
use crate::element::{elem_set, ElementalAura};

macro_rules! test {
  ($fst:expr, $snd:expr; $value:path, $($rest:path),* $(,)*) => {
    if $value.test_reaction($fst, $snd) { return Some($value) };
    $(if $rest.test_reaction($fst, $snd) { return Some($value) });*
  }
}

macro_rules! test_detail {
  ($fst:expr, $snd:expr; $value:path, $($rest:path),* $(,)*) => {
    if $value.test_reaction($fst, $snd) { return Some(ReactionDetail::new($value, $fst, $snd)) };
    $(if $rest.test_reaction($fst, $snd) { return Some(ReactionDetail::new($value, $fst, $snd)) });*
  }
}

impl Reaction {
  pub const fn reaction_elems(self) -> (ElementSet, ElementSet) {
    use elem_set as S;
    match self {
      BLOOM => (S!(DENDRO), S!(HYDRO)),
      BURNING => (S!(DENDRO), S!(PYRO)),
      CRYSTALLIZE => (S!(PYRO, HYDRO, CRYO, ELECTRO), S!(GEO)),
      ELECTROCHARGED => (S!(HYDRO), S!(ELECTRO)),
      FROZEN => (S!(HYDRO), S!(CRYO)),
      MELT => (S!(PYRO), S!(CRYO)),
      OVERLOADED => (S!(PYRO), S!(ELECTRO)),
      QUICKEN => (S!(DENDRO), S!(ELECTRO)),
      SUPERCONDUCT => (S!(CRYO), S!(ELECTRO)),
      SWIRL => (S!(PYRO, HYDRO, CRYO, ELECTRO), S!(ANEMO)),
      VAPORIZE => (S!(HYDRO), S!(PYRO)),
    }
  }
  pub const fn damage_boost(self) -> usize {
    match self {
      BLOOM => 1,
      BURNING => 1,
      CRYSTALLIZE => 1,
      ELECTROCHARGED => 1,
      FROZEN => 1,
      MELT => 2,
      OVERLOADED => 2,
      QUICKEN => 1,
      SUPERCONDUCT => 1,
      SWIRL => 0,
      VAPORIZE => 2,
    }
  }

  fn test_reaction(self, fst: Element, snd: Element) -> bool {
    let (x, y) = self.reaction_elems();
    x.contains(fst) && y.contains(snd) || y.contains(fst) && x.contains(snd)
  }

  pub fn consult_reaction(fst: Element, snd: Element) -> Option<Reaction> {
    test!(fst, snd;
      BURNING,
      CRYSTALLIZE,
      ELECTROCHARGED,
      FROZEN,
      MELT,
      OVERLOADED,
      QUICKEN,
      SUPERCONDUCT,
      SWIRL,
      VAPORIZE,
    );
    None
  }

  pub fn consult_reaction_detail(fst: Element, snd: Element) -> Option<ReactionDetail> {
    test_detail!(fst, snd;
      BURNING,
      CRYSTALLIZE,
      ELECTROCHARGED,
      FROZEN,
      MELT,
      OVERLOADED,
      QUICKEN,
      SUPERCONDUCT,
      SWIRL,
      VAPORIZE,
    );
    None
  }

  pub fn consult_reaction_with_aura(aura: ElementalAura, snd: Element) -> Option<ReactionDetail> {
    aura.consult_reaction(snd)
  }
}

pub struct ReactionDetail {
  reaction: Reaction,
  fst: Element,
  snd: Element,
}

impl ReactionDetail {
  pub const fn new(reaction: Reaction, fst: Element, snd: Element) -> Self {
    Self { reaction, fst, snd }
  }

  pub fn elem_reaction(self, elem: Element) -> bool {
    self.fst == elem || self.snd == elem
  }
}