use std::cmp::{PartialEq, PartialOrd};
use std::convert::From;
use std::ops::{Add, Div, Mul, Rem, Sub};

macro_rules! implement_trait {
    ($type:ident, $inner:ty, $int:ty, $trait:ident, $method:ident) => {
        impl $trait<$int> for $type {
            type Output = $type;
            fn $method(self, other: $int) -> Self::Output {
                Self(self.0.$method(other as $inner))
            }
        }
        impl $trait<$type> for $int {
            type Output = $type;
            fn $method(self, other: $type) -> Self::Output {
                $type((self as $inner).$method(other.0))
            }
        }
        impl $trait for $type {
            type Output = $type;
            fn $method(self, other: $type) -> Self::Output {
                Self(self.0.$method(other.0))
            }
        }
    };
}

macro_rules! implement_eq_trait {
    ($type:ident, $inner:ty, $int:ty) => {
        impl PartialEq<$int> for $type {
            fn eq(&self, other: &$int) -> bool {
                self.0 == *other as $inner
            }
        }
        impl PartialEq<$type> for $int {
            fn eq(&self, other: &$type) -> bool {
                (*self as $inner) == other.0
            }
        }
    };
}

macro_rules! implement_ord_trait {
    ($type:ident, $inner:ty, $int:ty) => {
        impl PartialOrd<$int> for $type {
            fn partial_cmp(&self, other: &$int) -> Option<std::cmp::Ordering> {
                self.0.partial_cmp(&(*other as $inner))
            }
        }
        impl PartialOrd<$type> for $int {
            fn partial_cmp(&self, other: &$type) -> Option<std::cmp::Ordering> {
                (*self as $inner).partial_cmp(&other.0)
            }
        }
    };
}

macro_rules! implement_from {
    ($type:ident, $inner:ty, $($int:ty),*) => {
        $(impl From<$int> for $type {
            fn from(value: $int) -> Self {
                Self(value as $inner)
            }
        })*
    };
}

/// A macros for implementing integer types.
/// When you defining a wrapper over ineger type you can
/// Use this macros and all basic operations like conversions,
/// arithmetic, will be implemented for you:
/// ```
/// struct MyIntWrapper(u64);
/// implement_int!(MyIntWrapper, u64);
/// ```
#[macro_export]
macro_rules! implement_int {
    ($type:ident, $inner:ty) => {
        implement_from!($type, $inner, u8, u16, u32, u64, i8, i16, i32, i64);

        impl From<$type> for $inner {
            fn from(value: $type) -> Self {
                value.0
            }
        }

        implement_trait!($type, $inner, $inner, Add, add);
        implement_trait!($type, $inner, $inner, Sub, sub);
        implement_trait!($type, $inner, $inner, Mul, mul);
        implement_trait!($type, $inner, $inner, Div, div);
        implement_trait!($type, $inner, $inner, Rem, rem);
        implement_eq_trait!($type, $inner, $inner);
        implement_ord_trait!($type, $inner, $inner);
    };
}

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

    #[derive(Debug, Clone, Copy)]
    struct TestIntU64(u64);
    #[derive(Debug, Clone, Copy)]
    struct TestIntU32(u32);

    implement_int!(TestIntU64, u64);
    implement_int!(TestIntU32, u32);

    #[test]
    fn test_basic_add() {
        let a = TestIntU64(3);
        let b = 4;
        let c = a + b;
        assert_eq!(c, 7);
    }

    #[test]
    #[should_panic]
    fn test_add_overflow() {
        let a = TestIntU32(u32::max_value());
        let b: u32 = 1;
        let _ = a + b;
    }

    #[test]
    fn test_conversion_from_different_type() {
        let _: TestIntU64 = 10u8.into();
    }

    #[test]
    fn all_side_add_works() {
        let a = TestIntU32(13);
        let b = 1u32;
        let _ = a + b;
        let _ = b + a;
        let a = TestIntU32(3);
        let b = TestIntU32(1);
        let _ = a + b;
    }

    #[test]
    fn all_side_cmp_works() {
        let a = TestIntU32(13);
        let b = 1u32;
        let _ = a < b;
        let _ = b < a;
        let _ = a > b;
        let _ = b > a;
        let _ = b == a;
        let _ = a == b;
        let _ = a >= b;
        let _ = b >= a;
        let _ = b != a;
        let _ = a != b;
    }
}
