use std::collections::VecDeque;
use std::sync::Arc;
use std::time::Duration;
use bacnet_types::error::Error;
use super::schedule::{SampleSchedule, MIN_SAMPLE_PERIOD};
use crate::common;
use crate::traits::MonotonicClock;
pub const MAX_WINDOW_SAMPLES: u32 = 1_440;
pub const DEFAULT_WINDOW_SAMPLES: u32 = 15;
pub const DEFAULT_WINDOW_INTERVAL: u32 = 900;
const _: () = assert!(DEFAULT_WINDOW_SAMPLES >= 15 && MAX_WINDOW_SAMPLES >= DEFAULT_WINDOW_SAMPLES);
#[derive(Debug, Clone, Copy)]
struct Statistics {
minimum: f32,
maximum: f32,
average: f32,
valid: u32,
}
impl Statistics {
const EMPTY: Self = Self {
minimum: f32::INFINITY,
maximum: f32::NEG_INFINITY,
average: f32::NAN,
valid: 0,
};
}
#[derive(Debug, Clone)]
pub(super) struct SampleWindow {
interval: u32,
capacity: u32,
slots: VecDeque<Option<f32>>,
statistics: Statistics,
schedule: SampleSchedule,
}
impl SampleWindow {
pub(super) fn new() -> Self {
Self {
interval: DEFAULT_WINDOW_INTERVAL,
capacity: DEFAULT_WINDOW_SAMPLES,
slots: VecDeque::new(),
statistics: Statistics::EMPTY,
schedule: SampleSchedule::default(),
}
}
pub(super) fn interval(&self) -> u32 {
self.interval
}
pub(super) fn capacity(&self) -> u32 {
self.capacity
}
pub(super) fn minimum(&self) -> f32 {
self.statistics.minimum
}
pub(super) fn maximum(&self) -> f32 {
self.statistics.maximum
}
pub(super) fn average(&self) -> f32 {
self.statistics.average
}
pub(super) fn attempted(&self) -> u32 {
self.slots.len() as u32
}
pub(super) fn valid(&self) -> u32 {
self.statistics.valid
}
pub(super) fn set_interval(&mut self, interval: u32) -> Result<(), Error> {
if interval == 0 {
return Err(common::value_out_of_range_error());
}
self.interval = interval;
self.reset();
Ok(())
}
pub(super) fn set_capacity(&mut self, capacity: u32) -> Result<(), Error> {
if !(1..=MAX_WINDOW_SAMPLES).contains(&capacity) {
return Err(common::value_out_of_range_error());
}
self.capacity = capacity;
self.reset();
self.slots.shrink_to(capacity as usize);
Ok(())
}
pub(super) fn push(&mut self, sample: Option<f32>) {
if self.slots.len() >= self.capacity as usize {
self.slots.pop_front();
}
self.slots.push_back(sample);
self.recompute();
}
pub(super) fn reset(&mut self) {
self.slots.clear();
self.statistics = Statistics::EMPTY;
let period = self.period();
self.schedule.restart(period);
}
pub(super) fn period(&self) -> Duration {
(Duration::from_secs(self.interval.into()) / self.capacity).max(MIN_SAMPLE_PERIOD)
}
pub(super) fn bind_clock(&mut self, clock: Option<Arc<MonotonicClock>>) {
let period = self.period();
self.schedule.bind(clock, period);
}
pub(super) fn next_due(&self) -> Option<Duration> {
self.schedule.next_due()
}
pub(super) fn take_due(&mut self, now: Duration) -> bool {
let period = self.period();
self.schedule.take_due(now, period)
}
fn recompute(&mut self) {
let mut statistics = Statistics::EMPTY;
let mut sum = 0.0f64;
for &value in self.slots.iter().flatten() {
statistics.minimum = statistics.minimum.min(value);
statistics.maximum = statistics.maximum.max(value);
sum += f64::from(value);
statistics.valid += 1;
}
if statistics.valid > 0 {
let average = (sum / f64::from(statistics.valid)) as f32;
statistics.average = average.clamp(statistics.minimum, statistics.maximum);
}
self.statistics = statistics;
}
}