use core::{
    fmt,
    ops::{Div, Mul},
};

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Hertz(pub u64);

const KILO: u64 = 1_000;
const MEGA: u64 = 1_000_000;
const GIGA: u64 = 1_000_000_000;

pub trait IntoHertz {
    fn hz(&self) -> Hertz;
    #[inline]
    fn khz(&self) -> Hertz {
        self.hz() * KILO
    }
    #[inline]
    fn mhz(&self) -> Hertz {
        self.hz() * MEGA
    }
    #[inline]
    fn ghz(&self) -> Hertz {
        self.hz() * GIGA
    }
}

impl Mul<u32> for Hertz {
    type Output = Hertz;

    fn mul(self, rhs: u32) -> Self::Output {
        Self(self.0 * rhs as u64)
    }
}

impl Mul<u64> for Hertz {
    type Output = Hertz;

    fn mul(self, rhs: u64) -> Self::Output {
        Self(self.0 * rhs)
    }
}

impl Div<u32> for Hertz {
    type Output = Hertz;

    fn div(self, rhs: u32) -> Self::Output {
        Self(self.0 / rhs as u64)
    }
}

impl fmt::Display for Hertz {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let (scale, suffix) = match self.0 {
            x if x >= GIGA => (GIGA, "GHz"),
            x if x >= MEGA => (MEGA, "MHz"),
            x if x >= KILO => (KILO, "kHz"),
            x => return write!(f, "{x}Hz"),
        };

        let int = self.0 / scale;
        let frac = (self.0 / (scale / 1000)) % 1000;

        if frac == 0 {
            write!(f, "{int}{suffix}")
        } else {
            write!(f, "{int}.{frac:03}{suffix}")
        }
    }
}

macro_rules! impl_into_hertz {
    ($($ty:ty),+ $(,)?) => {
        $(
            impl IntoHertz for $ty {
                #[inline(always)]
                fn hz(&self) -> Hertz {
                    Hertz(*self as u64)
                }
            }
        )+
    };
}

impl_into_hertz!(u8, u16, u32, u64);
