use super::CheckedUnsignedAbs::{Negative, Positive};
use super::Sign::NoSign;
use super::{BigInt, UnsignedAbs};

use crate::{IsizePromotion, UsizePromotion};

use core::ops::{Div, DivAssign, Rem, RemAssign};
use num_integer::Integer;
use num_traits::{CheckedDiv, CheckedEuclid, Euclid, Signed, ToPrimitive, Zero};

forward_all_binop_to_ref_ref!(impl Div for BigInt, div);

impl Div<&BigInt> for &BigInt {
    type Output = BigInt;

    #[inline]
    fn div(self, other: &BigInt) -> BigInt {
        let (q, _) = self.div_rem(other);
        q
    }
}

impl DivAssign<&BigInt> for BigInt {
    #[inline]
    fn div_assign(&mut self, other: &BigInt) {
        *self = &*self / other;
    }
}
forward_val_assign!(impl DivAssign for BigInt, div_assign);

promote_all_scalars!(impl Div for BigInt, div);
promote_all_scalars_assign!(impl DivAssign for BigInt, div_assign);
forward_all_scalar_binop_to_val_val!(impl Div<u32> for BigInt, div);
forward_all_scalar_binop_to_val_val!(impl Div<u64> for BigInt, div);
forward_all_scalar_binop_to_val_val!(impl Div<u128> for BigInt, div);

impl Div<u32> for BigInt {
    type Output = BigInt;

    #[inline]
    fn div(self, other: u32) -> BigInt {
        BigInt::from_biguint(self.sign, self.data / other)
    }
}

impl DivAssign<u32> for BigInt {
    #[inline]
    fn div_assign(&mut self, other: u32) {
        self.data /= other;
        if self.data.is_zero() {
            self.sign = NoSign;
        }
    }
}

impl Div<BigInt> for u32 {
    type Output = BigInt;

    #[inline]
    fn div(self, other: BigInt) -> BigInt {
        BigInt::from_biguint(other.sign, self / other.data)
    }
}

impl Div<u64> for BigInt {
    type Output = BigInt;

    #[inline]
    fn div(self, other: u64) -> BigInt {
        BigInt::from_biguint(self.sign, self.data / other)
    }
}

impl DivAssign<u64> for BigInt {
    #[inline]
    fn div_assign(&mut self, other: u64) {
        self.data /= other;
        if self.data.is_zero() {
            self.sign = NoSign;
        }
    }
}

impl Div<BigInt> for u64 {
    type Output = BigInt;

    #[inline]
    fn div(self, other: BigInt) -> BigInt {
        BigInt::from_biguint(other.sign, self / other.data)
    }
}

impl Div<u128> for BigInt {
    type Output = BigInt;

    #[inline]
    fn div(self, other: u128) -> BigInt {
        BigInt::from_biguint(self.sign, self.data / other)
    }
}

impl DivAssign<u128> for BigInt {
    #[inline]
    fn div_assign(&mut self, other: u128) {
        self.data /= other;
        if self.data.is_zero() {
            self.sign = NoSign;
        }
    }
}

impl Div<BigInt> for u128 {
    type Output = BigInt;

    #[inline]
    fn div(self, other: BigInt) -> BigInt {
        BigInt::from_biguint(other.sign, self / other.data)
    }
}

forward_all_scalar_binop_to_val_val!(impl Div<i32> for BigInt, div);
forward_all_scalar_binop_to_val_val!(impl Div<i64> for BigInt, div);
forward_all_scalar_binop_to_val_val!(impl Div<i128> for BigInt, div);

impl Div<i32> for BigInt {
    type Output = BigInt;

    #[inline]
    fn div(self, other: i32) -> BigInt {
        match other.checked_uabs() {
            Positive(u) => self / u,
            Negative(u) => -self / u,
        }
    }
}

impl DivAssign<i32> for BigInt {
    #[inline]
    fn div_assign(&mut self, other: i32) {
        match other.checked_uabs() {
            Positive(u) => *self /= u,
            Negative(u) => {
                self.sign = -self.sign;
                *self /= u;
            }
        }
    }
}

