pub trait ReadableDataType {
    const BYTES: usize;
    fn from_be_slice(data: &[u8]) -> Self;
    fn from_le_slice(data: &[u8]) -> Self;
}

macro_rules! primitive_readable {
    ( $primitive:ty, $length:expr ) => {
        impl ReadableDataType for $primitive {
            const BYTES: usize = $length;
            fn from_be_slice(data: &[u8]) -> Self {
                Self::from_be_bytes(data.try_into().unwrap())
            }
            fn from_le_slice(data: &[u8]) -> Self {
                Self::from_le_bytes(data.try_into().unwrap())
            }
        }
    };
}

primitive_readable!(u8, 1);
primitive_readable!(u16, 2);
primitive_readable!(u32, 4);
primitive_readable!(u64, 8);
primitive_readable!(u128, 16);
primitive_readable!(usize, 8);
primitive_readable!(i8, 1);
primitive_readable!(i16, 2);
primitive_readable!(i32, 4);
primitive_readable!(i64, 8);
primitive_readable!(i128, 16);
primitive_readable!(isize, 8);
primitive_readable!(f32, 4);
primitive_readable!(f64, 8);

trait DataReadAccess {
    fn get_data(&self) -> &[u8];
    fn get_position(&self) -> usize;
    fn set_position(&mut self, position: usize);
}

pub trait DataReader {
    fn reset(&mut self);

    fn read_be<T>(&mut self) -> Option<T>
    where
        T: ReadableDataType;
    fn read_le<T>(&mut self) -> Option<T>
    where
        T: ReadableDataType;
    fn read(&mut self, bytes: usize) -> Option<&[u8]>;
}

pub struct DataViewReader<'a> {
    data: &'a [u8],
    position: usize,
}

impl<'a> DataViewReader<'a> {
    pub fn new(data: &'a [u8]) -> Self {
        Self {
            data,
            position: 0usize,
        }
    }
}

impl<'a> DataReadAccess for DataViewReader<'a> {
    fn get_data(&self) -> &[u8] {
        self.data
    }

    fn get_position(&self) -> usize {
        self.position
    }

    fn set_position(&mut self, position: usize) {
        self.position = position;
    }
}

pub struct DataBufferReader<const LENGTH: usize> {
    data: [u8; LENGTH],
    position: usize,
}

impl<const LENGTH: usize> DataBufferReader<LENGTH> {
    pub fn new() -> Self {
        Self {
            data: [0u8; LENGTH],
            position: 0usize,
        }
    }

    pub fn get_buffer(&mut self) -> &mut [u8] {
        &mut self.data
    }

    pub fn get_write_buffer(&mut self, length: usize) -> &mut [u8] {
        self.reset();
        &mut self.data[..length]
    }
}

impl<const LENGTH: usize> DataReadAccess for DataBufferReader<LENGTH> {
    fn get_data(&self) -> &[u8] {
        &self.data
    }

    fn get_position(&self) -> usize {
        self.position
    }

    fn set_position(&mut self, position: usize) {
        self.position = position;
    }
}

impl<U: DataReadAccess> DataReader for U {
    fn reset(&mut self) {
        self.set_position(0);
    }

    fn read_be<T>(&mut self) -> Option<T>
    where
        T: ReadableDataType,
    {
        let position = self.get_position();
        if self.get_data().len() < position + T::BYTES {
            return None;
        }
        let result = T::from_be_slice(
            &self.get_data()[position..(position + T::BYTES)]
        );
        self.set_position(position + T::BYTES);
        Some(result)
    }

    fn read_le<T>(&mut self) -> Option<T>
    where
        T: ReadableDataType,
    {
        let position = self.get_position();
        if self.get_data().len() < position + T::BYTES {
            return None;
        }
        let result = T::from_le_slice(
            &self.get_data()[position..(position + T::BYTES)]
        );
        self.set_position(position + T::BYTES);
        Some(result)
    }

    fn read(&mut self, bytes: usize) -> Option<&[u8]> {
        let position = self.get_position();
        if self.get_data().len() < position + bytes {
            return None;
        }
        self.set_position(position + bytes);
        let result = &self.get_data()[position..(position + bytes)];
        Some(result)
    }
}
