use std::convert::TryFrom;
use std::mem::size_of;
use std::ops::Not;

use traiter::numbers::{CheckedShr, DivRem, Sign, Signed, Zero, Zeroable};

use super::digits::{
    to_digits_sign, PrimitiveShiftDigitsRight, ShiftDigitsRight,
};
use super::types::{BigInt, ShrError};

impl<Digit: ShiftDigitsRight, const DIGIT_BITNESS: usize> CheckedShr
    for BigInt<Digit, DIGIT_BITNESS>
where
    for<'a> &'a Self: Signed,
{
    type Output = Result<Self, ShrError>;

    fn checked_shr(self, shift: Self) -> Self::Output {
        match (&shift).sign() {
            Sign::Negative => Err(ShrError::NegativeShift),
            Sign::Positive => {
                let (sign, digits) = Digit::shift_digits_right::<DIGIT_BITNESS>(
                    self.sign,
                    &self.digits,
                    &shift.digits,
                );
                Ok(BigInt::<Digit, DIGIT_BITNESS> { sign, digits })
            }
            Sign::Zero => Ok(self),
        }
    }
}

impl<Digit: ShiftDigitsRight, const DIGIT_BITNESS: usize> CheckedShr<&Self>
    for BigInt<Digit, DIGIT_BITNESS>
where
    for<'a> &'a Self: Signed,
{
    type Output = Result<Self, ShrError>;

    fn checked_shr(self, shift: &Self) -> Self::Output {
        match shift.sign() {
            Sign::Negative => Err(ShrError::NegativeShift),
            Sign::Positive => {
                let (sign, digits) = Digit::shift_digits_right::<DIGIT_BITNESS>(
                    self.sign,
                    &self.digits,
                    &shift.digits,
                );
                Ok(BigInt::<Digit, DIGIT_BITNESS> { sign, digits })
            }
            Sign::Zero => Ok(self),
        }
    }
}

impl<Digit: ShiftDigitsRight, const DIGIT_BITNESS: usize>
    CheckedShr<BigInt<Digit, DIGIT_BITNESS>> for &BigInt<Digit, DIGIT_BITNESS>
where
    for<'a> &'a BigInt<Digit, DIGIT_BITNESS>: Signed,
    BigInt<Digit, DIGIT_BITNESS>: Clone,
{
    type Output = Result<BigInt<Digit, DIGIT_BITNESS>, ShrError>;

    fn checked_shr(self, shift: BigInt<Digit, DIGIT_BITNESS>) -> Self::Output {
        match (&shift).sign() {
            Sign::Negative => Err(ShrError::NegativeShift),
            Sign::Positive => {
                let (sign, digits) = Digit::shift_digits_right::<DIGIT_BITNESS>(
                    self.sign,
                    &self.digits,
                    &shift.digits,
                );
                Ok(BigInt::<Digit, DIGIT_BITNESS> { sign, digits })
            }
            Sign::Zero => Ok(self.clone()),
        }
    }
}

impl<Digit: ShiftDigitsRight, const DIGIT_BITNESS: usize> CheckedShr
    for &BigInt<Digit, DIGIT_BITNESS>
where
    for<'a> &'a BigInt<Digit, DIGIT_BITNESS>: Signed,
    BigInt<Digit, DIGIT_BITNESS>: Clone,
{
    type Output = Result<BigInt<Digit, DIGIT_BITNESS>, ShrError>;

    fn checked_shr(self, shift: Self) -> Self::Output {
        match shift.sign() {
            Sign::Negative => Err(ShrError::NegativeShift),
            Sign::Positive => {
                let (sign, digits) = Digit::shift_digits_right::<DIGIT_BITNESS>(
                    self.sign,
                    &self.digits,
                    &shift.digits,
                );
                Ok(BigInt::<Digit, DIGIT_BITNESS> { sign, digits })
            }
            Sign::Zero => Ok(self.clone()),
        }
    }
}

