//! [`BoxedUint`] comparisons.
//!
//! By default these are all constant-time and use the `subtle` crate.

pub(super) use core::cmp::{Ordering, max};

use super::BoxedUint;
use crate::{CtAssign, CtEq, CtGt, CtLt, Limb, Uint};

impl BoxedUint {
    /// Returns the Ordering between `self` and `rhs` in variable time.
    #[must_use]
    pub fn cmp_vartime(&self, rhs: &Self) -> Ordering {
        debug_assert_eq!(self.limbs.len(), rhs.limbs.len());
        let mut i = self.limbs.len() - 1;
        loop {
            // TODO: investigate if directly comparing limbs is faster than performing a
            // subtraction between limbs
            let (val, borrow) = self.limbs[i].borrowing_sub(rhs.limbs[i], Limb::ZERO);
            if val.0 != 0 {
                return if borrow.0 != 0 {
                    Ordering::Less
                } else {
                    Ordering::Greater
                };
            }
            if i == 0 {
                return Ordering::Equal;
            }
            i -= 1;
        }
    }

    /// Determine in variable time whether the `self` is zero.
    pub(crate) fn is_zero_vartime(&self) -> bool {
        !self.limbs.iter().any(|l| l.0 != 0)
    }
}

impl Eq for BoxedUint {}
impl PartialEq for BoxedUint {
    fn eq(&self, other: &Self) -> bool {
        self.ct_eq(other).into()
    }
}

impl<const LIMBS: usize> PartialEq<Uint<LIMBS>> for BoxedUint {
    fn eq(&self, other: &Uint<LIMBS>) -> bool {
        self.eq(&Self::from(other))
    }
}

impl Ord for BoxedUint {
    fn cmp(&self, other: &Self) -> Ordering {
        let mut ret = Ordering::Equal;
        ret.ct_assign(&Ordering::Greater, self.ct_gt(other));
        ret.ct_assign(&Ordering::Less, self.ct_lt(other));

        #[cfg(debug_assertions)]
        if ret == Ordering::Equal {
            debug_assert_eq!(self, other);
        }

        ret
    }
}

impl PartialOrd for BoxedUint {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl<const LIMBS: usize> PartialOrd<Uint<LIMBS>> for BoxedUint {
    fn partial_cmp(&self, other: &Uint<LIMBS>) -> Option<Ordering> {
        self.partial_cmp(&Self::from(other))
    }
}

#[cfg(test)]
mod tests {
    use super::BoxedUint;
    use core::cmp::Ordering;

    #[test]
    fn cmp() {
        let a = BoxedUint::zero();
        let b = BoxedUint::one();
        let c = BoxedUint::max(64);

        assert_eq!(a.cmp(&b), Ordering::Less);
        assert_eq!(a.cmp(&c), Ordering::Less);
        assert_eq!(b.cmp(&c), Ordering::Less);

        assert_eq!(a.cmp(&a), Ordering::Equal);
        assert_eq!(b.cmp(&b), Ordering::Equal);
        assert_eq!(c.cmp(&c), Ordering::Equal);

        assert_eq!(b.cmp(&a), Ordering::Greater);
        assert_eq!(c.cmp(&a), Ordering::Greater);
        assert_eq!(c.cmp(&b), Ordering::Greater);
    }
}
