// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
use std::time::{Instant, Duration};

use tokio::time;

pub struct Bucket {
	amount: f32, // Leaks at a rate of one per second
	last_request: Instant,
}

impl Bucket {
	pub fn new(now: Instant) -> Bucket {
		Bucket { amount: 0.0, last_request: now }
	}

	/// Attempts to add some "liquid" to the bucket, adding the liquid and
	/// returning true if capacity's available. Otherwise returns false and the
	/// bucket remains unchanged.
	pub fn add(&mut self, now: Instant, to_add: f32, capacity: f32) -> bool {
		let new_amount = self.new_amount(now, to_add);
		if new_amount > capacity {
			return false;
		}

		self.amount = new_amount;
		self.last_request = now;
		true
	}

	pub fn non_empty(&self, now: Instant) -> bool {
		self.amount - now.duration_since(self.last_request).as_secs_f32() > 0.0
	}

	/// Always adds "liquid" to the bucket. If the bucket is above its maximum
	/// capacity after adding the liquid, returns the length of time needed to
	/// sleep for the bucket to leak to capacity.
	pub async fn always_add(&mut self, now: Instant, to_add: f32, capacity: f32) {
		let new_amount = self.new_amount(now, to_add); // Possibly overflowing
		self.amount = new_amount.min(capacity);
		self.last_request = now;
		time::sleep(Duration::from_secs_f32(new_amount - self.amount)).await;
	}

	fn new_amount(&self, now: Instant, to_add: f32) -> f32 {
		(self.amount - now.duration_since(self.last_request).as_secs_f32()).max(0.0) + to_add
	}
}