macro_rules! checked_shr_signed_integer_impl {
    ($($integer:ty)*) => ($(
        impl<
                Digit: PrimitiveShiftDigitsRight + TryFrom<usize>,
                const DIGIT_BITNESS: usize,
            > CheckedShr<$integer> for BigInt<Digit, DIGIT_BITNESS>
        where
            for<'a> &'a Digit: Zeroable,
            Self: Clone + Not<Output = Self> + Zero,
        {
            type Output = Result<Self, ShrError>;

            fn checked_shr(self, shift: $integer) -> Self::Output {
                debug_assert!(
                    usize::BITS < <$integer>::BITS
                        || DIGIT_BITNESS < <$integer>::MAX as usize
                );
                match shift.sign() {
                    Sign::Negative => Err(ShrError::NegativeShift),
                    Sign::Positive => {
                        let (shift_quotient, shift_remainder) =
                            shift.div_rem(DIGIT_BITNESS as $integer);
                        if (<$integer>::BITS as usize) + 8 * size_of::<Digit>()
                            >= (usize::BITS as usize)
                            && unsafe {
                                usize::try_from(shift_quotient)
                                    .unwrap_unchecked()
                            } >= (usize::MAX / size_of::<Digit>())
                        {
                            Ok(Self::zero())
                        } else if (&self).is_negative() {
                            let inverted = !self;
                            let digits = Digit::primitive_shift_digits_right::<
                                DIGIT_BITNESS,
                            >(
                                &inverted.digits,
                                shift_quotient as usize,
                                unsafe {
                                    Digit::try_from(shift_remainder as usize)
                                        .unwrap_unchecked()
                                },
                            );
                            Ok(!Self {
                                sign: inverted.sign * to_digits_sign(&digits),
                                digits,
                            })
                        } else {
                            let digits = Digit::primitive_shift_digits_right::<
                                DIGIT_BITNESS,
                            >(
                                &self.digits,
                                shift_quotient as usize,
                                unsafe {
                                    Digit::try_from(shift_remainder as usize)
                                        .unwrap_unchecked()
                                },
                            );
                            Ok(Self {
                                sign: self.sign * to_digits_sign(&digits),
                                digits,
                            })
                        }
                    }
                    Sign::Zero => Ok(self),
                }
            }
        }

        impl<
                Digit: PrimitiveShiftDigitsRight + TryFrom<usize>,
                const DIGIT_BITNESS: usize,
            > CheckedShr<$integer> for &BigInt<Digit, DIGIT_BITNESS>
        where
            for<'a> &'a Digit: Zeroable,
            BigInt<Digit, DIGIT_BITNESS>:
                Clone + Not<Output = BigInt<Digit, DIGIT_BITNESS>> + Zero,
            Self: Not<Output = BigInt<Digit, DIGIT_BITNESS>>,
        {
            type Output = Result<BigInt<Digit, DIGIT_BITNESS>, ShrError>;

            fn checked_shr(self, shift: $integer) -> Self::Output {
                debug_assert!(
                    usize::BITS < <$integer>::BITS
                        || DIGIT_BITNESS < <$integer>::MAX as usize
                );
                match shift.sign() {
                    Sign::Negative => Err(ShrError::NegativeShift),
                    Sign::Positive => {
                        let (shift_quotient, shift_remainder) =
                            shift.div_rem(DIGIT_BITNESS as $integer);
                        if (<$integer>::BITS as usize) + 8 * size_of::<Digit>()
                            >= (usize::BITS as usize)
                            && unsafe {
                                usize::try_from(shift_quotient)
                                    .unwrap_unchecked()
                            } >= (usize::MAX / size_of::<Digit>())
                        {
                            Ok(BigInt::<Digit, DIGIT_BITNESS>::zero())
                        } else if self.is_negative() {
                            let inverted = !self;
                            let digits = Digit::primitive_shift_digits_right::<
                                DIGIT_BITNESS,
                            >(
                                &inverted.digits,
                                shift_quotient as usize,
                                unsafe {
                                    Digit::try_from(shift_remainder as usize)
                                        .unwrap_unchecked()
                                },
                            );
                            Ok(!BigInt::<Digit, DIGIT_BITNESS> {
                                sign: inverted.sign * to_digits_sign(&digits),
                                digits,
                            })
                        } else {
                            let digits = Digit::primitive_shift_digits_right::<
                                DIGIT_BITNESS,
                            >(
                                &self.digits,
                                shift_quotient as usize,
                                unsafe {
                                    Digit::try_from(shift_remainder as usize)
                                        .unwrap_unchecked()
                                },
                            );
                            Ok(BigInt::<Digit, DIGIT_BITNESS> {
                                sign: self.sign * to_digits_sign(&digits),
                                digits,
                            })
                        }
                    }
                    Sign::Zero => Ok(self.clone()),
                }
            }
        }
    )*)
}

