use std::mem::MaybeUninit;

pub trait NinBytes<'a> {
    type Bytes: AsRef<[u8]>;
    fn as_bytes(&'a self) -> Self::Bytes;
    fn from_bytes(b: Self::Bytes) -> Self;
}

macro_rules! impl_bytes {
    (be_bytes $T:ty => $R:ty) => {
        impl<'a> NinBytes<'a> for $T {
            type Bytes = $R;

            fn as_bytes(&'a self) -> Self::Bytes {
                self.to_be_bytes()
            }
            fn from_bytes(b: Self::Bytes) -> Self {
                Self::from_be_bytes(b)
            }
        }
    };
    (deref $r:lifetime $T:ty => $R:ty) => {
        impl<$r> NinBytes<$r> for $T {
            type Bytes = $R;

            fn as_bytes(&$r self) -> Self::Bytes {
                &*self
            }
            fn from_bytes(b: Self::Bytes) -> Self {
                b.into()
            }
        }
    };
}

impl_bytes!(be_bytes i8 => [u8; 1]);
impl_bytes!(be_bytes u8 => [u8; 1]);
impl_bytes!(be_bytes i16 => [u8; 2]);
impl_bytes!(be_bytes u16 => [u8; 2]);
impl_bytes!(be_bytes i32 => [u8; 4]);
impl_bytes!(be_bytes u32 => [u8; 4]);
impl_bytes!(be_bytes i64 => [u8; 8]);
impl_bytes!(be_bytes u64 => [u8; 8]);
impl_bytes!(be_bytes i128 => [u8; 16]);
impl_bytes!(be_bytes u128 => [u8; 16]);

impl_bytes!(be_bytes f32 => [u8; 4]);
impl_bytes!(be_bytes f64 => [u8; 8]);

impl_bytes!(deref 'a &'a [u8] => &'a [u8]);
impl_bytes!(deref 'a Vec<u8> => &'a [u8]);

impl<'a, const N: usize> NinBytes<'a> for [u8; N] {
    type Bytes = [u8; N];

    fn as_bytes(&'a self) -> Self::Bytes {
        *self
    }
    fn from_bytes(b: Self::Bytes) -> Self {
        b
    }
}

pub const fn concat_arrays<T: Copy, const A: usize, const B: usize, const C: usize>(a: [T; A], b: [T; B]) -> [T; C] {
    const {
        if A + B != C {
            panic!("concatenated array size mismatch");
        }
    }

    let mut result = [MaybeUninit::<T>::uninit(); C];

    let mut i = 0;
    while i < A {
        result[i] = MaybeUninit::new(a[i]);
        i += 1;
    }
    while i < C {
        result[i] = MaybeUninit::new(b[i - A]);
        i += 1;
    }

    unsafe { *(&raw const result as *const [T; C]) }
}
