use crate::*;

#[derive(Copy, Clone)]
pub struct Proportion<T: Internal> {
    pub(crate) value: T,
}

impl<I: Internal> Proportion<I> {
    pub fn new(value: I) -> Self {
        Self { value: value.clamp() }
    }

    pub const MIN: Self = Proportion {
        value: <I as Internal>::MIN,
    };
    pub const MAX: Self = Proportion {
        value: <I as Internal>::MAX,
    };
    pub const ZERO: Self = Self::MIN;
    pub const ONE: Self = Self::MAX;

    pub fn set(&mut self, new_value: I) {
        self.value = new_value.clamp();
    }

    pub fn is_min(&self) -> bool {
        self.value == I::MIN
    }
    pub fn is_max(&self) -> bool {
        self.value == I::MAX
    }

    pub fn is_zero(&self) -> bool {
        self.is_min()
    }
    pub fn is_one(&self) -> bool {
        self.is_max()
    }

    pub fn as_u8(&self) -> u8 {
        self.value.as_u8()
    }
    pub fn as_f32(&self) -> f32 {
        self.value.as_f32()
    }
    pub fn as_f64(&self) -> f64 {
        self.value.as_f64()
    }

    pub fn invert(&self) -> Self {
        Self {
            value: I::MAX - self.value,
        }
    }

    pub fn value(&self) -> I {
        self.value
    }
}

macro_rules! impl_mul {
    ($type:ty, $method:ident) => {
        impl<I: Internal> std::ops::Mul<Proportion<I>> for $type {
            type Output = $type;
            fn mul(self, proportion: Proportion<I>) -> $type {
                proportion.value.$method(self)
            }
        }
        impl<I: Internal> std::ops::Mul<$type> for Proportion<I> {
            type Output = $type;
            fn mul(self, value: $type) -> $type {
                self.value.$method(value)
            }
        }
        impl<I: Internal> std::ops::Mul<&Proportion<I>> for $type {
            type Output = $type;
            fn mul(self, proportion: &Proportion<I>) -> $type {
                proportion.value.$method(self)
            }
        }
        impl<I: Internal> std::ops::Mul<$type> for &Proportion<I> {
            type Output = $type;
            fn mul(self, value: $type) -> $type {
                self.value.$method(value)
            }
        }
    };
}

impl_mul!(u8, mul_u8);
impl_mul!(u16, mul_u16);
impl_mul!(u32, mul_u32);
impl_mul!(f32, mul_f32);
impl_mul!(f64, mul_f64);

use std::fmt::{Debug, Display, Formatter};

impl<T: Internal> Debug for Proportion<T> {
    fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
        write!(f, "Proportion {{ {:.2} }}", self.as_f32())
    }
}
impl<T: Internal> Display for Proportion<T> {
    fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
        write!(f, "{:.2}", self.as_f32())
    }
}