use std::io;

pub trait ReadBytes {
    fn read_bytes_be<T>(&mut self) -> io::Result<T>
    where
        T: BytesConvertible;

    fn read_bytes_le<T>(&mut self) -> io::Result<T>
    where
        T: BytesConvertible;
}

pub trait WriteBytes {
    fn write_bytes_be<T>(&mut self, value: T) -> io::Result<usize>
    where
        T: BytesConvertible;

    fn write_bytes_le<T>(&mut self, value: T) -> io::Result<usize>
    where
        T: BytesConvertible;
}

impl<R> ReadBytes for R
where
    R: io::Read,
{
    fn read_bytes_be<T>(&mut self) -> io::Result<T>
    where
        T: BytesConvertible,
    {
        T::read_bytes_be(self)
    }

    fn read_bytes_le<T>(&mut self) -> io::Result<T>
    where
        T: BytesConvertible,
    {
        T::read_bytes_le(self)
    }
}

impl<W> WriteBytes for W
where
    W: io::Write,
{
    fn write_bytes_be<T>(&mut self, value: T) -> io::Result<usize>
    where
        T: BytesConvertible,
    {
        value.write_bytes_be(self)
    }

    fn write_bytes_le<T>(&mut self, value: T) -> io::Result<usize>
    where
        T: BytesConvertible,
    {
        value.write_bytes_le(self)
    }
}

pub trait BytesConvertible {
    fn read_bytes_be<R>(buf: &mut R) -> io::Result<Self>
    where
        Self: Sized,
        R: io::Read;

    fn read_bytes_le<R>(buf: &mut R) -> io::Result<Self>
    where
        Self: Sized,
        R: io::Read;

    fn write_bytes_be<W>(self, buf: &mut W) -> io::Result<usize>
    where
        Self: Sized,
        W: io::Write;

    fn write_bytes_le<W>(self, buf: &mut W) -> io::Result<usize>
    where
        Self: Sized,
        W: io::Write;
}

macro_rules! impl_bytes_convertible {
    ($($ty:ty,)*) => {
        $(
            impl BytesConvertible for $ty {
                fn read_bytes_be<R>(buf: &mut R) -> io::Result<Self>
                where
                    R: io::Read,
                {
                    let mut tmp = [0; std::mem::size_of::<$ty>()];
                    buf.read_exact(&mut tmp)?;
                    Ok(<$ty>::from_be_bytes(tmp))
                }

                fn read_bytes_le<R>(buf: &mut R) -> io::Result<Self>
                where
                    R: io::Read,
                {
                    let mut tmp = [0; std::mem::size_of::<$ty>()];
                    buf.read_exact(&mut tmp)?;
                    Ok(<$ty>::from_le_bytes(tmp))
                }

                fn write_bytes_be<W>(self, buf: &mut W) -> io::Result<usize>
                where
                    W: io::Write,
                {
                    let tmp = self.to_be_bytes();
                    buf.write(&tmp)
                }

                fn write_bytes_le<W>(self, buf: &mut W) -> io::Result<usize>
                where
                    W: io::Write,
                {
                    let tmp = self.to_le_bytes();
                    buf.write(&tmp)
                }
            }
        )*
    };
}

impl_bytes_convertible! {
    u8,
    u16,
    u32,
    u64,
    i8,
    i16,
    i32,
    i64,
    f32,
    f64,
}
