use core::ops::*;
use idem::Zero;
use time;

pub struct Simulator<State> {
    tick: time::Span,
    then: time::Point,
    cumul: time::Span,
    total: time::Span,
    prior_state: State,
}

impl<State: Clone> Simulator<State> {
    pub fn new(tick: time::Span, state: State) -> Self { Simulator {
        tick: tick,
        then: time::Point::now(),
        cumul: Zero::zero,
        total: Zero::zero,
        prior_state: state,
    } }

    pub fn go<A, Step, Interpolate>(&mut self, state: &mut State,
                                    step: Step, interpolate: Interpolate) -> A
      where Step: Fn(&mut State),
            Interpolate: Fn(f64, &State, &State) -> A {
        {
            let now = time::Point::now();
            let elapsed = now - self.then;
            debug_assert!(elapsed >= Zero::zero);
            self.then = now;
            self.cumul += elapsed;
            self.total += elapsed;
        }
        while self.cumul > Zero::zero {
            self.cumul -= self.tick;
            if self.cumul < Zero::zero {
                self.prior_state.clone_from(state);
            }
            step(state);
        }
        interpolate((self.cumul.to_ns() as f64 / self.tick.to_ns() as f64).neg(),
                    &state, &self.prior_state)
    }
}