#![forbid(unsafe_code)]
use crate::src::const_fn::const_for;
use crate::src::enum_map::DefaultValue;
use std::fmt;
use std::fmt::Debug;
use std::fmt::Display;
use std::fmt::Formatter;

#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct InRange<T, const MIN: u128, const MAX: u128>(T);

impl<T, const MIN: u128, const MAX: u128> InRange<T, MIN, MAX>
where
    T: TryFrom<u128, Error: Debug>,
{
    pub fn min() -> Self {
        Self(MIN.try_into().unwrap())
    }

    pub fn max() -> Self {
        Self(MAX.try_into().unwrap())
    }
}

impl<T, const MIN: u128, const MAX: u128> InRange<T, MIN, MAX>
where
    T: TryFrom<u128, Error: Debug> + PartialEq + Eq + PartialOrd + Ord,
{
    fn in_bounds(&self) -> bool {
        *self >= Self::min() && *self <= Self::max()
    }

    pub fn new(value: T) -> Option<Self> {
        let this = Self(value);
        if this.in_bounds() { Some(this) } else { None }
    }

    pub fn get(self) -> T {
        debug_assert!(self.in_bounds());
        self.0
    }
}

impl<T, const MIN: u128, const MAX: u128> Default for InRange<T, MIN, MAX>
where
    T: TryFrom<u128, Error: Debug>,
{
    fn default() -> Self {
        Self::min()
    }
}

impl<T, const MIN: u128, const MAX: u128> Display for InRange<T, MIN, MAX>
where
    T: Display,
{
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

macro_rules! impl_const_new {
    ($T:ty) => {
        impl<const MIN: u128, const MAX: u128> DefaultValue for InRange<$T, MIN, MAX> {
            const DEFAULT: Self = Self(0);
        }

        impl<const MIN: u128, const MAX: u128> InRange<$T, MIN, MAX> {
            #[allow(unused)]
            pub const fn const_new(value: $T) -> Self {
                assert!(value as u128 >= MIN && value as u128 <= MAX);
                Self(value)
            }

            #[allow(unused)]
            pub const fn new_array<const N: usize>(a: [$T; N]) -> [Self; N] {
                let mut b = [DefaultValue::DEFAULT; N];
                const_for!(i in 0..N => {
                    b[i] = Self::const_new(a[i]);
                });
                b
            }
        }
    };
}

impl_const_new!(u8);
impl_const_new!(u16);
impl_const_new!(u32);
impl_const_new!(u64);
impl_const_new!(u128);

impl_const_new!(i8);
impl_const_new!(i16);
impl_const_new!(i32);
impl_const_new!(i64);
impl_const_new!(i128);
