use crate::{decimal::CalculationResult, ops, Decimal};
use core::ops::{Add, Div, Mul, Rem, Sub};
use num_traits::{CheckedAdd, CheckedDiv, CheckedMul, CheckedRem, CheckedSub, Inv};
#[rustfmt::skip]
macro_rules! impl_checked {
    ($long:literal, $short:literal, $fun:ident, $impl:ident) => {
        #[doc = concat!(
            "Checked ",
            $long,
            ". Computes `self ",
            $short,
            " other`, returning `None` if overflow occurred."
        )]
        #[inline(always)]
        #[must_use]
        pub fn $fun(self, other: Decimal) -> Option<Decimal> {
            match ops::$impl(&self, &other) {
                CalculationResult::Ok(result) => Some(result),
                _ => None,
            }
        }
    };
}
#[rustfmt::skip]
macro_rules! impl_saturating {
    ($long:literal, $short:literal, $fun:ident, $impl:ident, $cmp:ident) => {
        #[doc = concat!(
            "Saturating ",
            $long,
            ". Computes `self ",
            $short,
            " other`, saturating at the relevant upper or lower boundary.",
        )]
        #[inline(always)]
        #[must_use]
        pub fn $fun(self, other: Decimal) -> Decimal {
            if let Some(elem) = self.$impl(other) {
                elem
            } else {
                $cmp(&self, &other)
            }
        }
    };
}
macro_rules! impl_checked_and_saturating {
    (
        $op_long:literal,
        $op_short:literal,
        $checked_fun:ident,
        $checked_impl:ident,
        $saturating_fun:ident,
        $saturating_cmp:ident
    ) => {
        impl_checked!($op_long, $op_short, $checked_fun, $checked_impl);
        impl_saturating!(
            $op_long,
            $op_short,
            $saturating_fun,
            $checked_fun,
            $saturating_cmp
        );
    };
}
impl Decimal {
    impl_checked_and_saturating!(
        "addition",
        "+",
        checked_add,
        add_impl,
        saturating_add,
        if_a_is_positive_then_max
    );
    impl_checked_and_saturating!(
        "multiplication",
        "*",
        checked_mul,
        mul_impl,
        saturating_mul,
        if_xnor_then_max
    );
    impl_checked_and_saturating!(
        "subtraction",
        "-",
        checked_sub,
        sub_impl,
        saturating_sub,
        if_a_is_positive_then_max
    );
    impl_checked!("division", "/", checked_div, div_impl);
    impl_checked!("remainder", "%", checked_rem, rem_impl);
}
macro_rules! forward_all_binop {
    (impl $imp:ident for $res:ty, $method:ident) => {
        forward_val_val_binop!(impl $imp for $res, $method);
        forward_ref_val_binop!(impl $imp for $res, $method);
        forward_val_ref_binop!(impl $imp for $res, $method);
    };
}
macro_rules! forward_ref_val_binop {
    (impl $imp:ident for $res:ty, $method:ident) => {
        impl<'a> $imp<$res> for &'a $res {
            type Output = $res;
            #[inline]
            fn $method(self, other: $res) -> $res {
                self.$method(&other)
            }
        }
    };
}
macro_rules! forward_val_ref_binop {
    (impl $imp:ident for $res:ty, $method:ident) => {
        impl<'a> $imp<&'a $res> for $res {
            type Output = $res;
            #[inline]
            fn $method(self, other: &$res) -> $res {
                (&self).$method(other)
            }
        }
    };
}
macro_rules! forward_val_val_binop {
    (impl $imp:ident for $res:ty, $method:ident) => {
        impl $imp<$res> for $res {
            type Output = $res;
            #[inline]
            fn $method(self, other: $res) -> $res {
                (&self).$method(&other)
            }
        }
    };
}
forward_all_binop!(impl Add for Decimal, add);
impl<'a, 'b> Add<&'b Decimal> for &'a Decimal {
    type Output = Decimal;
    #[inline(always)]
    fn add(self, other: &Decimal) -> Decimal {
        match ops::add_impl(self, other) {
            CalculationResult::Ok(sum) => sum,
            _ => panic!("Addition overflowed"),
        }
    }
}
impl CheckedAdd for Decimal {
    #[inline]
    fn checked_add(&self, v: &Decimal) -> Option<Decimal> {
        Decimal::checked_add(*self, *v)
    }
}
impl CheckedSub for Decimal {
    #[inline]
    fn checked_sub(&self, v: &Decimal) -> Option<Decimal> {
        Decimal::checked_sub(*self, *v)
    }
}
impl CheckedMul for Decimal {
    #[inline]
    fn checked_mul(&self, v: &Decimal) -> Option<Decimal> {
        Decimal::checked_mul(*self, *v)
    }
}
impl CheckedDiv for Decimal {
    #[inline]
    fn checked_div(&self, v: &Decimal) -> Option<Decimal> {
        Decimal::checked_div(*self, *v)
    }
}
impl CheckedRem for Decimal {
    #[inline]
    fn checked_rem(&self, v: &Decimal) -> Option<Decimal> {
        Decimal::checked_rem(*self, *v)
    }
}
impl Inv for Decimal {
    type Output = Self;
    #[inline]
    fn inv(self) -> Self {
        Decimal::ONE / self
    }
}
forward_all_binop!(impl Div for Decimal, div);
impl<'a, 'b> Div<&'b Decimal> for &'a Decimal {
    type Output = Decimal;
    #[inline]
    fn div(self, other: &Decimal) -> Decimal {
        match ops::div_impl(self, other) {
            CalculationResult::Ok(quot) => quot,
            CalculationResult::Overflow => panic!("Division overflowed"),
            CalculationResult::DivByZero => panic!("Division by zero"),
        }
    }
}
forward_all_binop!(impl Mul for Decimal, mul);
impl<'a, 'b> Mul<&'b Decimal> for &'a Decimal {
    type Output = Decimal;
    #[inline]
    fn mul(self, other: &Decimal) -> Decimal {
        match ops::mul_impl(self, other) {
            CalculationResult::Ok(prod) => prod,
            _ => panic!("Multiplication overflowed"),
        }
    }
}
forward_all_binop!(impl Rem for Decimal, rem);
impl<'a, 'b> Rem<&'b Decimal> for &'a Decimal {
    type Output = Decimal;
    #[inline]
    fn rem(self, other: &Decimal) -> Decimal {
        match ops::rem_impl(self, other) {
            CalculationResult::Ok(rem) => rem,
            CalculationResult::Overflow => panic!("Division overflowed"),
            CalculationResult::DivByZero => panic!("Division by zero"),
        }
    }
}
forward_all_binop!(impl Sub for Decimal, sub);
impl<'a, 'b> Sub<&'b Decimal> for &'a Decimal {
    type Output = Decimal;
    #[inline(always)]
    fn sub(self, other: &Decimal) -> Decimal {
        match ops::sub_impl(self, other) {
            CalculationResult::Ok(sum) => sum,
            _ => panic!("Subtraction overflowed"),
        }
    }
}
#[inline(always)]
const fn if_a_is_positive_then_max(a: &Decimal, _b: &Decimal) -> Decimal {
    if a.is_sign_positive() {
        Decimal::MAX
    } else {
        Decimal::MIN
    }
}
#[inline(always)]
const fn if_xnor_then_max(a: &Decimal, b: &Decimal) -> Decimal {
    match (a.is_sign_positive(), b.is_sign_positive()) {
        (true, true) => Decimal::MAX,
        (true, false) => Decimal::MIN,
        (false, true) => Decimal::MIN,
        (false, false) => Decimal::MAX,
    }
}
#[cfg(test)]
mod tests {
    use crate::Decimal;
    #[test]
    fn checked_methods_have_correct_output() {
        assert_eq!(Decimal::MAX.checked_add(Decimal::MAX), None);
        assert_eq!(Decimal::MAX.checked_add(Decimal::MIN), Some(Decimal::ZERO));
        assert_eq!(Decimal::MAX.checked_div(Decimal::ZERO), None);
        assert_eq!(Decimal::MAX.checked_mul(Decimal::MAX), None);
        assert_eq!(Decimal::MAX.checked_mul(Decimal::MIN), None);
        assert_eq!(Decimal::MAX.checked_rem(Decimal::ZERO), None);
        assert_eq!(Decimal::MAX.checked_sub(Decimal::MAX), Some(Decimal::ZERO));
        assert_eq!(Decimal::MAX.checked_sub(Decimal::MIN), None);
        assert_eq!(Decimal::MIN.checked_add(Decimal::MAX), Some(Decimal::ZERO));
        assert_eq!(Decimal::MIN.checked_add(Decimal::MIN), None);
        assert_eq!(Decimal::MIN.checked_div(Decimal::ZERO), None);
        assert_eq!(Decimal::MIN.checked_mul(Decimal::MAX), None);
        assert_eq!(Decimal::MIN.checked_mul(Decimal::MIN), None);
        assert_eq!(Decimal::MIN.checked_rem(Decimal::ZERO), None);
        assert_eq!(Decimal::MIN.checked_sub(Decimal::MAX), None);
        assert_eq!(Decimal::MIN.checked_sub(Decimal::MIN), Some(Decimal::ZERO));
    }
    #[test]
    fn saturated_methods_have_correct_output() {
        assert_eq!(Decimal::MAX.saturating_add(Decimal::MAX), Decimal::MAX);
        assert_eq!(Decimal::MAX.saturating_add(Decimal::MIN), Decimal::ZERO);
        assert_eq!(Decimal::MAX.saturating_mul(Decimal::MAX), Decimal::MAX);
        assert_eq!(Decimal::MAX.saturating_mul(Decimal::MIN), Decimal::MIN);
        assert_eq!(Decimal::MAX.saturating_sub(Decimal::MAX), Decimal::ZERO);
        assert_eq!(Decimal::MAX.saturating_sub(Decimal::MIN), Decimal::MAX);
        assert_eq!(Decimal::MIN.saturating_add(Decimal::MAX), Decimal::ZERO);
        assert_eq!(Decimal::MIN.saturating_add(Decimal::MIN), Decimal::MIN);
        assert_eq!(Decimal::MIN.saturating_mul(Decimal::MAX), Decimal::MIN);
        assert_eq!(Decimal::MIN.saturating_mul(Decimal::MIN), Decimal::MAX);
        assert_eq!(Decimal::MIN.saturating_sub(Decimal::MAX), Decimal::MIN);
        assert_eq!(Decimal::MIN.saturating_sub(Decimal::MIN), Decimal::ZERO);
    }
}