use rand::prelude::IteratorRandom;
use crate::{Buttons, LightsSetter};
use super::button::Button;
use std::convert::TryInto;
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord)]
pub struct Lights {
bits: u64,
}
impl Lights {
pub const COUNT: usize = super::Button::COUNT * 4;
#[must_use]
pub fn from_slice(arr: &[bool]) -> Lights {
assert!(arr.len() <= Self::COUNT);
Self::from_indices(
arr.iter()
.enumerate()
.filter(|&(_, p)| *p)
.map(|(idx, _)| idx),
)
}
#[must_use]
pub const fn from_index(light_index: usize) -> Lights {
Self::from_bitset(1 << light_index)
}
pub fn from_indices<I>(indexes: I) -> Lights
where
I: IntoIterator<Item = usize>,
{
let mut bits: u64 = 0;
for index in indexes {
assert!(index < Self::COUNT);
bits |= 1 << index;
}
Lights { bits }
}
#[must_use]
pub const fn from_bitset(bits: u64) -> Lights {
assert!(bits >> Self::COUNT == 0);
Lights { bits }
}
#[must_use]
pub const fn all_from_button(button: Button) -> Lights {
Lights::from_bitset(0b1111 << (button.index() * 4))
}
#[must_use]
pub const fn all() -> Lights {
Lights {
bits: (1 << Self::COUNT) - 1,
}
}
#[must_use]
pub const fn none() -> Lights {
Self::from_bitset(0)
}
#[must_use]
pub fn len(&self) -> u32 {
self.bits.count_ones()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[must_use]
pub fn is_superset(&self, other: Self) -> bool {
(self.bits | other.bits) == self.bits
}
#[must_use]
pub fn is_subset(&self, other: Self) -> bool {
other.is_superset(*self)
}
#[must_use]
pub fn indices(&self) -> impl DoubleEndedIterator<Item = usize> + use<> {
Indices { bits: self.bits }
}
#[must_use]
pub const fn invert(&self) -> Lights {
Lights {
bits: (!self.bits) & Lights::all().bits,
}
}
#[must_use]
pub fn choose_n_randomly(&self, n: usize) -> Lights {
let mut chosen = [11; Self::COUNT];
let num_chosen = self
.indices()
.choose_multiple_fill(&mut rand::rng(), &mut chosen[0..n]);
Lights::from_indices(chosen[0..num_chosen].iter().copied())
}
#[must_use]
pub fn rotate_180(&self) -> Lights {
Lights::from_bitset(self.bits.reverse_bits() >> 24)
}
#[must_use]
pub const fn as_bitset(&self) -> u64 {
self.bits
}
pub fn set_color(self, color: crate::color::RGB) {
LightsSetter::get().set_color(self, color);
}
pub fn set_colors(self, colors: &[crate::color::RGB]) {
LightsSetter::get().set_colors_on(self, colors);
}
#[must_use]
pub const fn only(self, dir: LightDir) -> Lights {
let mask = dir.light_on_all_buttons().as_bitset();
Lights::from_bitset(self.as_bitset() & mask)
}
}
impl std::ops::BitAnd for Lights {
type Output = Self;
fn bitand(self, rhs: Self) -> Self::Output {
Lights {
bits: self.bits & rhs.bits,
}
}
}
impl std::ops::BitAndAssign for Lights {
fn bitand_assign(&mut self, rhs: Self) {
*self = *self & rhs;
}
}
impl std::ops::BitOr for Lights {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
Lights {
bits: self.bits | rhs.bits,
}
}
}
impl std::ops::BitOrAssign for Lights {
fn bitor_assign(&mut self, rhs: Self) {
self.bits |= rhs.bits;
}
}
impl std::ops::BitXor for Lights {
type Output = Self;
fn bitxor(self, rhs: Self) -> Self::Output {
Lights {
bits: self.bits ^ rhs.bits,
}
}
}
impl std::ops::BitXorAssign for Lights {
fn bitxor_assign(&mut self, rhs: Self) {
self.bits ^= rhs.bits;
}
}
impl std::ops::Not for Lights {
type Output = Self;
fn not(self) -> Self::Output {
self.invert()
}
}
impl std::iter::IntoIterator for Lights {
type Item = (Button, LightDir);
type IntoIter = Box<dyn Iterator<Item = (Button, LightDir)>>;
fn into_iter(self) -> Self::IntoIter {
Box::new(self.indices().map(|index| {
let button_index = index / 4;
let light_dir_index = index % 4;
(
Button::from_index(button_index),
LightDir::from_index(light_dir_index).unwrap(),
)
}))
}
}
impl From<Button> for Lights {
fn from(button: Button) -> Self {
Lights::all_from_button(button)
}
}
impl From<Buttons> for Lights {
fn from(buttons: Buttons) -> Self {
buttons.lights()
}
}
struct Indices {
bits: u64,
}
impl Iterator for Indices {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
if self.bits == 0 {
None
} else {
let next = self.bits.trailing_zeros().try_into().unwrap();
self.bits = self.bits & (self.bits - 1);
Some(next)
}
}
}
impl DoubleEndedIterator for Indices {
fn next_back(&mut self) -> Option<Self::Item> {
if self.bits == 0 {
None
} else {
let next = self.bits.ilog2();
self.bits ^= 1 << u64::from(next);
Some(next as usize)
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord)]
#[repr(u8)]
#[expect(
missing_docs,
reason = "Variants names are clear. Meaning of direction documented in enum documentation."
)]
pub enum LightDir {
Top = 0,
Left = 1,
Right = 2,
Bottom = 3,
}
impl LightDir {
#[must_use]
pub fn all_clockwise_from_top() -> &'static [Self] {
&[
LightDir::Top,
LightDir::Right,
LightDir::Bottom,
LightDir::Left,
]
}
#[must_use]
pub fn clockwise(self) -> Self {
match self {
LightDir::Top => LightDir::Right,
LightDir::Left => LightDir::Top,
LightDir::Right => LightDir::Bottom,
LightDir::Bottom => LightDir::Left,
}
}
#[must_use]
pub fn counter_clockwise(self) -> Self {
match self {
LightDir::Top => LightDir::Left,
LightDir::Left => LightDir::Bottom,
LightDir::Right => LightDir::Top,
LightDir::Bottom => LightDir::Right,
}
}
#[must_use]
pub fn from_index(idx: usize) -> Option<Self> {
match idx {
0 => Some(LightDir::Top),
1 => Some(LightDir::Left),
2 => Some(LightDir::Right),
3 => Some(LightDir::Bottom),
_ => None,
}
}
#[must_use]
pub const fn light_on_all_buttons(self) -> Lights {
Lights::from_bitset(match self {
LightDir::Top => 0x11111_11111,
LightDir::Left => 0x22222_22222,
LightDir::Right => 0x44444_44444,
LightDir::Bottom => 0x88888_88888,
})
}
}
#[cfg(test)]
#[path = "./tests/lights_test.rs"]
mod test;