// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
#[cfg(feature = "client")] use std::num::NonZeroUsize;
#[cfg(feature = "client")] use std::fmt::{Debug, Formatter, Result as FmtResult};
use std::collections::VecDeque;

#[cfg(feature = "client")]
pub struct RollingAverage {
	capacity: usize,
	queue: VecDeque<f32>,
	sum: f32, // Redundant information cached for optimisation
}

#[cfg(feature = "client")]
impl RollingAverage {
	pub fn new(capacity: NonZeroUsize) -> RollingAverage {
		let capacity = capacity.into();
		RollingAverage {
			capacity,
			queue: VecDeque::with_capacity(capacity),
			sum: 0.0,
		}
	}

	#[cfg(feature = "client")]
	pub fn reset(&mut self) {
		self.queue.clear();
		self.sum = 0.0;
	}

	pub fn add(&mut self, x: f32) {
		if self.queue.len() == self.capacity {
			self.sum -= self.queue.pop_back().unwrap();
		}
		self.sum += x;
		self.queue.push_front(x);
	}

	#[cfg(feature = "client")]
	pub fn mean(&self) -> Option<f32> {
		if self.queue.is_empty() { None } else { Some(self.sum / self.queue.len() as f32) }
	}
}

#[cfg(feature = "client")]
impl Debug for RollingAverage {
	fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
		self.queue.fmt(fmt)
	}
}

pub struct TimedRollingAverage {
	max_time: f32,
	max_capacity: usize,
	queue: VecDeque<(f32, f32)>,
	sum: f32,
	sum_time: f32,
	next_time: f32,
}

impl TimedRollingAverage {
	pub fn new(max_time: f32, max_capacity: usize) -> TimedRollingAverage {
		TimedRollingAverage {
			max_time,
			max_capacity,
			queue: VecDeque::new(), // Might not get close to `max_capacity` so not using `with_capacity`
			sum: 0.0,
			sum_time: 0.0,
			next_time: 0.0,
		}
	}

	pub fn add_time(&mut self, dt: f32) {
		self.next_time += dt;
	}

	pub fn add(&mut self, x: f32) {
		self.queue.push_front((x, self.next_time));
		self.sum_time += self.next_time;
		self.sum += x * self.next_time;
		self.next_time = 0.0;

		while self.queue.len() > self.max_capacity || self.sum_time > self.max_time {
			let Some((back_x, back_time)) = self.queue.pop_back() else {
				// Resets things in case of large floating-point error breaking things
				self.sum = 0.0;
				self.sum_time = 0.0;
				return;
			};

			self.sum_time -= back_time;
			self.sum -= back_x * back_time;
		}
	}

	pub fn mean_and_stddev(&self) -> Option<(f32, f32)> {
		if self.sum_time <= 0.0 { return None; }

		let mean = self.sum / self.sum_time;
		let stddev = (self.queue.iter().map(|&(x, time)| (x - mean).powi(2) * time).sum::<f32>() / self.sum_time).sqrt();
		Some((mean, stddev))
	}
}
