use super::LonghandId;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct PropertyMask {
  words: [usize; Self::WORD_COUNT],
}

impl PropertyMask {
  const BITS_PER_WORD: usize = usize::BITS as usize;
  const WORD_COUNT: usize = LonghandId::COUNT.div_ceil(Self::BITS_PER_WORD);

  pub const fn new() -> Self {
    Self {
      words: [0; Self::WORD_COUNT],
    }
  }

  pub fn insert(&mut self, property: LonghandId) -> bool {
    let word_index = property.index() / Self::BITS_PER_WORD;
    let bit_index = property.index() % Self::BITS_PER_WORD;
    let bit = 1usize << bit_index;
    let word = &mut self.words[word_index];
    let was_present = (*word & bit) != 0;
    *word |= bit;
    !was_present
  }

  pub fn contains(&self, property: &LonghandId) -> bool {
    let word_index = property.index() / Self::BITS_PER_WORD;
    let bit_index = property.index() % Self::BITS_PER_WORD;
    (self.words[word_index] & (1usize << bit_index)) != 0
  }

  pub fn append(&mut self, other: &mut Self) {
    for (word, other_word) in self.words.iter_mut().zip(other.words.iter_mut()) {
      *word |= *other_word;
      *other_word = 0;
    }
  }

  pub fn iter(&self) -> PropertyMaskIter<'_> {
    PropertyMaskIter {
      mask: self,
      word_index: 0,
      current_word: if Self::WORD_COUNT > 0 {
        self.words[0]
      } else {
        0
      },
    }
  }
}

impl Default for PropertyMask {
  fn default() -> Self {
    Self::new()
  }
}

impl Extend<LonghandId> for PropertyMask {
  fn extend<T: IntoIterator<Item = LonghandId>>(&mut self, iter: T) {
    for property in iter {
      self.insert(property);
    }
  }
}

impl FromIterator<LonghandId> for PropertyMask {
  fn from_iter<T: IntoIterator<Item = LonghandId>>(iter: T) -> Self {
    let mut mask = Self::new();
    mask.extend(iter);
    mask
  }
}

pub(crate) struct PropertyMaskIter<'a> {
  mask: &'a PropertyMask,
  word_index: usize,
  current_word: usize,
}

impl Iterator for PropertyMaskIter<'_> {
  type Item = LonghandId;

  fn next(&mut self) -> Option<Self::Item> {
    loop {
      if self.current_word != 0 {
        let bit = self.current_word.trailing_zeros() as usize;
        self.current_word &= self.current_word - 1;
        let index = self.word_index * PropertyMask::BITS_PER_WORD + bit;
        if index >= LonghandId::COUNT {
          return None;
        }
        return Some(LonghandId::ALL[index]);
      }
      self.word_index += 1;
      if self.word_index >= PropertyMask::WORD_COUNT {
        return None;
      }
      self.current_word = self.mask.words[self.word_index];
    }
  }
}
