#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(super) struct Bits64(u64);

impl Bits64 {
    const N: usize = 64;

    pub fn new(v: impl IntoIterator<Item = bool>) -> Self {
        let mut bits = Self::zeros();
        let mut n = 0;
        for bit in v {
            if bit {
                bits.set_bit_at(n);
            } else {
                bits.unset_bit_at(n);
            };
            n += 1;
        }
        assert_eq!(n, Self::N);
        bits
    }
    pub fn zeros() -> Self {
        Self(0)
    }
    #[allow(dead_code)]
    pub fn to_bitarray(self) -> [bool; Self::N] {
        let mut bits = [false; Self::N];
        for (n, bit) in bits.iter_mut().enumerate() {
            *bit = self.bit_at(n)
        }
        bits
    }
    pub fn hamming_distance(self, other: Bits64) -> u32 {
        self.xor(other).0.count_ones()
    }
    fn xor(self, other: Self) -> Self {
        Self(self.0 ^ other.0)
    }
    fn bit_at(self, n: usize) -> bool {
        assert!(n < Self::N);
        let bit = self.0 & (1 << n);
        bit != 0
    }
    fn set_bit_at(&mut self, n: usize) {
        assert!(n < Self::N);
        self.0 |= 1 << n;
    }
    fn unset_bit_at(&mut self, n: usize) {
        assert!(n < Self::N);
        self.0 &= !(1 << n);
    }
}

#[cfg(test)]
mod tests {
    use std::collections::HashMap;

    use super::*;

    #[test]
    fn test_bits64_ops() {
        let mut bits = Bits64::zeros();
        bits.set_bit_at(0);
        assert_eq!(bits, Bits64(1));
        bits.set_bit_at(1);
        assert_eq!(bits, Bits64(1 + 2));
        bits.unset_bit_at(0);
        assert_eq!(bits, Bits64(2));
        bits.unset_bit_at(1);
        assert_eq!(bits, Bits64::zeros());

        bits.set_bit_at(2);
        assert_eq!(bits, Bits64(4));
    }
    #[test]
    fn test_bitarray() {
        let mut v = [false; Bits64::N];
        v[3] = true;
        v[6] = true;
        let bits = Bits64::new(v);
        assert_eq!(bits.to_bitarray(), v);
    }
    #[test]
    fn test_bits64_new() {
        const N: usize = 64;

        let mut v = [false; N];
        v[0] = true;
        assert_eq!(Bits64::new(v), Bits64(1));
        v[1] = true;
        assert_eq!(Bits64::new(v), Bits64(1 + 2));
    }
    #[test]
    #[should_panic]
    fn test_bits64_new_fail() {
        const N: usize = 64;
        let it = (1..N).map(|x| x % 2 == 0);
        let _bits = Bits64::new(it);
    }

    #[test]
    fn test_hash() {
        let one = Bits64(1);
        let map = HashMap::from([(one, "1")]);
        assert_eq!(map[&one], "1");
    }
}
