use core::sync::atomic::{AtomicU64, Ordering};

use yggdrasil_abi::time::SystemTime;

// Millisecond resolution
const NANOSECONDS_IN_SECOND: u64 = 1_000_000_000;

// Monotonic and real times are running in nanoseconds
static MONOTONIC_TIME: AtomicU64 = AtomicU64::new(0);
static REAL_TIME_OFFSET: AtomicU64 = AtomicU64::new(0);

// Real time is running in milliseconds
static REAL_TIME: AtomicU64 = AtomicU64::new(0);

#[inline]
fn make_time(ticks: u64) -> SystemTime {
    unsafe {
        SystemTime::new_unchecked(ticks / NANOSECONDS_IN_SECOND, ticks % NANOSECONDS_IN_SECOND)
    }
}

#[inline]
pub fn real_time() -> SystemTime {
    let real_ms = REAL_TIME.load(Ordering::Acquire);
    let off_ns = REAL_TIME_OFFSET.load(Ordering::Relaxed);

    let mut nanoseconds = off_ns % NANOSECONDS_IN_SECOND + (real_ms % 1000) * 1000000;
    let mut seconds = off_ns / NANOSECONDS_IN_SECOND + real_ms / 1000;
    if nanoseconds >= NANOSECONDS_IN_SECOND {
        seconds = seconds.saturating_add(1);
        nanoseconds -= NANOSECONDS_IN_SECOND;
        debug_assert!(nanoseconds < NANOSECONDS_IN_SECOND);
    }

    unsafe { SystemTime::new_unchecked(seconds, nanoseconds) }
}

#[inline]
pub fn monotonic_time() -> SystemTime {
    make_time(MONOTONIC_TIME.load(Ordering::Acquire))
}

#[inline]
pub fn set_real_seconds(s: u64, reset: bool) {
    REAL_TIME.store(s * 1000, Ordering::Release);
    if reset {
        REAL_TIME_OFFSET.store(0, Ordering::Release);
    }
}

#[inline]
pub fn add_nanoseconds(ns: u64) {
    MONOTONIC_TIME.fetch_add(ns, Ordering::Release);
    REAL_TIME_OFFSET.fetch_add(ns, Ordering::Release);
}

#[inline]
pub fn add_milliseconds(ms: u64) {
    add_nanoseconds(ms * 1000000);
}

#[inline]
pub fn set_nanoseconds(ns: u64) {
    MONOTONIC_TIME.store(ns, Ordering::Release);
    REAL_TIME_OFFSET.store(ns, Ordering::Release);
}

#[inline]
pub fn set_milliseconds(ms: u64) {
    set_nanoseconds(ms * 1000000);
}
