use tokio::time::{sleep, Duration, Instant};
enum Mode {
    Normal,
    Rushed,
}
pub struct Ticker {
    last_reset: Instant,
    mode: Mode,
    max_timeout: Duration,
    min_timeout: Duration,
}
impl Ticker {
    pub fn new(mut max_timeout: Duration, min_timeout: Duration) -> Self {
        if max_timeout < min_timeout {
            max_timeout = min_timeout;
        };
        Self {
            last_reset: Instant::now(),
            mode: Mode::Normal,
            max_timeout,
            min_timeout,
        }
    }
    pub fn try_tick(&mut self) -> bool {
        let now = Instant::now();
        if now.saturating_duration_since(self.last_reset) >= self.min_timeout {
            self.mode = Mode::Normal;
            true
        } else {
            self.mode = Mode::Rushed;
            false
        }
    }
    pub async fn wait_and_tick(&mut self) -> bool {
        self.wait_current_timeout().await;
        match self.since_reset() > self.max_timeout {
            true => {
                self.reset();
                true
            }
            false => {
                self.mode = Mode::Normal;
                false
            }
        }
    }
    pub fn reset(&mut self) {
        self.last_reset = Instant::now();
        self.mode = Mode::Normal;
    }
    fn since_reset(&self) -> Duration {
        Instant::now().saturating_duration_since(self.last_reset)
    }
    async fn wait_current_timeout(&self) {
        let sleep_time = match self.mode {
            Mode::Normal => self.max_timeout,
            Mode::Rushed => self.min_timeout,
        }
        .saturating_sub(self.since_reset());
        sleep(sleep_time).await;
    }
}
#[cfg(test)]
mod tests {
    use tokio::time::{sleep, timeout, Duration};
    use super::Ticker;
    const MAX_TIMEOUT: Duration = Duration::from_millis(700);
    const MIN_TIMEOUT: Duration = Duration::from_millis(100);
    const MAX_TIMEOUT_PLUS: Duration = Duration::from_millis(800);
    const MIN_TIMEOUT_PLUS: Duration = Duration::from_millis(200);
    fn setup_ticker() -> Ticker {
        Ticker::new(MAX_TIMEOUT, MIN_TIMEOUT)
    }
    #[tokio::test]
    async fn try_tick() {
        let mut ticker = setup_ticker();
        assert!(!ticker.try_tick());
        sleep(MIN_TIMEOUT_PLUS).await;
        assert!(ticker.try_tick());
        assert!(ticker.try_tick());
    }
    #[tokio::test]
    async fn plain_wait() {
        let mut ticker = setup_ticker();
        assert!(timeout(MIN_TIMEOUT_PLUS, ticker.wait_and_tick()).await.is_err());
        assert_eq!(timeout(MAX_TIMEOUT, ticker.wait_and_tick()).await, Ok(true));
        assert!(
            timeout(MIN_TIMEOUT_PLUS, ticker.wait_and_tick()).await.is_err());
    }
    #[tokio::test]
    async fn wait_after_try_tick_true() {
        let mut ticker = setup_ticker();
        assert!(!ticker.try_tick());
        sleep(MIN_TIMEOUT).await;
        assert!(ticker.try_tick());
        assert!(timeout(MIN_TIMEOUT_PLUS, ticker.wait_and_tick()).await.is_err());
        assert_eq!(timeout(MAX_TIMEOUT, ticker.wait_and_tick()).await, Ok(true));
    }
    #[tokio::test]
    async fn wait_after_try_tick_false() {
        let mut ticker = setup_ticker();
        assert!(!ticker.try_tick());
        assert_eq!(
            timeout(MIN_TIMEOUT_PLUS, ticker.wait_and_tick()).await,
            Ok(false)
        );
        assert!(timeout(MIN_TIMEOUT_PLUS, ticker.wait_and_tick()).await.is_err());
        assert_eq!(timeout(MAX_TIMEOUT, ticker.wait_and_tick()).await, Ok(true));
    }
    #[tokio::test]
    async fn try_tick_after_wait() {
        let mut ticker = setup_ticker();
        assert_eq!(
            timeout(MAX_TIMEOUT_PLUS, ticker.wait_and_tick()).await,
            Ok(true)
        );
        assert!(!ticker.try_tick());
    }
    #[tokio::test]
    async fn wait_after_late_reset() {
        let mut ticker = setup_ticker();
        assert_eq!(
            timeout(MAX_TIMEOUT_PLUS, ticker.wait_and_tick()).await,
            Ok(true)
        );
        ticker.reset();
        assert!(timeout(MIN_TIMEOUT_PLUS, ticker.wait_and_tick()).await.is_err());
        assert_eq!(
            timeout(MAX_TIMEOUT_PLUS, ticker.wait_and_tick()).await,
            Ok(true)
        );
    }
    #[tokio::test]
    async fn wait_after_early_reset() {
        let mut ticker = setup_ticker();
        sleep(MIN_TIMEOUT).await;
        assert!(ticker.try_tick());
        ticker.reset();
        assert!(timeout(MIN_TIMEOUT_PLUS, ticker.wait_and_tick()).await.is_err());
        assert_eq!(
            timeout(MAX_TIMEOUT_PLUS, ticker.wait_and_tick()).await,
            Ok(true)
        );
    }
    #[tokio::test]
    async fn try_tick_after_reset() {
        let mut ticker = setup_ticker();
        assert_eq!(
            timeout(MAX_TIMEOUT_PLUS, ticker.wait_and_tick()).await,
            Ok(true)
        );
        ticker.reset();
        assert!(!ticker.try_tick());
        sleep(MIN_TIMEOUT).await;
        assert!(ticker.try_tick());
    }
}