pub(crate) enum BitStream<T> {
    ZeroSized(FixedValueIterator<u32>),
    NonZeroSized(NBitwiseIterator<T>),
}

impl<T> BitStream<T> {
    pub(crate) fn new(data: T, unit_size: usize, length: usize) -> Self {
        if unit_size == 0 {
            let iter = FixedValueIterator::new(0, length);
            Self::ZeroSized(iter)
        } else {
            let iter = NBitwiseIterator::new(data, unit_size);
            Self::NonZeroSized(iter)
        }
    }
}

impl<T> Iterator for BitStream<T>
where
    T: AsRef<[u8]>,
{
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        match self {
            Self::ZeroSized(z) => z.next(),
            Self::NonZeroSized(n) => n.next(),
        }
    }
}

pub(crate) struct FixedValueIterator<T> {
    val: T,
    length: usize,
}

impl<T> FixedValueIterator<T> {
    pub(crate) fn new(val: T, length: usize) -> Self {
        Self { val, length }
    }
}

impl<T> Iterator for FixedValueIterator<T>
where
    T: Copy,
{
    type Item = T;

    fn next(&mut self) -> Option<Self::Item> {
        if self.length > 0 {
            self.length -= 1;
            Some(self.val)
        } else {
            None
        }
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        (self.length, Some(self.length))
    }
}

#[derive(Clone)]
pub(crate) struct NBitwiseIterator<T> {
    pub(crate) data: T,
    pub(crate) size: usize,
    pub(crate) pos: usize,
    pub(crate) offset: usize,
}

impl<T> NBitwiseIterator<T> {
    pub(crate) fn new(data: T, size: usize) -> Self {
        Self {
            data,
            size,
            pos: 0,
            offset: 0,
        }
    }

    pub(crate) fn with_offset(self, offset_bits: usize) -> Self {
        Self {
            offset: offset_bits,
            ..self
        }
    }
}

impl<T> Iterator for NBitwiseIterator<T>
where
    T: AsRef<[u8]>,
{
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        let new_offset = self.offset + self.size;
        let (new_pos, new_offset) = (self.pos + new_offset / 8, new_offset % 8);
        let slice = self.data.as_ref();

        if self.pos >= slice.len()
            || new_pos > slice.len()
            || (new_pos == slice.len() && new_offset > 0)
        {
            return None;
        }

        let val = slice[self.pos] << self.offset >> self.offset;
        let mut val: u32 = u32::from(val);
        if new_pos == self.pos {
            val >>= 8 - new_offset;
        } else {
            let mut pos = self.pos + 1;
            while pos < new_pos {
                val = (val << 8) | u32::from(slice[pos]);
                pos += 1;
            }
            if new_offset > 0 {
                let shift = 8 - new_offset;
                let last_val = u32::from(slice[pos]) >> shift;
                val = (val << new_offset) | last_val;
            }
        }

        self.pos = new_pos;
        self.offset = new_offset;
        Some(val)
    }
}

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

    #[test]
    fn fixed_value_iterator_not_crashing_after_iteration_finished() {
        let length = 2;
        let mut iter = FixedValueIterator::new(1u8, length);
        for _ in 0..length + 1 {
            let _val = iter.next();
        }
    }

    #[test]
    fn nbitwise_iterator_u2() {
        let slice: [u8; 5] = [0, 255, 255, 0, 0];

        let mut iter = NBitwiseIterator::new(&slice, 2);
        assert_eq!(iter.next(), Some(0b00));
        assert_eq!(iter.next(), Some(0b00));
        assert_eq!(iter.next(), Some(0b00));
        assert_eq!(iter.next(), Some(0b00));
        assert_eq!(iter.next(), Some(0b11));
        assert_eq!(iter.next(), Some(0b11));
    }

    #[test]
    fn nbitwise_iterator_u5() {
        let slice: [u8; 5] = [0, 255, 255, 0, 0];

        let mut iter = NBitwiseIterator::new(&slice, 5);
        assert_eq!(iter.next(), Some(0b00000));
        assert_eq!(iter.next(), Some(0b00011));
        assert_eq!(iter.next(), Some(0b11111));
        assert_eq!(iter.next(), Some(0b11111));
        assert_eq!(iter.next(), Some(0b11110));
        assert_eq!(iter.next(), Some(0b00000));
        assert_eq!(iter.next(), Some(0b00000));
        assert_eq!(iter.next(), Some(0b00000));
        assert_eq!(iter.next(), None);
        assert_eq!(iter.next(), None);
    }

    #[test]
    fn nbitwise_iterator_u9() {
        let slice: [u8; 5] = [0, 255, 255, 0, 0];

        let mut iter = NBitwiseIterator::new(&slice, 9);
        assert_eq!(iter.next(), Some(0b000000001));
        assert_eq!(iter.next(), Some(0b111111111));
        assert_eq!(iter.next(), Some(0b111111000));
        assert_eq!(iter.next(), Some(0b000000000));
        assert_eq!(iter.next(), None);
    }

    #[test]
    fn nbitwise_iterator_u13() {
        let slice: [u8; 5] = [0, 255, 255, 0, 0];

        let mut iter = NBitwiseIterator::new(&slice, 13);
        assert_eq!(iter.next(), Some(0b0000000011111));
        assert_eq!(iter.next(), Some(0b1111111111100));
        assert_eq!(iter.next(), Some(0b0000000000000));
        assert_eq!(iter.next(), None);
    }

    #[test]
    fn nbitwise_iterator_with_offset() {
        let slice: [u8; 5] = [0, 255, 255, 0, 0];

        let mut iter = NBitwiseIterator::new(&slice, 2).with_offset(7);
        assert_eq!(iter.next(), Some(0b01));
    }

    #[test]
    fn nbitwise_iterator_empty() {
        let slice: [u8; 0] = [];

        let mut iter = NBitwiseIterator::new(&slice, 0);
        assert_eq!(iter.next(), None);
    }
}
