use std::{
iter::{Peekable, StepBy},
ops::RangeFrom,
};
use crate::utils::timestamp;
pub(crate) type SecondsState = Peekable<StepBy<RangeFrom<u64>>>;
const ONE_MINUTE: u64 = 60;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FrequencySeconds {
Once(u64),
Repeated(u64),
CountDown(u64, u64),
}
impl FrequencySeconds {
pub(crate) fn interval(&self) -> u64 {
match self {
Self::Once(seconds) => *seconds,
Self::Repeated(seconds) => *seconds,
Self::CountDown(_, seconds) => *seconds,
}
}
}
impl Default for FrequencySeconds {
fn default() -> FrequencySeconds {
FrequencySeconds::Once(ONE_MINUTE)
}
}
#[derive(Clone)]
#[allow(dead_code)]
pub(crate) enum FrequencyState {
SecondsRepeated(SecondsState),
SecondsCountDown(u64, SecondsState),
}
impl From<FrequencySeconds> for FrequencyState {
fn from(frequency: FrequencySeconds) -> Self {
match frequency {
FrequencySeconds::Once(seconds) => {
assert!(seconds > 0, "once frequency must be greater than 0");
let state: SecondsState = ((timestamp() + seconds)..)
.step_by(seconds as usize)
.peekable();
FrequencyState::SecondsRepeated(state)
}
FrequencySeconds::Repeated(seconds) => {
assert!(seconds > 0, "repeated frequency must be greater than 0");
let state: SecondsState = ((timestamp() + seconds)..)
.step_by(seconds as usize)
.peekable();
FrequencyState::SecondsRepeated(state)
}
FrequencySeconds::CountDown(count_down, seconds) => {
assert!(seconds > 0, "countdown initial must be greater than 0");
let state: SecondsState = (timestamp() + seconds..)
.step_by(count_down as usize)
.peekable();
FrequencyState::SecondsCountDown(count_down, state)
}
}
}
}
impl FrequencyState {
#[allow(dead_code)]
pub(crate) fn peek_alarm_timestamp(&mut self) -> Option<u64> {
match self {
Self::SecondsRepeated(state) => state.peek().copied(),
Self::SecondsCountDown(_, state) => state.peek().copied(),
}
}
pub(crate) fn next_alarm_timestamp(&mut self) -> Option<u64> {
match self {
Self::SecondsRepeated(state) => state.next(),
Self::SecondsCountDown(_, state) => state.next(),
}
}
#[allow(dead_code)]
pub(crate) fn down_count(&mut self) {
if let Self::SecondsCountDown(count, _) = self {
*count = count.saturating_sub(1);
}
}
pub(crate) fn reset_from_timestamp(&mut self, base_timestamp: u64, interval: u64) {
let new_state: SecondsState = ((base_timestamp + interval)..)
.step_by(interval as usize)
.peekable();
match self {
Self::SecondsRepeated(_) => {
*self = Self::SecondsRepeated(new_state);
}
Self::SecondsCountDown(count, _) => {
*self = Self::SecondsCountDown(*count, new_state);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_frequency_state_from_once() {
let freq = FrequencySeconds::Once(10);
let mut state = FrequencyState::from(freq);
let now = crate::utils::timestamp();
let alarm = state.peek_alarm_timestamp().unwrap();
assert!(alarm >= now + 10);
let alarm2 = state.peek_alarm_timestamp().unwrap();
assert_eq!(alarm, alarm2);
let alarm3 = state.next_alarm_timestamp().unwrap();
assert_eq!(alarm, alarm3);
}
#[test]
fn test_frequency_state_from_repeated() {
let freq = FrequencySeconds::Repeated(5);
let mut state = FrequencyState::from(freq);
let now = crate::utils::timestamp();
let alarm1 = state.next_alarm_timestamp().unwrap();
assert_eq!(alarm1, now + 5);
let alarm2 = state.next_alarm_timestamp().unwrap();
assert_eq!(alarm2, now + 10);
let alarm3 = state.next_alarm_timestamp().unwrap();
assert_eq!(alarm3, now + 15);
}
#[test]
fn test_frequency_state_from_countdown() {
let freq = FrequencySeconds::CountDown(2, 5); let state = FrequencyState::from(freq);
match state {
FrequencyState::SecondsCountDown(count, _) => assert_eq!(count, 2),
_ => panic!("Expected SecondsCountDown variant"),
}
}
#[test]
fn test_peek_alarm_timestamp() {
let freq = FrequencySeconds::Repeated(10);
let mut state = FrequencyState::from(freq);
let peek1 = state.peek_alarm_timestamp().unwrap();
let peek2 = state.peek_alarm_timestamp().unwrap();
assert_eq!(peek1, peek2);
let next1 = state.next_alarm_timestamp().unwrap();
assert_eq!(peek1, next1);
let peek3 = state.peek_alarm_timestamp().unwrap();
assert_ne!(peek1, peek3);
}
#[test]
fn test_reset_from_timestamp_repeated() {
let freq = FrequencySeconds::Repeated(10);
let mut state = FrequencyState::from(freq);
let _ = state.next_alarm_timestamp().unwrap();
let _ = state.next_alarm_timestamp().unwrap();
let reset_base = 1000;
state.reset_from_timestamp(reset_base, 10);
let next = state.peek_alarm_timestamp().unwrap();
assert_eq!(next, reset_base + 10);
let next2 = state.next_alarm_timestamp().unwrap();
assert_eq!(next2, reset_base + 10);
let next3 = state.next_alarm_timestamp().unwrap();
assert_eq!(next3, reset_base + 20);
}
#[test]
fn test_reset_from_timestamp_countdown() {
let freq = FrequencySeconds::CountDown(5, 10);
let mut state = FrequencyState::from(freq);
let reset_base = 2000;
state.reset_from_timestamp(reset_base, 10);
let next = state.peek_alarm_timestamp().unwrap();
assert_eq!(next, reset_base + 10);
match state {
FrequencyState::SecondsCountDown(count, _) => assert_eq!(count, 5),
_ => panic!("Expected SecondsCountDown variant"),
}
}
#[test]
fn test_frequency_seconds_interval() {
assert_eq!(FrequencySeconds::Once(30).interval(), 30);
assert_eq!(FrequencySeconds::Repeated(60).interval(), 60);
assert_eq!(FrequencySeconds::CountDown(3, 15).interval(), 15);
}
}