//! `Timespec` and related types, which are used by multiple public API
//! modules.

#![allow(dead_code)]

use core::num::TryFromIntError;
use core::ops::{Add, AddAssign, Neg, Sub, SubAssign};
use core::time::Duration;

use crate::backend::c;
#[allow(unused)]
use crate::ffi;
#[cfg(not(fix_y2038))]
use core::ptr::null;

/// `struct timespec`—A quantity of time in seconds plus nanoseconds.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
#[repr(C)]
pub struct Timespec {
    /// Seconds.
    pub tv_sec: Secs,

    /// Nanoseconds. Must be less than 1_000_000_000.
    ///
    /// When passed to [`rustix::fs::utimensat`], this field may instead be
    /// assigned the values [`UTIME_NOW`] or [`UTIME_OMIT`].
    ///
    /// [`UTIME_NOW`]: crate::fs::UTIME_NOW
    /// [`UTIME_OMIT`]: crate::fs::UTIME_OMIT
    /// [`rustix::fs::utimensat`]: crate::fs::utimensat
    pub tv_nsec: Nsecs,
}

/// A type for the `tv_sec` field of [`Timespec`].
pub type Secs = i64;

/// A type for the `tv_nsec` field of [`Timespec`].
#[cfg(any(
    fix_y2038,
    linux_raw,
    all(libc, target_arch = "x86_64", target_pointer_width = "32")
))]
pub type Nsecs = i64;

/// A type for the `tv_nsec` field of [`Timespec`].
#[cfg(all(
    not(fix_y2038),
    libc,
    not(all(target_arch = "x86_64", target_pointer_width = "32"))
))]
pub type Nsecs = ffi::c_long;

impl Timespec {
    /// Checked `Timespec` addition. Returns `None` if overflow occurred.
    ///
    /// # Panics
    ///
    /// If `0 <= .tv_nsec < 1_000_000_000` doesn't hold, this function may
    /// panic or return unexpected results.
    ///
    /// # Example
    ///
    /// ```
    /// use rustix::event::Timespec;
    ///
    /// assert_eq!(
    ///     Timespec {
    ///         tv_sec: 1,
    ///         tv_nsec: 2
    ///     }
    ///     .checked_add(Timespec {
    ///         tv_sec: 30,
    ///         tv_nsec: 40
    ///     }),
    ///     Some(Timespec {
    ///         tv_sec: 31,
    ///         tv_nsec: 42
    ///     })
    /// );
    /// assert_eq!(
    ///     Timespec {
    ///         tv_sec: 0,
    ///         tv_nsec: 999_999_999
    ///     }
    ///     .checked_add(Timespec {
    ///         tv_sec: 0,
    ///         tv_nsec: 2
    ///     }),
    ///     Some(Timespec {
    ///         tv_sec: 1,
    ///         tv_nsec: 1
    ///     })
    /// );
    /// assert_eq!(
    ///     Timespec {
    ///         tv_sec: i64::MAX,
    ///         tv_nsec: 999_999_999
    ///     }
    ///     .checked_add(Timespec {
    ///         tv_sec: 0,
    ///         tv_nsec: 1
    ///     }),
    ///     None
    /// );
    /// ```
    pub const fn checked_add(self, rhs: Self) -> Option<Self> {
        if let Some(mut tv_sec) = self.tv_sec.checked_add(rhs.tv_sec) {
            let mut tv_nsec = self.tv_nsec + rhs.tv_nsec;
            if tv_nsec >= 1_000_000_000 {
                tv_nsec -= 1_000_000_000;
                if let Some(carried_sec) = tv_sec.checked_add(1) {
                    tv_sec = carried_sec;
                } else {
                    return None;
                }
            }
            Some(Self { tv_sec, tv_nsec })
        } else {
            None
        }
    }

