use std::time::Duration;
use bacnet_types::calendar::SpecificDate;
use crate::clock::ClockFrame;
pub(super) const DAY: u32 = 8_640_000;
const JUMP: i64 = 100;
#[derive(Debug, Default)]
pub(super) struct Alignment {
served: Option<i64>,
seen: Option<(Duration, i64)>,
}
impl Alignment {
pub(super) fn due(
&mut self,
now: Duration,
frame: &ClockFrame,
interval: u32,
offset: u32,
) -> Option<i64> {
let local = local_hundredths(frame)?;
let since = local - i64::from(offset);
let boundary = since.div_euclid(i64::from(interval));
let jumped = self.seen.is_some_and(|(then, before)| {
let elapsed =
i64::try_from(now.saturating_sub(then).as_millis() / 10).unwrap_or(i64::MAX);
(local - before.saturating_add(elapsed)).abs() > JUMP
});
self.seen = Some((now, local));
match self.served {
Some(served) if !jumped => (boundary > served).then_some(boundary),
_ => {
let on_boundary = since.rem_euclid(i64::from(interval)) == 0;
self.served = Some(boundary - i64::from(on_boundary));
on_boundary.then_some(boundary)
}
}
}
pub(super) fn serve(&mut self, boundary: i64) {
self.served = Some(boundary);
}
pub(super) fn wait(&self, now: Duration, interval: u32, offset: u32) -> Duration {
let (Some(served), Some((then, local))) = (self.served, self.seen) else {
return Duration::ZERO;
};
let next = (served + 1) * i64::from(interval) + i64::from(offset);
let left = Duration::from_millis(u64::try_from(next - local).unwrap_or(0) * 10)
.saturating_sub(now.saturating_sub(then));
left.max(Duration::from_millis(10))
}
}
fn local_hundredths(frame: &ClockFrame) -> Option<i64> {
let day = SpecificDate::from_date(&frame.local_date)?;
let t = frame.local_time;
let time_of_day = ((i64::from(t.hour) * 60 + i64::from(t.minute)) * 60 + i64::from(t.second))
* 100
+ i64::from(t.hundredths);
Some(day.days_since_1970() * i64::from(DAY) + time_of_day)
}