Documentation
use std::time::Duration;

use tokio::time::{
	Instant,
	sleep_until
};
use tracing::{
	debug_span,
	Span,
	Instrument
};

pub struct Sleeper {
	span: Span,
	pushed: Duration,
	until: Option<Instant>
}

impl Sleeper {

	pub fn start() -> Self {
		Self {
			span: debug_span!("sleep"),
			pushed: Duration::ZERO,
			until: None
		}
	}

	pub fn push(&mut self, amount: Duration) {
		match Instant::now().checked_add(amount) {
			Some(until) => {
				self.pushed = amount;
				self.until = Some(until);
				debug!(@self, "next tick in {amount:?}");
			},
			None => {
				self.pushed = Duration::ZERO;
				self.until = None;
				warn!(@self, "Instant overflowed, not pushed!");
			}
		}
	}

	pub async fn sleep(&mut self) -> Option<()> {
		sleep_until(self.until?).in_current_span().await;
		self.until.take();
		debug!(@self, "slept for {:?}", self.pushed);
		self.pushed = Duration::ZERO;
		Some(())
	}
}

#[test_log::test(tokio::test(start_paused = true))]
async fn sleep() {
	use futures::FutureExt;
	const PUSH: Duration = Duration::from_secs(3);

	let mut sleeper = Sleeper::start();

	let not_pushed = sleeper.sleep().now_or_never();
	assert_eq!(
		not_pushed,
		Some(None),
		"not pushed, should be ready immediately"
	);

	let before = Instant::now();
	sleeper.push(PUSH);
	sleeper.sleep().await.unwrap();
	let elapsed = Instant::now() - before;
	assert!(elapsed >= PUSH, "should sleep for at least {PUSH:?}");

	let cleared = sleeper.sleep().now_or_never();
	assert_eq!(
		cleared,
		Some(None),
		"cleared, should be ready immediately"
	);

	// Overflow the Instant
	sleeper.push(Duration::MAX);
	let cleared = sleeper.sleep().now_or_never();
	assert_eq!(
		cleared,
		Some(None),
		"cleared because of overflow, should be ready immediately"
	);
}