use std::mem;

/// A type that represents an object that consumes a memory slice on demand.
/// The consumer lives as long as the slice is alive.
pub struct ByteConsumer<'b> {
        /// The memory slice that get's consumed.
        buffer: &'b [u8],
}

impl<'b> ByteConsumer<'b> {
        /// Creates a new ByteConsumer from a stream of bytes.
        pub fn new(stream: &'b [u8]) -> ByteConsumer {
            ByteConsumer { buffer: stream }
    }
    
    /// Consumes `nr_bytes` from the original slice and returns an `Option` that holds a reference
    /// to a memory slice which is `nr_bytes` long. If `nr_bytes` is greater than the remaining
    /// bytes of the consumer's slice then `None` is returned.
    /// 
    /// *Note*: The memory slice returned is not copied, it's a reference to the initial slice
    /// 
    /// # Arguments
    /// 
    /// * `nr_bytes` - A number representing the number of bytes to consume
    /// 
    /// 
    /// # Examples
    /// 
    /// ```
    /// use byte_consumer::ByteConsumer;
    /// let mut consumer = ByteConsumer::new(&[10, 20, 30, 40]);
    /// let first_two = consumer.consume(2);
    /// let second_two = consumer.consume(2);
    /// 
    /// assert!(first_two.is_some());
    /// assert_eq!(first_two.unwrap(), [10, 20]);
    /// 
    /// assert!(second_two.is_some());
    /// assert_eq!(second_two.unwrap(), [30, 40]);
    /// 
    /// assert_eq!(consumer.consume(1), None);
    /// ```
    /// 
    #[inline]
        pub fn consume(&mut self, nr_bytes: usize) -> Option<&'b [u8]> {
            if nr_bytes > self.buffer.len() || nr_bytes == 0 {
                None
        } else {
            let (res, remaining) = self.buffer.split_at(nr_bytes);
                self.buffer = remaining;
                Some(res)
        }
    }
    
    /// Consumes `nr_bytes` from the original slice and returns a reference to a memory slice which is `nr_bytes` long.
    /// 
    /// *Note*: The memory slice returned is not copied, it's a reference to the initial slice
    /// 
    /// # Arguments
    /// 
    /// * `nr_bytes` - A number representing the number of bytes to consume
    /// 
    /// # Panics
    /// 
    /// If `nr_bytes` is greater than the remaining bytes in the consumer's slice then
    /// then it panics with a custom message.
    /// 
    /// # Examples
    /// 
    /// ```
    /// use byte_consumer::ByteConsumer;
    /// let mut consumer = ByteConsumer::new(&[10, 20, 30, 40]);
    /// let first_two = consumer.consume_unchecked(2);
    /// let second_two = consumer.consume_unchecked(2);
    /// 
    /// assert_eq!(first_two, [10, 20]);
    /// assert_eq!(second_two, [30, 40]);
    /// 
    /// // let next = consumer.consume_unchecked(1) <-- This should panic.
    /// ``` 
    #[inline]
        pub fn consume_unchecked(&mut self, nr_bytes: usize) -> &'b [u8] {
            self.consume(nr_bytes).expect(format!(
            "[ByteConsumer]: The number of bytes requested ({}) is greater that the number of bytes available ({}).",
            nr_bytes,
            self.buffer.len()
            ).as_str())
    }
    
    
    /// Consumes as many bytes as the size of type `T` and converts them to an object of type `T`.
    /// If the size of type `T` in number of bytes is greater than the remaining bytes in the consumer's buffer
    /// then it returns None.
    /// 
    /// *Note*: The type `T` must implement the `Copy` trait as we are moving the bytes out of the slice
    /// 
    /// # Examples
    /// 
    /// ```
    /// use byte_consumer::ByteConsumer;
    /// 
    /// let mut consumer = ByteConsumer::new(&[0x1, 0x2, 0x1, 0x1]);
    /// let first_byte_as_u8: Option<u8> = consumer.consume_as();
    /// let second_byte_as_u8 = consumer.consume_as::<u8>();
    /// let last_two_as_u16 = consumer.consume_as::<u16>();
    /// 
    /// assert!(first_byte_as_u8.is_some());
    /// assert_eq!(first_byte_as_u8.unwrap(), 1);
    /// 
    /// assert!(second_byte_as_u8.is_some());
    /// assert_eq!(second_byte_as_u8.unwrap(), 2);
    /// 
    /// assert!(last_two_as_u16.is_some());
    /// assert_eq!(last_two_as_u16.unwrap(), 257);
    /// 
    /// assert_eq!(consumer.consume_as::<u32>(), None);
    /// ```
    #[inline]
        pub fn consume_as<T: Copy>(&mut self) -> Option<T> {
            let bytes = self.consume(mem::size_of::<T>());
            match bytes {
                Some(bytes) => unsafe { Some(*(bytes.as_ptr() as *const T)) },
                None => None,
        }
    }
    
    /// Consumes as many bytes as the size of type `T` and converts them to an object of type `T`.
    /// 
    /// *Note*: The type `T` must implement the `Copy` trait as we are moving the bytes out of the slice
    /// 
    /// # Panics
    /// 
    /// If size of type `T` in bytes is greater than the remaining bytes in the consumer's buffer
    /// then it panics with a custom message.
    /// 
    /// # Examples
    /// 
    /// ```
    /// use byte_consumer::ByteConsumer;
    /// 
    /// let mut consumer = ByteConsumer::new(&[0x1, 0x2, 0x1, 0x1]);
    /// let first_byte_as_u8: u8 = consumer.consume_as_unchecked();
    /// let second_byte_as_u8 = consumer.consume_as_unchecked::<u8>();
    /// let last_two_as_u16 = consumer.consume_as_unchecked::<u16>();
    /// 
    /// assert_eq!(first_byte_as_u8, 1);
    /// assert_eq!(second_byte_as_u8, 2);
    /// assert_eq!(last_two_as_u16, 257);
    /// 
    /// // let next = consumer.consume_as_unchecked::<u32>() <-- This should panic.
    /// ```
    #[inline]
        pub fn consume_as_unchecked<T: Copy>(&mut self) -> T {
            self.consume_as::<T>().expect(format!(
            "[ByteConsumer]: The number of bytes ({}) in the type ({}) is greater that the number of bytes available ({}).",
            mem::size_of::<T>(),
            std::any::type_name::<T>(),
            self.buffer.len()
            ).as_str())
    }
    
    /// Returns whether there are remaining bytes to consume or not.
    /// # Examples
    ///
    /// ```
    /// // Calling has_remaining on a consumer with an empty slice should return false.
    /// use byte_consumer::ByteConsumer;
    /// let mut consumer = ByteConsumer::new(&[]);
    /// assert!(!consumer.has_remaining());
    /// ```
    ///
    /// ```
    /// // Calling has_remaining on a non consumed or partially consumed slice should return true.
    /// // After consuming everything, we should get false
    /// use byte_consumer::ByteConsumer;
    /// let mut consumer = ByteConsumer::new(&[1, 2, 3, 4]);
    /// assert!(consumer.has_remaining());
    /// consumer.consume(3);
    /// assert!(consumer.has_remaining());
    /// consumer.consume(1);
    /// assert!(!consumer.has_remaining());
    /// ```
    #[inline]
        pub fn has_remaining(&mut self) -> bool {
            self.buffer.len() != 0
    }
}

