use core::cmp::Ordering;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct UInt128 {
    pub high: u64,
    pub low: u64,
}

impl UInt128 {
    #[inline(always)]
    pub fn new(high: u64, low: u64) -> Self {
        Self { high, low }
    }

    #[inline]
    fn sum(a: u64, b: u64, c: u64) -> (u64, u64) {
        let (s0, overflow0) = a.overflowing_add(b);
        let mut high = if overflow0 { 1 } else { 0 };
        let (s1, overflow1) = s0.overflowing_add(c);
        if overflow1 {
            high += 1;
        }

        (s1, high)
    }

    #[inline]
    pub fn multiply(a: u64, b: u64) -> Self {
        if a.leading_zeros() + b.leading_zeros() >= u64::BITS {
            return Self::new(0, a * b);
        }

        let a1 = a >> 32;
        let a0 = a & 0xFFFF_FFFF;
        let b1 = b >> 32;
        let b0 = b & 0xFFFF_FFFF;

        let ab00 = a0 * b0;
        let (m_partial, m_high) = Self::sum(a0 * b1, a1 * b0, ab00 >> 32);
        let high = a1 * b1 + (m_partial >> 32) + (m_high << 32);

        let low = (m_partial << 32) | (ab00 & 0xFFFF_FFFF);

        Self::new(high, low)
    }
}

impl PartialOrd for UInt128 {
    #[inline(always)]
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for UInt128 {
    #[inline(always)]
    fn cmp(&self, other: &Self) -> Ordering {
        let cmp_high = self.high.cmp(&other.high);
        match cmp_high {
            Ordering::Equal => self.low.cmp(&other.low),
            _ => cmp_high,
        }
    }
}