checked_shr_signed_integer_impl!(i8 i16 i32 i64 i128 isize);

macro_rules! checked_shr_unsigned_integer_impl {
    ($($integer:ty)*) => ($(
        impl<
                Digit: PrimitiveShiftDigitsRight + TryFrom<usize>,
                const DIGIT_BITNESS: usize,
            > CheckedShr<$integer> for BigInt<Digit, DIGIT_BITNESS>
        where
            for<'a> &'a Digit: Zeroable,
            Self: Zero,
        {
            type Output = Result<Self, ShrError>;

            fn checked_shr(self, shift: $integer) -> Self::Output {
                debug_assert!(
                    usize::BITS < <$integer>::BITS
                        || DIGIT_BITNESS < <$integer>::MAX as usize
                );
                if shift == 0 {
                    Ok(self)
                } else {
                    let (shift_quotient, shift_remainder) =
                        shift.div_rem(DIGIT_BITNESS as $integer);
                    if (<$integer>::BITS as usize) + 8 * size_of::<Digit>()
                        >= (usize::BITS as usize)
                        && unsafe {
                            usize::try_from(shift_quotient).unwrap_unchecked()
                        } >= (usize::MAX / size_of::<Digit>())
                    {
                        Ok(Self::zero())
                    } else {
                        let digits =
                            Digit::primitive_shift_digits_right::<DIGIT_BITNESS>(
                                &self.digits,
                                shift_quotient as usize,
                                unsafe {
                                    Digit::try_from(shift_remainder as usize)
                                        .unwrap_unchecked()
                                },
                            );
                        Ok(Self {
                            sign: self.sign * to_digits_sign(&digits),
                            digits,
                        })
                    }
                }
            }
        }

        impl<
                Digit: PrimitiveShiftDigitsRight + TryFrom<usize>,
                const DIGIT_BITNESS: usize,
            > CheckedShr<$integer> for &BigInt<Digit, DIGIT_BITNESS>
        where
            for<'a> &'a Digit: Zeroable,
            BigInt<Digit, DIGIT_BITNESS>: Clone + Zero,
        {
            type Output = Result<BigInt<Digit, DIGIT_BITNESS>, ShrError>;

            fn checked_shr(self, shift: $integer) -> Self::Output {
                debug_assert!(
                    usize::BITS < <$integer>::BITS
                        || DIGIT_BITNESS < <$integer>::MAX as usize
                );
                if shift == 0 {
                    Ok(self.clone())
                } else {
                    let (shift_quotient, shift_remainder) =
                        shift.div_rem(DIGIT_BITNESS as $integer);
                    if (<$integer>::BITS as usize) + 8 * size_of::<Digit>()
                        >= (usize::BITS as usize)
                        && unsafe {
                            usize::try_from(shift_quotient).unwrap_unchecked()
                        } >= (usize::MAX / size_of::<Digit>())
                    {
                        Ok(BigInt::<Digit, DIGIT_BITNESS>::zero())
                    } else {
                        let digits =
                            Digit::primitive_shift_digits_right::<DIGIT_BITNESS>(
                                &self.digits,
                                shift_quotient as usize,
                                unsafe {
                                    Digit::try_from(shift_remainder as usize)
                                        .unwrap_unchecked()
                                },
                            );
                        Ok(BigInt::<Digit, DIGIT_BITNESS> {
                            sign: self.sign * to_digits_sign(&digits),
                            digits,
                        })
                    }
                }
            }
        }
    )*)
}

checked_shr_unsigned_integer_impl!(u8 u16 u32 u64 u128 usize);
