Documentation
#![no_std]

use core::{cmp::Ordering, convert::From, hash::{Hash, Hasher}, marker::PhantomData, mem};

use num_traits::{NumAssign, cast::AsPrimitive, int::PrimInt};

pub trait Endian: private::Sealed {
    const is_big: bool = !Self::is_lil;
    const is_lil: bool = !Self::is_big;

    fn read<N: 'static + PrimInt + NumAssign + ShlAssign<usize>>(bs: &[u8]) -> N where u8: AsPrimitive<N>;
    fn write<N: PrimInt + NumAssign + ShrAssign<usize>>(bs: &mut [u8], n: N) where N: AsPrimitive<u8>;

    #[inline]
    fn from<N: PrimInt + NumAssign + ShrAssign<usize>>(ns: &[N], bs: &mut [u8]) where N: AsPrimitive<u8> {
        assert_eq!(bs.len(), mem::size_of::<N>() * ns.len());
        for (np, bs) in Iterator::zip(ns.iter(), bs.chunks_mut(mem::size_of::<N>())) {
            Self::write(bs, *np);
        }
    }

    #[inline]
    fn to<N: 'static + PrimInt + NumAssign + ShlAssign<usize>>(bs: &[u8], ns: &mut [N]) where u8: AsPrimitive<N> {
        assert_eq!(bs.len(), mem::size_of::<N>() * ns.len());
        for (bs, np) in Iterator::zip(bs.chunks(mem::size_of::<N>()), ns.iter_mut()) {
            *np = Self::read(bs);
        }
    }

    #[inline] #[deprecated]
    fn read_u(bs: &[u8]) -> u64 { Self::read(bs) }
    #[inline] #[deprecated]
    fn read_i(bs: &[u8]) -> u64 { Self::read(bs) }
    #[inline] #[deprecated]
    fn write_u(bs: &mut [u8], n: u64) { Self::write(bs, n) }
    #[inline] #[deprecated]
    fn write_i(bs: &mut [u8], n: i64) { Self::write(bs, n) }
    #[inline] #[deprecated]
    fn to_u16s(bs: &[u8], ns: &mut [u16]) { Self::to(bs, ns) }
    #[inline] #[deprecated]
    fn to_i16s(bs: &[u8], ns: &mut [i16]) { Self::to(bs, ns) }
    #[inline] #[deprecated]
    fn from_u16s(ns: &[u16], bs: &mut [u8]) { Self::from(ns, bs) }
    #[inline] #[deprecated]
    fn from_i16s(ns: &[i16], bs: &mut [u8]) { Self::from(ns, bs) }
    #[inline] #[deprecated]
    fn to_u32s(bs: &[u8], ns: &mut [u32]) { Self::to(bs, ns) }
    #[inline] #[deprecated]
    fn to_i32s(bs: &[u8], ns: &mut [i32]) { Self::to(bs, ns) }
    #[inline] #[deprecated]
    fn from_u32s(ns: &[u32], bs: &mut [u8]) { Self::from(ns, bs) }
    #[inline] #[deprecated]
    fn from_i32s(ns: &[i32], bs: &mut [u8]) { Self::from(ns, bs) }
    #[inline] #[deprecated]
    fn to_u64s(bs: &[u8], ns: &mut [u64]) { Self::to(bs, ns) }
    #[inline] #[deprecated]
    fn to_i64s(bs: &[u8], ns: &mut [i64]) { Self::to(bs, ns) }
    #[inline] #[deprecated]
    fn from_u64s(ns: &[u64], bs: &mut [u8]) { Self::from(ns, bs) }
    #[inline] #[deprecated]
    fn from_i64s(ns: &[i64], bs: &mut [u8]) { Self::from(ns, bs) }
}

mod private {
    pub trait Sealed {}
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Big;

#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Lil;

impl private::Sealed for Big {}
impl private::Sealed for Lil {}

impl Endian for Big {
    const is_big: bool = true;

    #[inline]
    fn read<N: 'static + PrimInt + NumAssign + ShlAssign<usize>>(bs: &[u8]) -> N where u8: AsPrimitive<N> {
        assert!(mem::size_of::<N>() >= bs.len());
        let mut n = 0.as_();
        for &b in bs {
            n <<= 8;
            n += b.as_();
        }
        n
    }

    #[inline]
    fn write<N: PrimInt + NumAssign + ShrAssign<usize>>(bs: &mut [u8], mut n: N) where N: AsPrimitive<u8> {
        assert!(mem::size_of::<N>() >= bs.len());
        for bp in bs.iter_mut().rev() {
            *bp = n.as_();
            n >>= 8;
        }
    }
}

impl Endian for Lil {
    const is_lil: bool = true;

