use serde::{Deserialize, Serialize};
use std::fmt;
use std::time::{Duration, Instant};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum BrainWave {
Gamma,
Beta,
Alpha,
Theta,
Delta,
}
impl BrainWave {
#[must_use]
pub const fn allows_tools(self) -> bool {
matches!(self, Self::Gamma | Self::Beta | Self::Alpha)
}
#[must_use]
pub const fn allows_consolidation(self) -> bool {
matches!(self, Self::Theta)
}
#[must_use]
pub const fn is_dormant(self) -> bool {
matches!(self, Self::Delta)
}
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::Gamma => "Gamma (active)",
Self::Beta => "Beta (working)",
Self::Alpha => "Alpha (idle)",
Self::Theta => "Theta (drowsy)",
Self::Delta => "Delta (dormant)",
}
}
}
impl fmt::Display for BrainWave {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BrainWaveConfig {
pub gamma_rate: f64,
pub alpha_idle: Duration,
pub theta_idle: Duration,
pub delta_idle: Duration,
}
impl Default for BrainWaveConfig {
fn default() -> Self {
Self {
gamma_rate: 10.0,
alpha_idle: Duration::from_secs(30),
theta_idle: Duration::from_secs(300),
delta_idle: Duration::from_secs(1800),
}
}
}
impl BrainWaveConfig {
#[must_use]
pub fn from_env() -> Self {
let defaults = Self::default();
Self {
gamma_rate: std::env::var("WM_GAMMA_RATE")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(defaults.gamma_rate),
alpha_idle: Duration::from_secs(
std::env::var("WM_ALPHA_IDLE")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(defaults.alpha_idle.as_secs()),
),
theta_idle: Duration::from_secs(
std::env::var("WM_THETA_IDLE")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(defaults.theta_idle.as_secs()),
),
delta_idle: Duration::from_secs(
std::env::var("WM_DELTA_IDLE")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(defaults.delta_idle.as_secs()),
),
}
}
}
pub struct BrainWaveTracker {
pub config: BrainWaveConfig,
timestamps: smallvec::SmallVec<[Instant; 64]>,
last_event: Instant,
current: BrainWave,
}
impl BrainWaveTracker {
#[must_use]
pub fn new(config: BrainWaveConfig) -> Self {
let now = Instant::now();
Self {
config,
timestamps: smallvec::SmallVec::new(),
last_event: now,
current: BrainWave::Delta,
}
}
pub fn record_event(&mut self) -> BrainWave {
let now = Instant::now();
self.last_event = now;
self.timestamps.push(now);
let cutoff = now.checked_sub(Duration::from_secs(60)).unwrap();
self.timestamps.retain(|t| *t > cutoff);
self.recompute(now);
self.current
}
pub fn recompute(&mut self, now: Instant) -> BrainWave {
let rate = self.event_rate(now);
let idle = now.duration_since(self.last_event);
self.current = if rate > self.config.gamma_rate {
BrainWave::Gamma
} else if rate > 0.0 {
BrainWave::Beta
} else if idle > self.config.delta_idle {
BrainWave::Delta
} else if idle > self.config.theta_idle {
BrainWave::Theta
} else {
BrainWave::Alpha
};
self.current
}
#[must_use]
pub const fn current(&self) -> BrainWave {
self.current
}
#[must_use]
pub fn next_transition_duration(&self) -> Duration {
let now = Instant::now();
let idle = now.duration_since(self.last_event);
match self.current {
BrainWave::Gamma | BrainWave::Beta => self.config.alpha_idle.saturating_sub(idle),
BrainWave::Alpha => self.config.theta_idle.saturating_sub(idle),
BrainWave::Theta => self.config.delta_idle.saturating_sub(idle),
BrainWave::Delta => Duration::from_secs(3600),
}
}
#[must_use]
pub fn idle_duration(&self) -> Duration {
Instant::now().duration_since(self.last_event)
}
fn event_rate(&self, now: Instant) -> f64 {
if self.timestamps.is_empty() {
return 0.0;
}
let cutoff = now.checked_sub(Duration::from_secs(60)).unwrap();
let recent = self.timestamps.iter().filter(|&&t| t > cutoff).count();
recent as f64
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn brain_wave_allows_tools() {
assert!(BrainWave::Gamma.allows_tools());
assert!(BrainWave::Beta.allows_tools());
assert!(BrainWave::Alpha.allows_tools());
assert!(!BrainWave::Theta.allows_tools());
assert!(!BrainWave::Delta.allows_tools());
}
#[test]
fn tracker_starts_dormant() {
let tracker = BrainWaveTracker::new(BrainWaveConfig::default());
assert_eq!(tracker.current(), BrainWave::Delta);
}
#[test]
fn tracker_transitions_on_event() {
let mut tracker = BrainWaveTracker::new(BrainWaveConfig::default());
let _ = tracker.record_event();
assert_eq!(tracker.current(), BrainWave::Beta);
}
#[test]
fn tracker_gamma_on_burst() {
let mut tracker = BrainWaveTracker::new(BrainWaveConfig::default());
for _ in 0..15 {
let _ = tracker.record_event();
}
assert_eq!(tracker.current(), BrainWave::Gamma);
}
#[test]
fn next_transition_duration_beta_to_alpha() {
let mut tracker = BrainWaveTracker::new(BrainWaveConfig::default());
let _ = tracker.record_event();
assert_eq!(tracker.current(), BrainWave::Beta);
let d = tracker.next_transition_duration();
assert!(d <= Duration::from_secs(30));
assert!(d > Duration::from_secs(28));
}
#[test]
fn next_transition_duration_delta_sleeps_long() {
let tracker = BrainWaveTracker::new(BrainWaveConfig::default());
assert_eq!(tracker.current(), BrainWave::Delta);
let d = tracker.next_transition_duration();
assert_eq!(d, Duration::from_secs(3600));
}
#[test]
fn idle_duration_grows_after_event() {
let mut tracker = BrainWaveTracker::new(BrainWaveConfig::default());
let _ = tracker.record_event();
let d1 = tracker.idle_duration();
std::thread::sleep(Duration::from_millis(50));
let d2 = tracker.idle_duration();
assert!(d2 > d1);
}
}