    /// Checked `Timespec` subtraction. Returns `None` if overflow occurred.
    ///
    /// # Panics
    ///
    /// If `0 <= .tv_nsec < 1_000_000_000` doesn't hold, this function may
    /// panic or return unexpected results.
    ///
    /// # Example
    ///
    /// ```
    /// use rustix::event::Timespec;
    ///
    /// assert_eq!(
    ///     Timespec {
    ///         tv_sec: 31,
    ///         tv_nsec: 42
    ///     }
    ///     .checked_sub(Timespec {
    ///         tv_sec: 30,
    ///         tv_nsec: 40
    ///     }),
    ///     Some(Timespec {
    ///         tv_sec: 1,
    ///         tv_nsec: 2
    ///     })
    /// );
    /// assert_eq!(
    ///     Timespec {
    ///         tv_sec: 1,
    ///         tv_nsec: 1
    ///     }
    ///     .checked_sub(Timespec {
    ///         tv_sec: 0,
    ///         tv_nsec: 2
    ///     }),
    ///     Some(Timespec {
    ///         tv_sec: 0,
    ///         tv_nsec: 999_999_999
    ///     })
    /// );
    /// assert_eq!(
    ///     Timespec {
    ///         tv_sec: i64::MIN,
    ///         tv_nsec: 0
    ///     }
    ///     .checked_sub(Timespec {
    ///         tv_sec: 0,
    ///         tv_nsec: 1
    ///     }),
    ///     None
    /// );
    /// ```
    pub const fn checked_sub(self, rhs: Self) -> Option<Self> {
        if let Some(mut tv_sec) = self.tv_sec.checked_sub(rhs.tv_sec) {
            let mut tv_nsec = self.tv_nsec - rhs.tv_nsec;
            if tv_nsec < 0 {
                tv_nsec += 1_000_000_000;
                if let Some(borrowed_sec) = tv_sec.checked_sub(1) {
                    tv_sec = borrowed_sec;
                } else {
                    return None;
                }
            }
            Some(Self { tv_sec, tv_nsec })
        } else {
            None
        }
    }

    /// Convert from `Timespec` to `c::c_int` milliseconds, rounded up.
    pub(crate) fn as_c_int_millis(&self) -> Option<c::c_int> {
        let secs = self.tv_sec;
        if secs < 0 {
            return None;
        }
        secs.checked_mul(1000)
            .and_then(|millis| {
                // Add the nanoseconds, converted to milliseconds, rounding up.
                // With Rust 1.73.0 this can use `div_ceil`.
                millis.checked_add((i64::from(self.tv_nsec) + 999_999) / 1_000_000)
            })
            .and_then(|millis| c::c_int::try_from(millis).ok())
    }
}

impl TryFrom<Timespec> for Duration {
    type Error = TryFromIntError;

    fn try_from(ts: Timespec) -> Result<Self, Self::Error> {
        Ok(Self::new(ts.tv_sec.try_into()?, ts.tv_nsec as _))
    }
}

impl TryFrom<Duration> for Timespec {
    type Error = TryFromIntError;

    fn try_from(dur: Duration) -> Result<Self, Self::Error> {
        Ok(Self {
            tv_sec: dur.as_secs().try_into()?,
            tv_nsec: dur.subsec_nanos() as _,
        })
    }
}

impl Add for Timespec {
    type Output = Self;

    fn add(self, rhs: Self) -> Self {
        self.checked_add(rhs)
            .expect("overflow when adding timespecs")
    }
}

impl AddAssign for Timespec {
    fn add_assign(&mut self, rhs: Self) {
        *self = *self + rhs;
    }
}

impl Sub for Timespec {
    type Output = Self;

    fn sub(self, rhs: Self) -> Self {
        self.checked_sub(rhs)
            .expect("overflow when subtracting timespecs")
    }
}

impl SubAssign for Timespec {
    fn sub_assign(&mut self, rhs: Self) {
        *self = *self - rhs;
    }
}

impl Neg for Timespec {
    type Output = Self;

    fn neg(self) -> Self {
        Self::default() - self
    }
}

/// On 32-bit glibc platforms, `timespec` has anonymous padding fields, which
/// Rust doesn't support yet (see `unnamed_fields`), so we define our own
/// struct with explicit padding, with bidirectional `From` impls.
#[cfg(fix_y2038)]
#[repr(C)]
#[derive(Debug, Clone)]
pub(crate) struct LibcTimespec {
    pub(crate) tv_sec: Secs,

    #[cfg(target_endian = "big")]
    padding: core::mem::MaybeUninit<u32>,

    pub(crate) tv_nsec: i32,

    #[cfg(target_endian = "little")]
    padding: core::mem::MaybeUninit<u32>,
}

#[cfg(fix_y2038)]
impl From<LibcTimespec> for Timespec {
    #[inline]
    fn from(t: LibcTimespec) -> Self {
        Self {
            tv_sec: t.tv_sec,
            tv_nsec: t.tv_nsec as _,
        }
    }
}

#[cfg(fix_y2038)]
impl From<Timespec> for LibcTimespec {
    #[inline]
    fn from(t: Timespec) -> Self {
        Self {
            tv_sec: t.tv_sec,
            tv_nsec: t.tv_nsec as _,
            padding: core::mem::MaybeUninit::uninit(),
        }
    }
}

