use num::{One, PrimInt, Unsigned, Zero};
use std::ops::{BitAnd, ShrAssign};

pub(crate) const USIZE_BIT_DEPTH: usize = std::mem::size_of::<usize>() * 8;

pub(crate) fn bits_to_u8(bits: [bool; 8]) -> u8 {
    let mut byte = 0u8;
    for i in 0..8 {
        byte <<= 1;
        if bits[i] {
            byte |= 1;
        }
    }
    byte
}

pub(crate) fn bits_to_usize(bits: [bool; USIZE_BIT_DEPTH]) -> usize {
    let mut num = 0usize;
    for i in 0..USIZE_BIT_DEPTH {
        num <<= 1;
        if bits[i] {
            num |= 1;
        }
    }
    num
}

pub(crate) fn unsigned_int_to_bits<T>(mut num: T) -> Vec<bool>
where
    T: Unsigned + PrimInt + PartialEq + BitAnd + ShrAssign + Zero + One,
{
    let bit_depth = std::mem::size_of::<T>() * 8;
    let mut v: Vec<bool> = Vec::with_capacity(bit_depth);
    while num != T::zero() {
        let bit = (num & T::one()) == T::one();
        v.insert(0, bit);
        num >>= T::one();
    }

    while v.len() != bit_depth {
        v.insert(0, false);
    }

    v
}

pub(crate) fn get_bit_from_u8(byte: u8, bit_index: u8) -> bool {
    assert!(bit_index < 8);
    ((byte >> 7 - bit_index) & 1) != 0
}

pub(crate) fn u8_to_gray_code(byte: u8) -> u8 {
    byte ^ (byte >> 1)
}

pub(crate) fn u8_to_binary_code(byte: u8) -> u8 {
    let mut mask = byte;
    let mut binary = byte;
    while mask != 0 {
        mask >>= 1;
        binary ^= mask;
    }
    binary
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_get_bit() {
        let b1 = 0b01000000 as u8;
        assert_eq!(get_bit_from_u8(b1, 1), true)
    }

    #[test]
    fn test_to_gray_code() {
        assert_eq!(0b0010u8, u8_to_gray_code(0b0011u8)); // 3u8
        assert_eq!(0b1100u8, u8_to_gray_code(0b1000u8)); // 8u8
        assert_eq!(0b1011u8, u8_to_gray_code(0b1101u8)); // 13u8
        assert_eq!(0b1000u8, u8_to_gray_code(0b1111u8)); // 15u8
    }

    #[test]
    fn test_to_binary_code() {
        assert_eq!(u8_to_binary_code(0b0010u8), 0b0011u8); // 3u8
        assert_eq!(u8_to_binary_code(0b1100u8), 0b1000u8); // 8u8
        assert_eq!(u8_to_binary_code(0b1011u8), 0b1101u8); // 13u8
        assert_eq!(u8_to_binary_code(0b1000u8), 0b1111u8); // 15u8
    }

    #[test]
    fn test_generic_unsigned_int_to_bits() {
        let num: u8 = 235;
        let bits: [bool; 8] = unsigned_int_to_bits(num).try_into().unwrap();
        let expected = [true, true, true, false, true, false, true, true];
        assert_eq!(bits, expected);

        let num: u16 = 5274;
        let bits: [bool; 16] = unsigned_int_to_bits(num).try_into().unwrap();
        let expected = [
            false, false, false, true, false, true, false, false, true, false, false, true, true,
            false, true, false,
        ];
        assert_eq!(bits, expected);

        let num: u32 = 1583765927;
        let bits: [bool; 32] = unsigned_int_to_bits(num).try_into().unwrap();
        let expected = [
            false, true, false, true, true, true, true, false, false, true, true, false, false,
            true, true, false, false, true, false, true, true, false, false, true, true, false,
            true, false, false, true, true, true,
        ];
        assert_eq!(bits, expected);

        let num: u64 = 837659277593756383;
        let bits: [bool; 64] = unsigned_int_to_bits(num).try_into().unwrap();
        let expected = [
            false, false, false, false, true, false, true, true, true, false, false, true, true,
            true, true, true, true, true, true, true, false, true, true, false, true, false, true,
            false, true, true, false, false, true, true, false, false, false, false, true, true,
            true, true, false, false, true, false, false, true, false, true, false, false, false,
            false, true, false, true, true, false, true, true, true, true, true,
        ];
        assert_eq!(bits, expected);
    }
}
