use std::ops::{
    Add, AddAssign, Deref, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign,
};
use crate::numeric_support::call_numeric_func;
use crate::{pg_sys, AnyNumeric, Numeric};
impl<const P: u32, const S: u32> Deref for Numeric<P, S> {
    type Target = AnyNumeric;
    #[inline]
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}
macro_rules! anynumeric_math_op {
    ($opname:ident, $trait_fnname:ident, $pg_func:ident) => {
        impl $opname<AnyNumeric> for AnyNumeric {
            type Output = AnyNumeric;
            #[inline]
            fn $trait_fnname(self, rhs: AnyNumeric) -> Self::Output {
                call_numeric_func(pg_sys::$pg_func, &[self.as_datum(), rhs.as_datum()])
            }
        }
        impl<const P: u32, const S: u32> $opname<AnyNumeric> for Numeric<P, S> {
            type Output = AnyNumeric;
            #[inline]
            fn $trait_fnname(self, rhs: AnyNumeric) -> Self::Output {
                call_numeric_func(pg_sys::$pg_func, &[self.as_datum(), rhs.as_datum()])
            }
        }
    };
}
anynumeric_math_op!(Add, add, numeric_add);
anynumeric_math_op!(Sub, sub, numeric_sub);
anynumeric_math_op!(Mul, mul, numeric_mul);
anynumeric_math_op!(Div, div, numeric_div);
anynumeric_math_op!(Rem, rem, numeric_mod);
macro_rules! numeric_math_op {
    ($opname:ident, $trait_fnname:ident, $pg_func:ident) => {
        impl<const P: u32, const S: u32, const Q: u32, const T: u32> $opname<Numeric<Q, T>>
            for Numeric<P, S>
        {
            type Output = AnyNumeric;
            #[inline]
            fn $trait_fnname(self, rhs: Numeric<Q, T>) -> Self::Output {
                call_numeric_func(pg_sys::$pg_func, &[self.as_datum(), rhs.as_datum()])
            }
        }
        impl<const Q: u32, const T: u32> $opname<Numeric<Q, T>> for AnyNumeric {
            type Output = AnyNumeric;
            #[inline]
            fn $trait_fnname(self, rhs: Numeric<Q, T>) -> Self::Output {
                call_numeric_func(pg_sys::$pg_func, &[self.as_datum(), rhs.as_datum()])
            }
        }
    };
}
numeric_math_op!(Add, add, numeric_add);
numeric_math_op!(Sub, sub, numeric_sub);
numeric_math_op!(Mul, mul, numeric_mul);
numeric_math_op!(Div, div, numeric_div);
numeric_math_op!(Rem, rem, numeric_mod);
impl Neg for AnyNumeric {
    type Output = AnyNumeric;
    #[inline]
    fn neg(self) -> Self::Output {
        call_numeric_func(pg_sys::numeric_uminus, &[self.as_datum()])
    }
}
impl<const P: u32, const S: u32> Neg for Numeric<P, S> {
    type Output = Numeric<P, S>;
    #[inline]
    fn neg(self) -> Self::Output {
        Numeric(call_numeric_func(pg_sys::numeric_uminus, &[self.as_datum()]))
    }
}
macro_rules! anynumeric_assign_op_from_anynumeric {
    ($opname:ident, $trait_fname:ident, $pg_func:ident) => {
        impl $opname<AnyNumeric> for AnyNumeric {
            fn $trait_fname(&mut self, rhs: AnyNumeric) {
                *self = call_numeric_func(pg_sys::$pg_func, &[self.as_datum(), rhs.as_datum()]);
            }
        }
    };
}
anynumeric_assign_op_from_anynumeric!(AddAssign, add_assign, numeric_add);
anynumeric_assign_op_from_anynumeric!(SubAssign, sub_assign, numeric_sub);
anynumeric_assign_op_from_anynumeric!(MulAssign, mul_assign, numeric_mul);
anynumeric_assign_op_from_anynumeric!(DivAssign, div_assign, numeric_div);
anynumeric_assign_op_from_anynumeric!(RemAssign, rem_assign, numeric_mod);
macro_rules! anynumeric_assign_op_from_primitive {
    ($opname:ident, $trait_fname:ident, $ty:ty, $op:tt) => {
        impl $opname<$ty> for AnyNumeric {
            #[inline]
            fn $trait_fname(&mut self, rhs: $ty) {
                *self = self.clone() $op AnyNumeric::from(rhs);
            }
        }
    }
}
anynumeric_assign_op_from_primitive!(AddAssign, add_assign, i128, +);
anynumeric_assign_op_from_primitive!(AddAssign, add_assign, isize, +);
anynumeric_assign_op_from_primitive!(AddAssign, add_assign, i64, +);
anynumeric_assign_op_from_primitive!(AddAssign, add_assign, i32, +);
anynumeric_assign_op_from_primitive!(AddAssign, add_assign, i16, +);
anynumeric_assign_op_from_primitive!(AddAssign, add_assign, i8, +);
anynumeric_assign_op_from_primitive!(AddAssign, add_assign, u128, +);
anynumeric_assign_op_from_primitive!(AddAssign, add_assign, usize, +);
anynumeric_assign_op_from_primitive!(AddAssign, add_assign, u64, +);
anynumeric_assign_op_from_primitive!(AddAssign, add_assign, u32, +);
anynumeric_assign_op_from_primitive!(AddAssign, add_assign, u16, +);
anynumeric_assign_op_from_primitive!(AddAssign, add_assign, u8, +);
anynumeric_assign_op_from_primitive!(SubAssign, sub_assign, i128, -);
anynumeric_assign_op_from_primitive!(SubAssign, sub_assign, isize, -);
anynumeric_assign_op_from_primitive!(SubAssign, sub_assign, i64, -);
anynumeric_assign_op_from_primitive!(SubAssign, sub_assign, i32, -);
anynumeric_assign_op_from_primitive!(SubAssign, sub_assign, i16, -);
anynumeric_assign_op_from_primitive!(SubAssign, sub_assign, i8, -);
anynumeric_assign_op_from_primitive!(SubAssign, sub_assign, u128, -);
anynumeric_assign_op_from_primitive!(SubAssign, sub_assign, usize, -);
anynumeric_assign_op_from_primitive!(SubAssign, sub_assign, u64, -);
anynumeric_assign_op_from_primitive!(SubAssign, sub_assign, u32, -);
anynumeric_assign_op_from_primitive!(SubAssign, sub_assign, u16, -);
anynumeric_assign_op_from_primitive!(SubAssign, sub_assign, u8, -);
anynumeric_assign_op_from_primitive!(MulAssign, mul_assign, i128, *);
anynumeric_assign_op_from_primitive!(MulAssign, mul_assign, isize, *);
anynumeric_assign_op_from_primitive!(MulAssign, mul_assign, i64, *);
anynumeric_assign_op_from_primitive!(MulAssign, mul_assign, i32, *);
anynumeric_assign_op_from_primitive!(MulAssign, mul_assign, i16, *);
anynumeric_assign_op_from_primitive!(MulAssign, mul_assign, i8, *);
anynumeric_assign_op_from_primitive!(MulAssign, mul_assign, u128, *);
anynumeric_assign_op_from_primitive!(MulAssign, mul_assign, usize, *);
anynumeric_assign_op_from_primitive!(MulAssign, mul_assign, u64, *);
anynumeric_assign_op_from_primitive!(MulAssign, mul_assign, u32, *);
anynumeric_assign_op_from_primitive!(MulAssign, mul_assign, u16, *);
anynumeric_assign_op_from_primitive!(MulAssign, mul_assign, u8, *);
anynumeric_assign_op_from_primitive!(DivAssign, div_assign, i128, /);
anynumeric_assign_op_from_primitive!(DivAssign, div_assign, isize, /);
anynumeric_assign_op_from_primitive!(DivAssign, div_assign, i64, /);
anynumeric_assign_op_from_primitive!(DivAssign, div_assign, i32, /);
anynumeric_assign_op_from_primitive!(DivAssign, div_assign, i16, /);
anynumeric_assign_op_from_primitive!(DivAssign, div_assign, i8, /);
anynumeric_assign_op_from_primitive!(DivAssign, div_assign, u128, /);
anynumeric_assign_op_from_primitive!(DivAssign, div_assign, usize, /);
anynumeric_assign_op_from_primitive!(DivAssign, div_assign, u64, /);
anynumeric_assign_op_from_primitive!(DivAssign, div_assign, u32, /);
anynumeric_assign_op_from_primitive!(DivAssign, div_assign, u16, /);
anynumeric_assign_op_from_primitive!(DivAssign, div_assign, u8, /);
anynumeric_assign_op_from_primitive!(RemAssign, rem_assign, i128, %);
anynumeric_assign_op_from_primitive!(RemAssign, rem_assign, isize, %);
anynumeric_assign_op_from_primitive!(RemAssign, rem_assign, i64, %);
anynumeric_assign_op_from_primitive!(RemAssign, rem_assign, i32, %);
anynumeric_assign_op_from_primitive!(RemAssign, rem_assign, i16, %);
anynumeric_assign_op_from_primitive!(RemAssign, rem_assign, i8, %);
anynumeric_assign_op_from_primitive!(RemAssign, rem_assign, u128, %);
anynumeric_assign_op_from_primitive!(RemAssign, rem_assign, usize, %);
anynumeric_assign_op_from_primitive!(RemAssign, rem_assign, u64, %);
anynumeric_assign_op_from_primitive!(RemAssign, rem_assign, u32, %);
anynumeric_assign_op_from_primitive!(RemAssign, rem_assign, u16, %);
anynumeric_assign_op_from_primitive!(RemAssign, rem_assign, u8, %);
macro_rules! anynumeric_assign_op_from_float {
    ($opname:ident, $trait_fname:ident, $ty:ty, $op:tt) => {
        impl $opname<$ty> for AnyNumeric {
            #[inline]
            fn $trait_fname(&mut self, rhs: $ty) {
                #[cfg(any(feature = "pg11", feature = "pg12", feature = "pg13"))]
                {
                    *self = self.clone() $op AnyNumeric::try_from(rhs).unwrap();
                }
                #[cfg(any(feature = "pg14", feature = "pg15", feature = "pg16"))]
                {
                    unsafe {
                        *self = self.clone() $op AnyNumeric::try_from(rhs).unwrap_unchecked();
                    }
                }
            }
        }
    };
}
anynumeric_assign_op_from_float!(AddAssign, add_assign, f32, +);
anynumeric_assign_op_from_float!(SubAssign, sub_assign, f32, -);
anynumeric_assign_op_from_float!(MulAssign, mul_assign, f32, *);
anynumeric_assign_op_from_float!(DivAssign, div_assign, f32, /);
anynumeric_assign_op_from_float!(RemAssign, rem_assign, f32, %);
anynumeric_assign_op_from_float!(AddAssign, add_assign, f64, +);
anynumeric_assign_op_from_float!(SubAssign, sub_assign, f64, -);
anynumeric_assign_op_from_float!(MulAssign, mul_assign, f64, *);
anynumeric_assign_op_from_float!(DivAssign, div_assign, f64, /);
anynumeric_assign_op_from_float!(RemAssign, rem_assign, f64, %);
macro_rules! primitive_math_op {
    ($opname:ident, $primitive:ty, $trait_fnname:ident, $pg_func:ident) => {
        impl $opname<$primitive> for AnyNumeric {
            type Output = AnyNumeric;
            #[inline]
            fn $trait_fnname(self, rhs: $primitive) -> Self::Output {
                call_numeric_func(
                    pg_sys::$pg_func,
                    &[self.as_datum(), AnyNumeric::try_from(rhs).unwrap().as_datum()],
                )
            }
        }
        impl $opname<AnyNumeric> for $primitive {
            type Output = AnyNumeric;
            #[inline]
            fn $trait_fnname(self, rhs: AnyNumeric) -> Self::Output {
                call_numeric_func(
                    pg_sys::$pg_func,
                    &[AnyNumeric::try_from(self).unwrap().as_datum(), rhs.as_datum()],
                )
            }
        }
        impl<const P: u32, const S: u32> $opname<$primitive> for Numeric<P, S> {
            type Output = AnyNumeric;
            #[inline]
            fn $trait_fnname(self, rhs: $primitive) -> Self::Output {
                call_numeric_func(
                    pg_sys::$pg_func,
                    &[self.as_datum(), AnyNumeric::try_from(rhs).unwrap().as_datum()],
                )
            }
        }
        impl<const P: u32, const S: u32> $opname<Numeric<P, S>> for $primitive {
            type Output = AnyNumeric;
            #[inline]
            fn $trait_fnname(self, rhs: Numeric<P, S>) -> Self::Output {
                call_numeric_func(
                    pg_sys::$pg_func,
                    &[rhs.as_datum(), AnyNumeric::try_from(self).unwrap().as_datum()],
                )
            }
        }
    };
}
primitive_math_op!(Add, u8, add, numeric_add);
primitive_math_op!(Add, u16, add, numeric_add);
primitive_math_op!(Add, u32, add, numeric_add);
primitive_math_op!(Add, u64, add, numeric_add);
primitive_math_op!(Add, u128, add, numeric_add);
primitive_math_op!(Add, usize, add, numeric_add);
primitive_math_op!(Add, i8, add, numeric_add);
primitive_math_op!(Add, i16, add, numeric_add);
primitive_math_op!(Add, i32, add, numeric_add);
primitive_math_op!(Add, i64, add, numeric_add);
primitive_math_op!(Add, i128, add, numeric_add);
primitive_math_op!(Add, isize, add, numeric_add);
primitive_math_op!(Add, f32, add, numeric_add);
primitive_math_op!(Add, f64, add, numeric_add);
primitive_math_op!(Sub, u8, sub, numeric_sub);
primitive_math_op!(Sub, u16, sub, numeric_sub);
primitive_math_op!(Sub, u32, sub, numeric_sub);
primitive_math_op!(Sub, u64, sub, numeric_sub);
primitive_math_op!(Sub, u128, sub, numeric_sub);
primitive_math_op!(Sub, usize, sub, numeric_sub);
primitive_math_op!(Sub, i8, sub, numeric_sub);
primitive_math_op!(Sub, i16, sub, numeric_sub);
primitive_math_op!(Sub, i32, sub, numeric_sub);
primitive_math_op!(Sub, i64, sub, numeric_sub);
primitive_math_op!(Sub, i128, sub, numeric_sub);
primitive_math_op!(Sub, isize, sub, numeric_sub);
primitive_math_op!(Sub, f32, sub, numeric_sub);
primitive_math_op!(Sub, f64, sub, numeric_sub);
primitive_math_op!(Mul, u8, mul, numeric_mul);
primitive_math_op!(Mul, u16, mul, numeric_mul);
primitive_math_op!(Mul, u32, mul, numeric_mul);
primitive_math_op!(Mul, u64, mul, numeric_mul);
primitive_math_op!(Mul, u128, mul, numeric_mul);
primitive_math_op!(Mul, usize, mul, numeric_mul);
primitive_math_op!(Mul, i8, mul, numeric_mul);
primitive_math_op!(Mul, i16, mul, numeric_mul);
primitive_math_op!(Mul, i32, mul, numeric_mul);
primitive_math_op!(Mul, i64, mul, numeric_mul);
primitive_math_op!(Mul, i128, mul, numeric_mul);
primitive_math_op!(Mul, isize, mul, numeric_mul);
primitive_math_op!(Mul, f32, mul, numeric_mul);
primitive_math_op!(Mul, f64, mul, numeric_mul);
primitive_math_op!(Div, u8, div, numeric_div);
primitive_math_op!(Div, u16, div, numeric_div);
primitive_math_op!(Div, u32, div, numeric_div);
primitive_math_op!(Div, u64, div, numeric_div);
primitive_math_op!(Div, u128, div, numeric_div);
primitive_math_op!(Div, usize, div, numeric_div);
primitive_math_op!(Div, i8, div, numeric_div);
primitive_math_op!(Div, i16, div, numeric_div);
primitive_math_op!(Div, i32, div, numeric_div);
primitive_math_op!(Div, i64, div, numeric_div);
primitive_math_op!(Div, i128, div, numeric_div);
primitive_math_op!(Div, isize, div, numeric_div);
primitive_math_op!(Div, f32, div, numeric_div);
primitive_math_op!(Div, f64, div, numeric_div);
primitive_math_op!(Rem, u8, rem, numeric_mod);
primitive_math_op!(Rem, u16, rem, numeric_mod);
primitive_math_op!(Rem, u32, rem, numeric_mod);
primitive_math_op!(Rem, u64, rem, numeric_mod);
primitive_math_op!(Rem, u128, rem, numeric_mod);
primitive_math_op!(Rem, usize, rem, numeric_mod);
primitive_math_op!(Rem, i8, rem, numeric_mod);
primitive_math_op!(Rem, i16, rem, numeric_mod);
primitive_math_op!(Rem, i32, rem, numeric_mod);
primitive_math_op!(Rem, i64, rem, numeric_mod);
primitive_math_op!(Rem, i128, rem, numeric_mod);
primitive_math_op!(Rem, isize, rem, numeric_mod);
primitive_math_op!(Rem, f32, rem, numeric_mod);
primitive_math_op!(Rem, f64, rem, numeric_mod);