#[cfg(not(fix_y2038))]
pub(crate) fn as_libc_timespec_ptr(timespec: &Timespec) -> *const c::timespec {
    #[cfg(test)]
    {
        assert_eq_size!(Timespec, c::timespec);
    }
    crate::utils::as_ptr(timespec).cast::<c::timespec>()
}

#[cfg(not(fix_y2038))]
pub(crate) fn as_libc_timespec_mut_ptr(
    timespec: &mut core::mem::MaybeUninit<Timespec>,
) -> *mut c::timespec {
    #[cfg(test)]
    {
        assert_eq_size!(Timespec, c::timespec);
    }
    timespec.as_mut_ptr().cast::<c::timespec>()
}

#[cfg(not(fix_y2038))]
pub(crate) fn option_as_libc_timespec_ptr(timespec: Option<&Timespec>) -> *const c::timespec {
    match timespec {
        None => null(),
        Some(timespec) => as_libc_timespec_ptr(timespec),
    }
}

/// As described [here], Apple platforms may return a negative nanoseconds
/// value in some cases; adjust it so that nanoseconds is always in
/// `0..1_000_000_000`.
///
/// [here]: https://github.com/rust-lang/rust/issues/108277#issuecomment-1787057158
#[cfg(apple)]
#[inline]
pub(crate) fn fix_negative_nsecs(
    mut secs: c::time_t,
    mut nsecs: c::c_long,
) -> (c::time_t, c::c_long) {
    #[cold]
    fn adjust(secs: &mut c::time_t, nsecs: c::c_long) -> c::c_long {
        assert!(nsecs >= -1_000_000_000);
        assert!(*secs < 0);
        assert!(*secs > c::time_t::MIN);
        *secs -= 1;
        nsecs + 1_000_000_000
    }

    if nsecs < 0 {
        nsecs = adjust(&mut secs, nsecs);
    }
    (secs, nsecs)
}

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

    #[cfg(apple)]
    #[test]
    fn test_negative_timestamps() {
        let mut secs = -59;
        let mut nsecs = -900_000_000;
        (secs, nsecs) = fix_negative_nsecs(secs, nsecs);
        assert_eq!(secs, -60);
        assert_eq!(nsecs, 100_000_000);
        (secs, nsecs) = fix_negative_nsecs(secs, nsecs);
        assert_eq!(secs, -60);
        assert_eq!(nsecs, 100_000_000);
    }

    #[test]
    fn test_sizes() {
        assert_eq_size!(Secs, u64);
        const_assert!(core::mem::size_of::<Timespec>() >= core::mem::size_of::<(u64, u32)>());
        const_assert!(core::mem::size_of::<Nsecs>() >= 4);

        let mut t = Timespec {
            tv_sec: 0,
            tv_nsec: 0,
        };

        // `tv_nsec` needs to be able to hold nanoseconds up to a second.
        t.tv_nsec = 999_999_999_u32 as _;
        assert_eq!(t.tv_nsec as u64, 999_999_999_u64);

        // `tv_sec` needs to be able to hold more than 32-bits of seconds.
        t.tv_sec = 0x1_0000_0000_u64 as _;
        assert_eq!(t.tv_sec as u64, 0x1_0000_0000_u64);
    }

    // Test that our workarounds are needed.
    #[cfg(fix_y2038)]
    #[test]
    #[allow(deprecated)]
    fn test_fix_y2038() {
        assert_eq_size!(libc::time_t, u32);
    }

    // Test that our workarounds are not needed.
    #[cfg(not(fix_y2038))]
    #[test]
    fn timespec_layouts() {
        use crate::backend::c;
        check_renamed_struct!(Timespec, timespec, tv_sec, tv_nsec);
    }

    // Test that `Timespec` matches Linux's `__kernel_timespec`.
    #[cfg(linux_raw_dep)]
    #[test]
    fn test_against_kernel_timespec() {
        assert_eq_size!(Timespec, linux_raw_sys::general::__kernel_timespec);
        assert_eq_align!(Timespec, linux_raw_sys::general::__kernel_timespec);
        assert_eq!(
            memoffset::span_of!(Timespec, tv_sec),
            memoffset::span_of!(linux_raw_sys::general::__kernel_timespec, tv_sec)
        );
        assert_eq!(
            memoffset::span_of!(Timespec, tv_nsec),
            memoffset::span_of!(linux_raw_sys::general::__kernel_timespec, tv_nsec)
        );
    }
}
