Documentation
use core::fmt;
use core::net::{Ipv4Addr, Ipv6Addr};

use crate::{Ipv4Cidr, Ipv6Cidr};

#[derive(Copy, Clone, Eq, PartialEq)]
pub struct Nibble {
    pub byte: u8,
    pub bits: u8,
}

impl Nibble {
    #[inline]
    pub const fn nil() -> Self {
        Self { byte: 0, bits: 0 }
    }
}

impl fmt::Debug for Nibble {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "Nibble({:0width$b} / {})",
            self.byte,
            self.bits,
            width = self.bits as usize
        )
    }
}

impl From<(u8, u8)> for Nibble {
    fn from(value: (u8, u8)) -> Self {
        Self {
            byte: value.0,
            bits: value.1,
        }
    }
}

pub struct Nibbles<const N: usize> {
    bytes: [u8; N],
    cursor: u8,
    bits: u8,
}

impl<const N: usize> Nibbles<N> {
    #[inline]
    #[allow(dead_code)]
    const fn new(bytes: [u8; N], bits: u8) -> Self {
        debug_assert!(bits <= N as u8 * 8, "bits is too long");
        Self {
            bytes,
            cursor: 0,
            bits,
        }
    }

    #[inline]
    #[allow(dead_code)]
    pub const fn bits(&self) -> u8 {
        self.bits
    }

    #[inline]
    #[allow(dead_code)]
    pub const fn to_bytes(&self) -> [u8; N] {
        self.bytes
    }
}

impl<const N: usize> Iterator for Nibbles<N> {
    type Item = Nibble;

    fn next(&mut self) -> Option<Self::Item> {
        if self.cursor >= self.bits {
            return None;
        }

        let bits = 4.min(self.bits - self.cursor);
        let byte = {
            let i = (self.cursor / 8) as usize;
            let b = self.bytes[i];
            let b = if self.cursor.is_multiple_of(8) {
                b >> 4
            } else {
                b & 0x0f
            };
            b >> (4 - bits) << (4 - bits)
        };
        self.cursor += bits;

        Some(Nibble { bits, byte })
    }
}

impl From<Ipv4Addr> for Nibbles<4> {
    fn from(addr: Ipv4Addr) -> Self {
        let bytes = addr.octets();
        Self {
            bytes,
            cursor: 0,
            bits: 32,
        }
    }
}

impl From<Ipv4Cidr> for Nibbles<4> {
    fn from(cidr: Ipv4Cidr) -> Self {
        let bytes = cidr.octets();
        Self {
            bytes,
            cursor: 0,
            bits: cidr.bits(),
        }
    }
}

impl From<Ipv6Addr> for Nibbles<16> {
    fn from(addr: Ipv6Addr) -> Self {
        let bytes = addr.octets();
        Self {
            bytes,
            cursor: 0,
            bits: 128,
        }
    }
}

