use std::{fmt, ops};
use compile_fmt::{clip, compile_panic};
use crate::metadata::SizeUnit;
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ByteSize(pub u64);
impl fmt::Debug for ByteSize {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Display::fmt(self, formatter)
    }
}
impl fmt::Display for ByteSize {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        if self.0 == 0 {
            formatter.write_str("0 B")
        } else if self.0 % (1 << 30) == 0 {
            write!(formatter, "{} GiB", self.0 >> 30)
        } else if self.0 % (1 << 20) == 0 {
            write!(formatter, "{} MiB", self.0 >> 20)
        } else if self.0 % (1 << 10) == 0 {
            write!(formatter, "{} KiB", self.0 >> 10)
        } else {
            write!(formatter, "{} B", self.0)
        }
    }
}
impl From<u64> for ByteSize {
    fn from(value: u64) -> Self {
        Self(value)
    }
}
impl ByteSize {
    pub const fn checked(val: u64, unit: SizeUnit) -> Option<Self> {
        match val.checked_mul(unit.bytes_in_unit()) {
            Some(val) => Some(Self(val)),
            None => None,
        }
    }
    pub const fn new(val: u64, unit: SizeUnit) -> Self {
        if let Some(size) = Self::checked(val, unit) {
            size
        } else {
            compile_panic!(
                val => compile_fmt::fmt::<u64>(), " ", unit.plural() => clip(16, ""), " does not fit into a `u64` value"
            );
        }
    }
    pub const fn checked_add(self, rhs: Self) -> Option<Self> {
        match self.0.checked_add(rhs.0) {
            Some(val) => Some(Self(val)),
            None => None,
        }
    }
    pub const fn checked_mul(self, factor: u64) -> Option<Self> {
        match self.0.checked_mul(factor) {
            Some(val) => Some(Self(val)),
            None => None,
        }
    }
}
impl From<SizeUnit> for ByteSize {
    fn from(unit: SizeUnit) -> Self {
        Self(unit.bytes_in_unit())
    }
}
impl ops::Mul<u64> for SizeUnit {
    type Output = ByteSize;
    fn mul(self, rhs: u64) -> Self::Output {
        ByteSize::from(self)
            .checked_mul(rhs)
            .unwrap_or_else(|| panic!("Integer overflow getting {rhs} * {self}"))
    }
}
impl ops::Mul<SizeUnit> for u64 {
    type Output = ByteSize;
    fn mul(self, rhs: SizeUnit) -> Self::Output {
        ByteSize::from(rhs)
            .checked_mul(self)
            .unwrap_or_else(|| panic!("Integer overflow getting {self} * {rhs}"))
    }
}
impl ops::Add for ByteSize {
    type Output = Self;
    fn add(self, rhs: Self) -> Self::Output {
        self.checked_add(rhs)
            .unwrap_or_else(|| panic!("Integer overflow getting {self} + {rhs}"))
    }
}
impl ops::Mul<u64> for ByteSize {
    type Output = Self;
    fn mul(self, rhs: u64) -> Self::Output {
        self.checked_mul(rhs)
            .unwrap_or_else(|| panic!("Integer overflow getting {self} * {rhs}"))
    }
}