Documentation
use core::ops::{Add, Div, Mul, RangeBounds, Rem, Sub};

use crate::{Random, RandomSource};

pub trait Num: Sized + Copy {
    const MAX: Self;
    const MIN: Self;

    fn abs(&self) -> Self { *self }
}

macro_rules! impl_for_numeric {
    ($($t:ident),* $(,)?) => {
        $(
            impl Num for $t {
                const MAX: Self = $t :: MAX;
                const MIN: Self = $t :: MIN;
            }
        )*
    };
}

macro_rules! impl_for_signed_numeric {
    ($($t:ident),* $(,)?) => {
        $(
            impl Num for $t {
                const MAX: Self = $t :: MAX;
                const MIN: Self = $t :: MIN;

                fn abs(&self) -> Self {
                    $t::abs(*self)
                }
            }
        )*
    };
}

impl_for_numeric!(u8, u16, u32, u64, u128, usize);

impl_for_signed_numeric!(i8, i16, i32, i64, i128, isize);

pub struct Simple<T> {
    low: T,
    high: T,
}

impl<T> Simple<T>
where
    T: Add<Output = T> + Sub<Output = T> + From<u8> + Copy + Num + PartialOrd
{
    pub fn new<R: RangeBounds<T>>(range: R) -> Option<Self> {
        use core::ops::Bound;
        let low = match range.start_bound() {
            Bound::Included(n) => *n,
            Bound::Excluded(n) => *n + T::from(1),
            Bound::Unbounded => T::MIN,
        };
        let high = match range.end_bound() {
            Bound::Included(n) => *n,
            Bound::Excluded(n) => *n - T::from(1),
            Bound::Unbounded => T::MAX,
        };
        if low >= high {
            None
        } else {
            Some(Self { low, high })
        }
    }
}

pub trait Distribution<T: Random> {
    fn sample<S>(&self, source: &mut S) -> T
    where
        S: RandomSource + ?Sized;
}

impl<T> Distribution<T> for Simple<T>
where
    T: Random + PartialOrd + Add<Output = T> + Sub<Output = T>
       + Mul<Output = T> + Div<Output = T> + Rem<Output = T> + Num + Copy
       + From<u8>
{
    fn sample<S>(&self, source: &mut S) -> T
    where
        S: RandomSource + ?Sized
    {
        let val = T::random(source).abs();
        let diff = self.high - self.low.abs();
        (val % (T::from(1) + diff)) + self.low
    }
}