use crate::number::{
    CheckedAddAssign, CheckedMulAssign, CheckedSubAssign, SaturatingAddAssign, SaturatingMulAssign,
    SaturatingSubAssign, UpcastFrom,
};
use core::{cmp::Ordering, convert::TryFrom, marker::PhantomData, ops};
#[derive(Clone, Copy, Debug, Default, Hash)]
pub struct Counter<T, Behavior = ()>(T, PhantomData<Behavior>);
#[derive(Clone, Copy, Debug, Default, Hash)]
pub struct Saturating;
impl<T, Behavior> Counter<T, Behavior> {
    #[inline]
    pub const fn new(value: T) -> Self {
        Self(value, PhantomData)
    }
    #[inline]
    pub fn set(&mut self, value: T) {
        self.0 = value;
    }
    #[inline]
    pub fn try_add<V>(&mut self, value: V) -> Result<(), T::Error>
    where
        T: TryFrom<V>,
        Self: ops::AddAssign<T>,
    {
        let value = T::try_from(value)?;
        *self += value;
        Ok(())
    }
    #[inline]
    pub fn try_sub<V>(&mut self, value: V) -> Result<(), T::Error>
    where
        T: TryFrom<V>,
        Self: ops::SubAssign<T>,
    {
        let value = T::try_from(value)?;
        *self -= value;
        Ok(())
    }
}
macro_rules! assign_trait {
    (
        $op:ident,
        $method:ident,
        $saturating_trait:ident,
        $saturating_method:ident,
        $checked_trait:ident,
        $checked:ident
    ) => {
        impl<T, R> ops::$op<R> for Counter<T, ()>
        where
            T: $saturating_trait<R> + $checked_trait<R> + ops::$op + UpcastFrom<R>,
        {
            #[inline]
            fn $method(&mut self, rhs: R) {
                if cfg!(feature = "checked-counters") {
                    (self.0).$checked(rhs).expect("counter overflow");
                } else if cfg!(debug_assertions) {
                    (self.0).$method(T::upcast_from(rhs));
                } else {
                    (self.0).$saturating_method(rhs);
                }
            }
        }
        impl<T, R> ops::$op<R> for Counter<T, Saturating>
        where
            T: $saturating_trait<R>,
        {
            #[inline]
            fn $method(&mut self, rhs: R) {
                (self.0).$saturating_method(rhs);
            }
        }
    };
}
assign_trait!(
    AddAssign,
    add_assign,
    SaturatingAddAssign,
    saturating_add_assign,
    CheckedAddAssign,
    checked_add_assign
);
assign_trait!(
    SubAssign,
    sub_assign,
    SaturatingSubAssign,
    saturating_sub_assign,
    CheckedSubAssign,
    checked_sub_assign
);
assign_trait!(
    MulAssign,
    mul_assign,
    SaturatingMulAssign,
    saturating_mul_assign,
    CheckedMulAssign,
    checked_mul_assign
);
impl<T, B> UpcastFrom<Counter<T, B>> for T {
    #[inline]
    fn upcast_from(value: Counter<T, B>) -> Self {
        value.0
    }
}
impl<T, B> UpcastFrom<&Counter<T, B>> for T
where
    T: for<'a> UpcastFrom<&'a T>,
{
    #[inline]
    fn upcast_from(value: &Counter<T, B>) -> Self {
        T::upcast_from(&value.0)
    }
}
impl<T, B> ops::Deref for Counter<T, B> {
    type Target = T;
    #[inline]
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}
impl<T, B, R> PartialEq<R> for Counter<T, B>
where
    Self: PartialOrd<R>,
{
    #[inline]
    fn eq(&self, other: &R) -> bool {
        self.partial_cmp(other) == Some(Ordering::Equal)
    }
}
impl<T, B> PartialOrd<T> for Counter<T, B>
where
    T: PartialOrd<T>,
{
    #[inline]
    fn partial_cmp(&self, other: &T) -> Option<Ordering> {
        self.0.partial_cmp(other)
    }
}
impl<T, B> PartialOrd for Counter<T, B>
where
    T: PartialOrd<T>,
{
    #[inline]
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        self.0.partial_cmp(&other.0)
    }
}
impl<T, B> Eq for Counter<T, B> where Self: Ord {}
impl<T, B> Ord for Counter<T, B>
where
    T: Ord,
{
    #[inline]
    fn cmp(&self, other: &Self) -> Ordering {
        self.0.cmp(&other.0)
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn automatic_upcast() {
        let mut a: Counter<u32> = Counter::new(0);
        a += 1u8;
        a += 2u16;
        a += 3u32;
        assert_eq!(a, Counter::new(6));
        assert_eq!(a, 6u32);
    }
    #[test]
    fn saturating() {
        let mut a: Counter<u8, Saturating> = Counter::new(0);
        a += 250;
        a += 250;
        a += 123;
        assert_eq!(a, Counter::new(255));
    }
}