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

pub struct Metronome {
    taps: Vec<Instant>,
    beat_interval: Option<u128>,
    timeout: Duration,
}

impl Metronome {
    pub fn new(timeout: Duration) -> Self {
        Self {
            taps: Vec::new(),
            beat_interval: None,
            timeout,
        }
    }

    pub fn tap(&mut self) {
        let now = Instant::now();
        if let Some(t) = self.taps.last() {
            if now - *t > self.timeout {
                self.taps.clear();
                self.beat_interval = None;
            }
        }

        self.taps.push(now);

        let n = self.taps.len();
        if n >= 2 {
            let dt = *self.taps.last().unwrap() - *self.taps.first().unwrap();
            let interval_millis = dt.as_millis() / (n - 1) as u128;
            self.beat_interval = Some(interval_millis);
        }
    }

    pub fn get_beat_progress(&self, now: Instant) -> Option<f64> {
        if self.beat_interval.is_none() {
            return None;
        }

        let relative_millis = (now - *self.taps.last().unwrap()).as_millis();

        Some(relative_millis as f64 / self.beat_interval.unwrap() as f64)
    }

    pub fn get_beats(&self, from: Instant, to: Instant) -> Vec<(u64, Instant)> {
        if self.beat_interval.is_none() {
            return Vec::new();
        }

        let beat_interval = self.beat_interval.unwrap();

        let last = *self.taps.last().unwrap();

        let relative_from = (from - last).as_millis() as f64 / beat_interval as f64;
        let relative_to = (to - last).as_millis() as f64 / beat_interval as f64;

        (relative_from.ceil() as u64..=relative_to.floor() as u64)
            .map(|beat_number| {
                (
                    beat_number,
                    last + Duration::from_millis((beat_number as u128 * beat_interval) as u64),
                )
            })
            .collect()
    }
}
