//! Serial number arithmetic.
//!
//! See [RFC 1982](https://datatracker.ietf.org/doc/html/rfc1982).

use core::{cmp::Ordering, fmt};

use domain_macros::*;

use super::wire::U32;

//----------- Serial ---------------------------------------------------------

/// Serial number arithmetic.
///
/// [`Serial`] implements the "Serial number arithmetic" defined in [RFC1982]
/// with a `SERIAL_BITS` value of 32.
///
/// [`Serial`] should not be used interchangably or be confused with the SOA
/// Serial Number, which is a [`Serial`] but not the sole user of "Serial
/// number arithmetic".
///
/// [RFC1982]: https://datatracker.ietf.org/doc/html/rfc1982
#[derive(
    Copy,
    Clone,
    Debug,
    PartialEq,
    Eq,
    Hash,
    AsBytes,
    BuildBytes,
    ParseBytes,
    ParseBytesZC,
    SplitBytes,
    SplitBytesZC,
    UnsizedCopy,
)]
#[repr(transparent)]
pub struct Serial(pub U32);

//--- Construction

impl Serial {
    /// Construct a new [`Serial`]
    #[must_use]
    pub const fn new(value: u32) -> Self {
        Serial(U32::new(value))
    }

    /// Get [`u32`] value of [`Serial`]
    #[must_use]
    pub const fn get(&self) -> u32 {
        self.0.get()
    }

    /// Measure the current time (in seconds) in serial number space.
    #[cfg(feature = "std")]
    pub fn unix_time() -> Self {
        use std::time::SystemTime;

        let time = SystemTime::now()
            .duration_since(SystemTime::UNIX_EPOCH)
            .expect("The current time is after the Unix Epoch");
        Self::from(time.as_secs() as u32)
    }
}

//--- Interaction

impl Serial {
    /// Increment this by a non-negative number.
    ///
    /// The number must be in the range `[0, 2^31 - 1]`. An [`i32`] is used
    /// instead of a [`u32`] because it is easier to understand and implement
    /// a non-negative check versus the upper range check.
    ///
    /// Section 7 in [RFC1982] states, in particular for the SOA Serial
    /// Number, but for any Serial number using a "SERIAL_BITS" value of 32:
    /// "The maximum defined increment is 2147483647 (2^31 - 1)."
    ///
    /// # Panics
    ///
    /// Panics if the number is negative.
    ///
    /// [RFC1982]: https://datatracker.ietf.org/doc/html/rfc1982#section-7
    pub fn inc(self, num: i32) -> Self {
        assert!(num >= 0, "Cannot subtract from a `Serial`");
        self.0.get().wrapping_add_signed(num).into()
    }
}

//--- Ordering

impl PartialOrd for Serial {
    /// The comparison of Serial Number values is defined in Section 3.2
    /// [RFC1982].
    ///
    /// [RFC1982]: https://datatracker.ietf.org/doc/html/rfc1982#section-3.2
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        let (lhs, rhs) = (self.0.get(), other.0.get());

        if lhs == rhs {
            Some(Ordering::Equal)
        } else if lhs.abs_diff(rhs) == 1 << 31 {
            None
        } else if (lhs < rhs) ^ (lhs.abs_diff(rhs) > (1 << 31)) {
            Some(Ordering::Less)
        } else {
            Some(Ordering::Greater)
        }
    }
}

//--- Conversion to and from native integer types

impl From<u32> for Serial {
    fn from(value: u32) -> Self {
        Self(U32::new(value))
    }
}

impl From<Serial> for u32 {
    fn from(value: Serial) -> Self {
        value.0.get()
    }
}

//--- Formatting

impl fmt::Display for Serial {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.0.get().fmt(f)
    }
}

//============ Tests =========================================================

#[cfg(test)]
mod test {
    use super::Serial;

    #[test]
    fn comparisons() {
        // TODO: Use property-based testing.
        assert!(Serial::from(u32::MAX) > Serial::from(u32::MAX / 2 + 1));
        assert!(Serial::from(0) > Serial::from(u32::MAX));
        assert!(Serial::from(1) > Serial::from(0));
    }

    #[test]
    fn operations() {
        // TODO: Use property-based testing.
        assert_eq!(u32::from(Serial::from(1).inc(1)), 2);
        assert_eq!(u32::from(Serial::from(u32::MAX).inc(1)), 0);
    }
}
