use core::ptr::NonNull;

macro_rules! def_addr {
    ($name:ident, $t:ty) => {
        #[repr(transparent)]
        #[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
        pub struct $name($t);

        impl From<$t> for $name {
            #[inline(always)]
            fn from(value: $t) -> Self {
                Self(value)
            }
        }

        impl $name {
            #[inline(always)]
            pub fn raw(&self) -> $t {
                self.0
            }

            #[inline(always)]
            pub const fn new(value: $t) -> Self {
                Self(value)
            }
        }

        impl core::ops::Add<$t> for $name {
            type Output = Self;

            #[inline(always)]
            fn add(self, rhs: $t) -> Self::Output {
                Self(self.0 + rhs)
            }
        }

        impl core::ops::AddAssign<$t> for $name {
            #[inline(always)]
            fn add_assign(&mut self, rhs: $t) {
                self.0 += rhs;
            }
        }

        impl core::ops::Sub<$t> for $name {
            type Output = Self;

            #[inline(always)]
            fn sub(self, rhs: $t) -> Self::Output {
                Self(self.0 - rhs)
            }
        }

        impl core::ops::Sub<Self> for $name {
            type Output = $t;

            #[inline(always)]
            fn sub(self, rhs: Self) -> Self::Output {
                self.0 - rhs.0
            }
        }

        impl core::fmt::Debug for $name {
            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
                write!(f, "0x{:0>16x}", self.0)
            }
        }
    };
}

def_addr!(PhysAddr, usize);
def_addr!(VirtAddr, usize);

impl VirtAddr {
    #[inline(always)]
    pub fn as_ptr(self) -> *mut u8 {
        self.0 as _
    }
}

impl From<*mut u8> for VirtAddr {
    #[inline(always)]
    fn from(val: *mut u8) -> Self {
        Self(val as _)
    }
}

impl From<NonNull<u8>> for VirtAddr {
    #[inline(always)]
    fn from(val: NonNull<u8>) -> Self {
        Self(val.as_ptr() as _)
    }
}

impl From<*const u8> for VirtAddr {
    #[inline(always)]
    fn from(val: *const u8) -> Self {
        Self(val as _)
    }
}

#[cfg(target_pointer_width = "64")]
impl From<u64> for PhysAddr {
    #[inline(always)]
    fn from(value: u64) -> Self {
        Self(value as _)
    }
}

#[cfg(target_pointer_width = "32")]
impl From<u32> for PhysAddr {
    #[inline(always)]
    fn from(value: u32) -> Self {
        Self(value as _)
    }
}