impl Div<BigInt> for i32 {
    type Output = BigInt;

    #[inline]
    fn div(self, other: BigInt) -> BigInt {
        match self.checked_uabs() {
            Positive(u) => u / other,
            Negative(u) => u / -other,
        }
    }
}

impl Div<i64> for BigInt {
    type Output = BigInt;

    #[inline]
    fn div(self, other: i64) -> BigInt {
        match other.checked_uabs() {
            Positive(u) => self / u,
            Negative(u) => -self / u,
        }
    }
}

impl DivAssign<i64> for BigInt {
    #[inline]
    fn div_assign(&mut self, other: i64) {
        match other.checked_uabs() {
            Positive(u) => *self /= u,
            Negative(u) => {
                self.sign = -self.sign;
                *self /= u;
            }
        }
    }
}

impl Div<BigInt> for i64 {
    type Output = BigInt;

    #[inline]
    fn div(self, other: BigInt) -> BigInt {
        match self.checked_uabs() {
            Positive(u) => u / other,
            Negative(u) => u / -other,
        }
    }
}

impl Div<i128> for BigInt {
    type Output = BigInt;

    #[inline]
    fn div(self, other: i128) -> BigInt {
        match other.checked_uabs() {
            Positive(u) => self / u,
            Negative(u) => -self / u,
        }
    }
}

impl DivAssign<i128> for BigInt {
    #[inline]
    fn div_assign(&mut self, other: i128) {
        match other.checked_uabs() {
            Positive(u) => *self /= u,
            Negative(u) => {
                self.sign = -self.sign;
                *self /= u;
            }
        }
    }
}

impl Div<BigInt> for i128 {
    type Output = BigInt;

    #[inline]
    fn div(self, other: BigInt) -> BigInt {
        match self.checked_uabs() {
            Positive(u) => u / other,
            Negative(u) => u / -other,
        }
    }
}

forward_all_binop_to_ref_ref!(impl Rem for BigInt, rem);

impl Rem<&BigInt> for &BigInt {
    type Output = BigInt;

    #[inline]
    fn rem(self, other: &BigInt) -> BigInt {
        if let Some(other) = other.to_u32() {
            self % other
        } else if let Some(other) = other.to_i32() {
            self % other
        } else {
            let (_, r) = self.div_rem(other);
            r
        }
    }
}

impl RemAssign<&BigInt> for BigInt {
    #[inline]
    fn rem_assign(&mut self, other: &BigInt) {
        *self = &*self % other;
    }
}
forward_val_assign!(impl RemAssign for BigInt, rem_assign);

promote_all_scalars!(impl Rem for BigInt, rem);
promote_all_scalars_assign!(impl RemAssign for BigInt, rem_assign);
forward_all_scalar_binop_to_val_val!(impl Rem<u32> for BigInt, rem);
forward_all_scalar_binop_to_val_val!(impl Rem<u64> for BigInt, rem);
forward_all_scalar_binop_to_val_val!(impl Rem<u128> for BigInt, rem);

impl Rem<u32> for BigInt {
    type Output = BigInt;

    #[inline]
    fn rem(self, other: u32) -> BigInt {
        BigInt::from_biguint(self.sign, self.data % other)
    }
}

impl RemAssign<u32> for BigInt {
    #[inline]
    fn rem_assign(&mut self, other: u32) {
        self.data %= other;
        if self.data.is_zero() {
            self.sign = NoSign;
        }
    }
}

impl Rem<BigInt> for u32 {
    type Output = BigInt;

    #[inline]
    fn rem(self, other: BigInt) -> BigInt {
        BigInt::from(self % other.data)
    }
}

impl Rem<u64> for BigInt {
    type Output = BigInt;

    #[inline]
    fn rem(self, other: u64) -> BigInt {
        BigInt::from_biguint(self.sign, self.data % other)
    }
}

impl RemAssign<u64> for BigInt {
    #[inline]
    fn rem_assign(&mut self, other: u64) {
        self.data %= other;
        if self.data.is_zero() {
            self.sign = NoSign;
        }
    }
}

impl Rem<BigInt> for u64 {
    type Output = BigInt;