    #[inline]
    fn read<N: 'static + PrimInt + NumAssign + ShlAssign<usize>>(bs: &[u8]) -> N where u8: AsPrimitive<N> {
        assert!(mem::size_of::<N>() >= bs.len());
        let mut n = 0.as_();
        for &b in bs.iter().rev() {
            n <<= 8;
            n += b.as_();
        }
        n
    }

    #[inline]
    fn write<N: PrimInt + NumAssign + ShrAssign<usize>>(bs: &mut [u8], mut n: N) where N: AsPrimitive<u8> {
        assert!(mem::size_of::<N>() >= bs.len());
        for bp in bs {
            *bp = n.as_();
            n >>= 8;
        }
    }
}

#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct End<A, E: Endian>(A, PhantomData<E>);

impl<A: PartialEq, E: Endian> PartialEq for End<A, E> {
    #[inline]
    fn eq(&self, other: &Self) -> bool { self.0 == other.0 }
}
impl<A: Eq, E: Endian> Eq for End<A, E> {}
impl<A: Hash, E: Endian> Hash for End<A, E> {
    #[inline]
    fn hash<H: Hasher>(&self, h: &mut H) { self.0.hash(h) }
}

macro_rules! impl_fmt {
    ($c:path) => {
        impl<A: Copy, E: Copy + Endian> $c for End<A, E> where A: From<End<A, E>> + $c {
            #[inline]
            fn fmt(&self, fmt: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
                <A>::from(*self).fmt(fmt)
            }
        }
    };

    ($c:path, $($cs:path),*) => { impl_fmt!($c); $(impl_fmt!($cs);)* }
}

use core::fmt;
impl_fmt!(fmt::Debug, fmt::Display, fmt::Octal, fmt::LowerHex, fmt::UpperHex);

macro_rules! do_impls {
    ($t:ty) => {
        impl<E: Endian> From<$t> for End<$t, E> {
            #[inline]
            fn from(a: $t) -> Self {
                End(if cfg!(target_endian = "little") == E::is_lil { a } else { a.swap_bytes() },
                    PhantomData)
            }
        }

        impl<E: Endian> From<End<$t, E>> for $t {
            #[inline]
            fn from(End(a, _): End<$t, E>) -> Self {
                if cfg!(target_endian = "little") == E::is_lil { a } else { a.swap_bytes() }
            }
        }
    };

    ($t:ty, $($ts:ty),*) => { do_impls!($t); $(do_impls!($ts);)* }
}

do_impls!(usize, u8, u16, u32, u64, u128,
          isize, i8, i16, i32, i64, i128);

macro_rules! impl_op {
    ($op:ident, $f:ident) => {
        impl<A: $op<B>, B, E: Endian> $op<B> for End<A, E>
          where A: From<End<A, E>>, End<<A as $op<B>>::Output, E>: From<<A as $op<B>>::Output> {
            type Output = End<<A as $op<B>>::Output, E>;
            #[inline]
            fn $f(self, other: B) -> Self::Output { A::$f(self.into(), other).into() }
        }
    }
}

macro_rules! impl_op_assign {
    ($op:ident, $f:ident) => {
        impl<A: $op<B>, B, E: Endian> $op<B> for End<A, E> {
            #[inline]
            fn $f(&mut self, operand: B) { A::$f(&mut self.0, operand) }
        }
    }
}

macro_rules! impl_op_unary {
    ($op:ident, $f:ident) => {
        impl<A: $op, E: Endian> $op for End<A, E>
          where A: From<End<A, E>>, End<<A as $op>::Output, E>: From<<A as $op>::Output> {
            type Output = End<<A as $op>::Output, E>;
            #[inline]
            fn $f(self) -> Self::Output { A::$f(self.into()).into() }
        }
    }
}

use core::ops::*;

impl_op!(BitAnd, bitand);
impl_op!(BitOr,  bitor);
impl_op!(BitXor, bitxor);
impl_op!(Add, add);
impl_op!(Sub, sub);
impl_op!(Mul, mul);
impl_op!(Div, div);
impl_op!(Rem, rem);
impl_op!(Shl, shl);
impl_op!(Shr, shr);

impl_op_unary!(Neg, neg);
impl_op_unary!(Not, not);

impl_op_assign!(BitAndAssign, bitand_assign);
impl_op_assign!(BitOrAssign,  bitor_assign);
impl_op_assign!(BitXorAssign, bitxor_assign);

impl<A: Copy + PartialOrd, E: Copy + Endian> PartialOrd for End<A, E> where Self: Into<A> {
    #[inline]
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        A::partial_cmp(&(*self).into(), &(*other).into())
    }
}

impl<A: Copy + Ord, E: Copy + Endian> Ord for End<A, E> where Self: Into<A> {
    #[inline]
    fn cmp(&self, other: &Self) -> Ordering {
        A::cmp(&(*self).into(), &(*other).into())
    }
}