use num_traits::AsPrimitive;
use std::cell::Cell;
use std::ops::Add;

#[derive(Clone, Debug)]
pub struct Quota(pub(crate) Cell<f64>);

#[derive(Clone, Copy, Debug)]
pub struct QuotaTooLow(CurrentQuota);

#[derive(Clone, Copy, Debug)]
pub struct CurrentQuota {
    available: f64,
}

pub type QuotaResult = Result<CurrentQuota, QuotaTooLow>;

impl QuotaTooLow {
    #[inline]
    pub fn current(&self) -> CurrentQuota {
        self.0
    }
}

impl CurrentQuota {
    #[inline]
    pub fn available(&self) -> f64 {
        self.available
    }
}

impl Quota {
    #[inline]
    pub fn new() -> Quota {
        Quota(Cell::new(0.0))
    }

    #[inline]
    pub fn with_initial_tokens<T>(initial_available_tokens: T) -> Self
    where
        T: AsPrimitive<f64>,
    {
        Self(Cell::new(initial_available_tokens.as_().max(0.0)))
    }

    #[inline]
    pub fn fill<T>(&self, units: T, max_capacity: f64) -> CurrentQuota
    where
        T: AsPrimitive<f64>,
    {
        let available = self.0.get().add(units.as_()).clamp(0.0, max_capacity);
        self.0.set(available);
        CurrentQuota { available }
    }
    #[inline]
    pub fn consume<T>(&self, cost: T) -> QuotaResult
    where
        T: AsPrimitive<f64>,
    {
        let cost = cost.as_();
        let remaining = self.0.get();

        if remaining - cost >= 0.0 {
            let new_remaining = remaining - cost;
            self.0.set(new_remaining);

            Ok(CurrentQuota {
                available: new_remaining,
            })
        } else {
            Err(QuotaTooLow(CurrentQuota {
                available: remaining,
            }))
        }
    }
}
#[cfg(test)]
mod tests;