    #[inline]
    fn rem(self, other: BigInt) -> BigInt {
        BigInt::from(self % other.data)
    }
}

impl Rem<u128> for BigInt {
    type Output = BigInt;

    #[inline]
    fn rem(self, other: u128) -> BigInt {
        BigInt::from_biguint(self.sign, self.data % other)
    }
}

impl RemAssign<u128> for BigInt {
    #[inline]
    fn rem_assign(&mut self, other: u128) {
        self.data %= other;
        if self.data.is_zero() {
            self.sign = NoSign;
        }
    }
}

impl Rem<BigInt> for u128 {
    type Output = BigInt;

    #[inline]
    fn rem(self, other: BigInt) -> BigInt {
        BigInt::from(self % other.data)
    }
}

forward_all_scalar_binop_to_val_val!(impl Rem<i32> for BigInt, rem);
forward_all_scalar_binop_to_val_val!(impl Rem<i64> for BigInt, rem);
forward_all_scalar_binop_to_val_val!(impl Rem<i128> for BigInt, rem);

impl Rem<i32> for BigInt {
    type Output = BigInt;

    #[inline]
    fn rem(self, other: i32) -> BigInt {
        self % other.uabs()
    }
}

impl RemAssign<i32> for BigInt {
    #[inline]
    fn rem_assign(&mut self, other: i32) {
        *self %= other.uabs();
    }
}

impl Rem<BigInt> for i32 {
    type Output = BigInt;

    #[inline]
    fn rem(self, other: BigInt) -> BigInt {
        match self.checked_uabs() {
            Positive(u) => u % other,
            Negative(u) => -(u % other),
        }
    }
}

impl Rem<i64> for BigInt {
    type Output = BigInt;

    #[inline]
    fn rem(self, other: i64) -> BigInt {
        self % other.uabs()
    }
}

impl RemAssign<i64> for BigInt {
    #[inline]
    fn rem_assign(&mut self, other: i64) {
        *self %= other.uabs();
    }
}

impl Rem<BigInt> for i64 {
    type Output = BigInt;

    #[inline]
    fn rem(self, other: BigInt) -> BigInt {
        match self.checked_uabs() {
            Positive(u) => u % other,
            Negative(u) => -(u % other),
        }
    }
}

impl Rem<i128> for BigInt {
    type Output = BigInt;

    #[inline]
    fn rem(self, other: i128) -> BigInt {
        self % other.uabs()
    }
}

impl RemAssign<i128> for BigInt {
    #[inline]
    fn rem_assign(&mut self, other: i128) {
        *self %= other.uabs();
    }
}

impl Rem<BigInt> for i128 {
    type Output = BigInt;

    #[inline]
    fn rem(self, other: BigInt) -> BigInt {
        match self.checked_uabs() {
            Positive(u) => u % other,
            Negative(u) => -(u % other),
        }
    }
}

impl CheckedDiv for BigInt {
    #[inline]
    fn checked_div(&self, v: &BigInt) -> Option<BigInt> {
        if v.is_zero() {
            return None;
        }
        Some(self.div(v))
    }
}

impl CheckedEuclid for BigInt {
    #[inline]
    fn checked_div_euclid(&self, v: &BigInt) -> Option<BigInt> {
        if v.is_zero() {
            return None;
        }
        Some(self.div_euclid(v))
    }

    #[inline]
    fn checked_rem_euclid(&self, v: &BigInt) -> Option<BigInt> {
        if v.is_zero() {
            return None;
        }
        Some(self.rem_euclid(v))
    }
}

impl Euclid for BigInt {
    #[inline]
    fn div_euclid(&self, v: &BigInt) -> BigInt {
        let (q, r) = self.div_rem(v);
        if r.is_negative() {
            if v.is_positive() {
                q - 1
            } else {
                q + 1
            }
        } else {
            q
        }
    }

    #[inline]
    fn rem_euclid(&self, v: &BigInt) -> BigInt {
        let r = self % v;
        if r.is_negative() {
            if v.is_positive() {
                r + v
            } else {
                r - v
            }
        } else {
            r
        }
    }
}