impl From<Ipv6Cidr> for Nibbles<16> {
    fn from(cidr: Ipv6Cidr) -> Self {
        let bytes = cidr.octets();
        Self {
            bytes,
            cursor: 0,
            bits: cidr.bits(),
        }
    }
}

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

    #[test]
    #[allow(clippy::too_many_lines)]
    fn test_nibbles_iteration() {
        let tests = [
            (([0b1111_1111, 0, 0, 0], 0), vec![]),
            (([0b1111_1111, 0, 0, 0], 1), vec![(0b1000, 1)]),
            (([0b1111_1111, 0, 0, 0], 2), vec![(0b1100, 2)]),
            (([0b1100_1010, 0, 0, 0], 8), vec![(0b1100, 4), (0b1010, 4)]),
            (([0b1100_1010, 0, 0, 0], 6), vec![(0b1100, 4), (0b1000, 2)]),
            (([0b1100_1010, 0, 0, 0], 5), vec![(0b1100, 4), (0b1000, 1)]),
            (([0b1100_1010, 0, 0, 0], 4), vec![(0b1100, 4)]),
            (([0b1111_1010, 0, 0, 0], 3), vec![(0b1110, 3)]),
            (
                ([0b1111_1010, 0b1111_1111, 0, 0], 11),
                vec![(0b1111, 4), (0b1010, 4), (0b1110, 3)],
            ),
            (
                ([0b1010_0101, 0b1110_0111, 0, 0], 11),
                vec![(0b1010, 4), (0b0101, 4), (0b1110, 3)],
            ),
            (
                ([0b1010_0101, 0b1110_0111, 0, 0], 15),
                vec![(0b1010, 4), (0b0101, 4), (0b1110, 4), (0b0110, 3)],
            ),
        ];

        for (input, expected) in tests {
            let (bytes, bits) = input;
            let nibbles = Nibbles::new(bytes, bits);

            let actual: Vec<Nibble> = nibbles.collect();
            let expected: Vec<Nibble> = expected
                .into_iter()
                .map(|(byte, bits)| Nibble { byte, bits })
                .collect();
            assert_eq!(
                actual, expected,
                "input: {input:?} = {actual:?}, expected: {expected:?}"
            );
        }
    }

    #[test]
    fn test_ipv4_cidr_to_nibbles() {
        let tests = [
            ("0.0.0.0/0", vec![]),
            (
                "192.168.0.1/32",
                vec![
                    (0b1100, 4),
                    (0b0000, 4),
                    (0b1010, 4),
                    (0b1000, 4),
                    (0b0000, 4),
                    (0b0000, 4),
                    (0b0000, 4),
                    (0b0001, 4),
                ],
            ),
            (
                "192.168.0.0/16",
                vec![(0b1100, 4), (0b0000, 4), (0b1010, 4), (0b1000, 4)],
            ),
            (
                "192.168.223.0/24",
                vec![
                    (0b1100, 4),
                    (0b0000, 4),
                    (0b1010, 4),
                    (0b1000, 4),
                    (0b1101, 4),
                    (0b1111, 4),
                ],
            ),
            (
                "192.168.223.0/25",
                vec![
                    (0b1100, 4),
                    (0b0000, 4),
                    (0b1010, 4),
                    (0b1000, 4),
                    (0b1101, 4),
                    (0b1111, 4),
                    (0b0000, 1),
                ],
            ),
            (
                "192.168.223.0/18",
                vec![
                    (0b1100, 4),
                    (0b0000, 4),
                    (0b1010, 4),
                    (0b1000, 4),
                    (0b1100, 2),
                ],
            ),
            (
                "10.0.0.1/32",
                vec![
                    (0b0000, 4),
                    (0b1010, 4),
                    (0b0000, 4),
                    (0b0000, 4),
                    (0b0000, 4),
                    (0b0000, 4),
                    (0b0000, 4),
                    (0b0001, 4),
                ],
            ),
        ];

        for (s, expected) in tests {
            let cidr = s.parse::<Ipv4Cidr>().unwrap();
            let actual = Nibbles::from(cidr);
            let actual: Vec<Nibble> = actual.collect();
            let expected: Vec<Nibble> = expected
                .into_iter()
                .map(|(byte, bits)| Nibble { byte, bits })
                .collect();

            assert_eq!(
                actual, expected,
                "input: {s} = {actual:?}, expected: {expected:?}"
            );
        }
    }

    #[test]
    fn test_ipv6_cidr_to_nibbles() {
        let tests = [
            ("::/0", vec![]),
            (
                "2001:db8::/32",
                vec![
                    (0b0010, 4),
                    (0b0000, 4),
                    (0b0000, 4),
                    (0b0001, 4),
                    (0b0000, 4),
                    (0b1101, 4),
                    (0b1011, 4),
                    (0b1000, 4),
                ],
            ),
        ];
        for (s, expected) in tests {
            let cidr = s.parse::<Ipv6Cidr>().unwrap();
            let actual = Nibbles::from(cidr);
            let actual: Vec<Nibble> = actual.collect();
            let expected: Vec<Nibble> = expected
                .into_iter()
                .map(|(byte, bits)| Nibble { byte, bits })
                .collect();

            assert_eq!(
                actual, expected,
                "input: {s} = {actual:?}, expected: {expected:?}"
            );
        }
    }
}