//! Frame timing, modeled on XNA's `GameTime`.

use std::time::{Duration, Instant};

/// A snapshot of timing for the current frame.
///
/// Mirrors XNA's `GameTime`: how long since the last frame (`delta`) and how
/// long since the game started (`total`). Passed by value into `update`.
#[derive(Debug, Clone, Copy)]
pub struct GameTime {
    /// Time since the game started.
    pub total: Duration,
    /// Time since the previous frame.
    pub delta: Duration,
}

impl GameTime {
    /// Delta time in seconds, the form you almost always want for integration.
    #[inline]
    pub fn delta_seconds(&self) -> f32 {
        self.delta.as_secs_f32()
    }

    /// Total elapsed time in seconds.
    #[inline]
    pub fn total_seconds(&self) -> f64 {
        self.total.as_secs_f64()
    }
}

/// Produces a [`GameTime`] once per frame. Call [`Clock::tick`] at the top of
/// each loop iteration.
#[derive(Debug)]
pub struct Clock {
    start: Instant,
    last: Instant,
}

impl Clock {
    pub fn new() -> Self {
        let now = Instant::now();
        Self {
            start: now,
            last: now,
        }
    }

    /// Advance the clock and return the timing for the frame that is starting.
    pub fn tick(&mut self) -> GameTime {
        let now = Instant::now();
        let delta = now.duration_since(self.last);
        self.last = now;
        GameTime {
            total: now.duration_since(self.start),
            delta,
        }
    }
}

impl Default for Clock {
    fn default() -> Self {
        Self::new()
    }
}

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

    #[test]
    fn delta_seconds_is_non_negative() {
        let mut clock = Clock::new();
        let t = clock.tick();
        assert!(t.delta_seconds() >= 0.0);
        assert!(t.total_seconds() >= 0.0);
    }
}
