//! Time, as an input.
//!
//! C's sansIO half asks the service thread's time (`lws_wsi_now()`), which
//! an embedder sets with `lws_service_set_now()`; it never reads a clock to
//! decide anything.  Here the caller passes `now` into every entry point
//! that can decide by it, and asks the connection when it next needs to be
//! called.  Nothing in the protocol crates reads a clock.
//!
//! An [`Instant`] is a point on the IO side's monotonic clock, in
//! microseconds as C's `lws_usec_t` is, from an origin the IO side chooses.
//! Intervals are [`core::time::Duration`].

use core::time::Duration;

/// A point on the monotonic clock, in microseconds from an origin the IO
/// side chooses.  Instants from different clocks must not be mixed.
///
/// ```
/// use core::time::Duration;
/// use npro_core::time::Instant;
///
/// let t0 = Instant::from_micros(1_000_000_000);
/// let t1 = t0.checked_add(Duration::from_millis(5)).unwrap();
/// assert_eq!(t1.as_micros(), 1_000_005_000);
/// assert_eq!(t1.saturating_duration_since(t0), Duration::from_millis(5));
/// assert_eq!(t0.saturating_duration_since(t1), Duration::ZERO);
/// ```
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Instant(u64);

impl Instant {
    /// The instant `us` microseconds after the clock's origin.
    #[must_use]
    pub const fn from_micros(us: u64) -> Self {
        Self(us)
    }

    /// Microseconds since the clock's origin.
    #[must_use]
    pub const fn as_micros(self) -> u64 {
        self.0
    }

    /// The instant `d` later, or `None` past the clock's range.  Below a
    /// microsecond, `d` is truncated.
    #[must_use]
    pub fn checked_add(self, d: Duration) -> Option<Self> {
        let us = u64::try_from(d.as_micros()).ok()?;
        self.0.checked_add(us).map(Self)
    }

    /// The instant `d` later, or the clock's last instant.  For a deadline
    /// that must never come out earlier than asked.
    #[must_use]
    pub fn saturating_add(self, d: Duration) -> Self {
        self.checked_add(d).unwrap_or(Self(u64::MAX))
    }

    /// How long after `earlier` this is, or zero if it is not later.
    #[must_use]
    pub const fn saturating_duration_since(self, earlier: Self) -> Duration {
        Duration::from_micros(self.0.saturating_sub(earlier.0))
    }
}

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

    #[test]
    fn adding_stays_in_range() {
        let end = Instant::from_micros(u64::MAX - 1);
        assert_eq!(end.checked_add(Duration::from_micros(2)), None);
        assert_eq!(
            end.saturating_add(Duration::from_micros(2)),
            Instant::from_micros(u64::MAX)
        );
        assert_eq!(Instant::from_micros(0).checked_add(Duration::MAX), None);
    }

    #[test]
    fn sub_microsecond_is_truncated() {
        let t = Instant::from_micros(10);
        assert_eq!(t.checked_add(Duration::from_nanos(999)), Some(t));
        assert_eq!(
            t.checked_add(Duration::from_nanos(1_500)),
            Some(Instant::from_micros(11))
        );
    }

    #[test]
    fn instants_order_as_their_micros() {
        assert!(Instant::from_micros(1) < Instant::from_micros(2));
        assert_eq!(
            Instant::from_micros(7).saturating_duration_since(Instant::from_micros(3)),
            Duration::from_micros(4)
        );
    }
}