use byteorder::{ByteOrder, BigEndian, LittleEndian};

pub trait ReadWriteEndian {
    type UnderlayingType: std::fmt::Display;

    fn read(&self) -> Self::UnderlayingType;
    fn write(&mut self, value: Self::UnderlayingType);
}

pub trait EndianFamilyTypes {
    type U16: ReadWriteEndian + std::default::Default;
    type U32: ReadWriteEndian + std::default::Default;
}

pub struct BigEndianFamily {}
pub struct LittleEndianFamily {}

impl EndianFamilyTypes for BigEndianFamily {
    type U16 = BigEndianU16;
    type U32 = BigEndianU32;
}

impl EndianFamilyTypes for LittleEndianFamily {
    type U16 = LittleEndianU16;
    type U32 = LittleEndianU32;
}

macro_rules! make_lsb_msb {
    ($type_val:ty) => {
        paste::item! {
            #[derive(Debug, Default, Copy, Clone)]
            #[repr(packed(1))]
            pub struct [< LittleEndian $type_val:upper >] {
                value: [u8; std::mem::size_of::<$type_val>()]
            }
            
            #[derive(Debug, Default, Copy, Clone)]
            #[repr(packed(1))]
            pub struct [< BigEndian $type_val:upper >] {
                value: [u8; std::mem::size_of::<$type_val>()]
            }

            #[derive(Debug, Default, Copy, Clone)]
            #[repr(packed(1))]
            pub struct [< LittleBigEndian $type_val:upper >] {
                pub lsb: [< LittleEndian $type_val:upper >],
                pub msb: [< BigEndian $type_val:upper >],
            }
            
            impl [< LittleEndian $type_val:upper >] {
                pub fn new(value: $type_val) -> [< LittleEndian $type_val:upper >] {
                    let mut new_value = [< LittleEndian $type_val:upper >]::default();

                    new_value.write(value);
                    new_value
                }
            }

            impl ReadWriteEndian for [< LittleEndian $type_val:upper >] {
                type UnderlayingType = $type_val;

                fn read(&self) -> Self::UnderlayingType {
                    LittleEndian::[< read_ $type_val >](&self.value)
                }
            
                fn write(&mut self, value: Self::UnderlayingType) {
                    LittleEndian::[< write_ $type_val >](&mut self.value, value);
                }
            }

            impl [< BigEndian $type_val:upper >] {
                pub fn new(value: $type_val) -> [< BigEndian $type_val:upper >] {
                    let mut new_value = [< BigEndian $type_val:upper >]::default();

                    new_value.write(value);
                    new_value
                }
            }

            impl ReadWriteEndian for [< BigEndian $type_val:upper >] {
                type UnderlayingType = $type_val;

                fn read(&self) -> Self::UnderlayingType {
                    BigEndian::[< read_ $type_val >](&self.value)
                }
            
                fn write(&mut self, value: Self::UnderlayingType) {
                    BigEndian::[< write_ $type_val >](&mut self.value, value);
                }
            }

            impl [< LittleBigEndian $type_val:upper >] { 
                pub fn new(value: $type_val) -> [< LittleBigEndian $type_val:upper >] {
                    let mut new_value = [< LittleBigEndian $type_val:upper >]::default();

                    new_value.write(value);
                    new_value
                }

                pub fn read_any(&self) -> $type_val {
                    self.lsb.read()
                }

                pub fn write(&mut self, value: $type_val) {
                    self.lsb.write(value);
                    self.msb.write(value);
                }
            } 
        }       
    };
}

make_lsb_msb!(u16);
make_lsb_msb!(i16);
make_lsb_msb!(u32);
