use std::option::Option;

pub trait Int : Sized {
    const MIN: Self;
    const MAX: Self;
    const NUM_BITS: usize;

    type Unsigned: Int;
    type Signed: Int;

    fn checked_add(self, rhs: Self) -> Option<Self>;
}

macro_rules! int_impl {
    (($unsigned:ident, $signed:ident), $bits:expr) => {
        int_impl!($unsigned, $unsigned, $signed, $bits, 0);
        int_impl!($signed, $unsigned, $signed, $bits, 1 << ($bits - 1));
    };
    ($ty:ident, $uty:ident, $ity:ident, $bits:expr, $min_expr:expr) => {
        impl Int for $ty {
            const MIN: $ty = $min_expr;
            const MAX: $ty = !($min_expr);
            const NUM_BITS: usize = $bits;

            type Unsigned = $uty;
            type Signed = $ity;

            fn checked_add(self, rhs: Self) -> Option<Self> {
                $ty::checked_add(self, rhs)
            }
        }
    };
}

int_impl!((u8, i8), 8);
int_impl!((u16, i16), 16);
int_impl!((u32, i32), 32);
int_impl!((u64, i64), 64);
int_impl!((u128, i128), 128);

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_int() {
        macro_rules! test_positive {
            ($ty:ident) => {
                assert_eq!(<$ty as Int>::MAX, $ty::max_value());
                assert_eq!(<$ty as Int>::MIN, $ty::min_value());
                assert_eq!(Int::checked_add(5 as $ty, 6 as $ty), (5 as $ty).checked_add(6 as $ty));
            };
        }

        test_positive!(u8);
        test_positive!(u16);
        test_positive!(u32);
        test_positive!(u64);
        test_positive!(u128);
        test_positive!(i8);
        test_positive!(i16);
        test_positive!(i32);
        test_positive!(i64);
        test_positive!(i128);
    }

    #[test]
    fn test_signed_types() {
        macro_rules! test_type {
            ($uty:ident, $ity:ident) => {
                assert_eq!(5 as <$uty as Int>::Unsigned, 5 as $uty);
                assert_eq!(5 as <$ity as Int>::Unsigned, 5 as $uty);
                assert_eq!(5 as <$uty as Int>::Signed, 5 as $ity);
                assert_eq!(5 as <$ity as Int>::Signed, 5 as $ity);
            };
        }
        test_type!(u8, i8);
        test_type!(u16, i16);
        test_type!(u32, i32);
        test_type!(u64, i64);
        test_type!(u128, i128);
    }
}