#[cfg(test)]
mod tests {
        use super::*;
    
        #[test]
        fn test_consume_bytes() {
            let mut consumer = ByteConsumer::new(&[10, 20, 30, 40, 50]);
            let first_two = consumer.consume(2);
            let third = consumer.consume(1);
            let last_two = consumer.consume(2);
        
            assert!(first_two.is_some());
            assert_eq!(first_two.unwrap(), [10, 20]);
        
            assert!(third.is_some());
            assert_eq!(third.unwrap(), [30]);
        
            assert!(last_two.is_some());
            assert_eq!(last_two.unwrap(), [40, 50]);
        
            assert!(consumer.consume(10).is_none());
            assert!(consumer.consume(100).is_none());
    }
    
    #[test]
        fn test_consume_bytes_unchecked() {
            let mut consumer = ByteConsumer::new(&[10, 20, 30, 40, 50]);
            assert_eq!(consumer.consume_unchecked(2), [10, 20]);
            assert_eq!(consumer.consume_unchecked(3), [30, 40, 50]);
    }
    
    #[test]
        fn test_consume_bytes_iteratively() {
            let view = [10, 20, 30, 40, 50];
            let mut index = 0;
            let mut consumer = ByteConsumer::new(&view);
            while let Some(bytes) = consumer.consume(1) {
                assert!(!bytes.is_empty());
                assert_eq!(bytes.len(), 1);
                assert_eq!(view[index], bytes[0]);
                index += 1;
        }
        assert_eq!(index, view.len());
    }
    
    #[test]
        fn test_consume_bytes_iteratively_unchecked() {
            let view = [10, 20, 30, 40, 50];
            let mut index = 0;
            let mut consumer = ByteConsumer::new(&view);
            while consumer.has_remaining() {
                assert_eq!(view[index], consumer.consume_unchecked(1)[0]);
                index += 1;
        }
        assert_eq!(index, view.len());
    }
    
    #[test]
        #[should_panic]
        fn test_panic_on_uncecked_consume() {
            let mut consumer = ByteConsumer::new(&[10, 20, 30, 40]);
            assert_eq!(consumer.consume_unchecked(4), [10, 20, 30, 40]); // This should not panic
            consumer.consume_unchecked(1); // This should panic
    }
    
    #[test]
        #[should_panic]
        fn test_panic_on_unchecked_consume_iteratively() {
            let view = [10, 20, 30, 40];
            let mut consumer = ByteConsumer::new(&view);
            for _ in 0..view.len() + 1 {
                consumer.consume_unchecked(1);
        }
    }
    
    #[test]
        fn test_consume_on_empty_slice() {
            let mut consumer = ByteConsumer::new(&[]);
            assert!(consumer.consume(1).is_none());
            assert!(consumer.consume_as::<u128>().is_none());
    }
    
    #[test]
        fn test_has_remaining_on_empty_slice() {
            let mut consumer = ByteConsumer::new(&[]);
            assert!(!consumer.has_remaining());
    }
    
    #[test]
        fn test_has_remaining_on_unconsumed_slice() {
            let mut consumer = ByteConsumer::new(&[1, 2, 3, 4]);
            assert!(consumer.has_remaining());
    }
    
    #[test]
        fn test_has_remaining_on_partially_consumed_slice() {
            let mut consumer = ByteConsumer::new(&[1, 2, 3, 4]);
            consumer.consume(3);
            assert!(consumer.has_remaining());
    }
    
    #[test]
        fn test_has_remaining_on_totaly_consumed_slice() {
            let mut consumer = ByteConsumer::new(&[1, 2, 3, 4]);
            consumer.consume(4);
            assert!(!consumer.has_remaining());
    }
}
