use core::{
    fmt,
    iter::Step,
    mem::align_of,
    num::TryFromIntError,
    ops::{Add, Sub},
};

use bytemuck::{Pod, Zeroable};
use kernel_arch_interface::mem::KernelTableManager;

/// Wrapper type to represent a physical memory address
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Pod, Zeroable)]
#[repr(transparent)]
pub struct PhysicalAddress(pub(crate) u64);

/// Interface for obtaining physical addresses of values
pub trait AsPhysicalAddress {
    /// Returns the value's physical address.
    ///
    /// # Safety
    ///
    /// The caller must ensure the value has been constructed and obtained through proper means.
    unsafe fn as_physical_address(&self) -> PhysicalAddress;
}

impl PhysicalAddress {
    /// Physical address of zero
    pub const ZERO: Self = Self(0);

    /// Maximum representable physical address
    pub const MAX: Self = Self(u64::MAX);
    /// Minumum representable physical address
    pub const MIN: Self = Self(u64::MIN);

    /// Applies an offset to the address
    pub const fn add(self, offset: usize) -> Self {
        Self(self.0 + offset as u64)
    }

    pub const fn from_usize(value: usize) -> Self {
        Self(value as u64)
    }

    pub const fn from_u64(value: u64) -> Self {
        Self(value)
    }

    pub const fn from_u32(value: u32) -> Self {
        Self(value as u64)
    }

    #[cfg(any(target_pointer_width = "64", rust_analyzer))]
    pub const fn into_usize(self) -> usize {
        self.0 as usize
    }

    pub fn try_into_usize(self) -> Result<usize, TryFromIntError> {
        self.0.try_into()
    }

    pub fn try_into_u32(self) -> Result<u32, TryFromIntError> {
        self.0.try_into()
    }

    pub const fn into_u64(self) -> u64 {
        self.0
    }

    /// Returns `true` if the address is zero
    #[inline(always)]
    pub const fn is_zero(self) -> bool {
        self.0 == 0
    }

    /// Returns `true` if the address is aligned to a boundary of a page at level `L`
    #[inline]
    pub const fn is_aligned_for<T: Sized>(self) -> bool {
        self.0 as usize % align_of::<T>() == 0
    }

    /// Converts a previously virtualized physical address back into its physical form.
    ///
    /// # Safety
    ///
    /// The caller must ensure the function only receives addresses obtained through
    /// [PhysicalAddress::virtualize_raw] or
    /// [super::pointer::PhysicalRef]/[super::pointer::PhysicalRefMut] facilities.
    #[inline]
    pub unsafe fn raw_from_virtualized<K: KernelTableManager>(address: usize) -> Self {
        PhysicalAddress(K::physicalize(address))
    }

    /// Converts the physical address to a virtual one
    #[inline]
    pub fn raw_virtualize<K: KernelTableManager>(self) -> usize {
        K::virtualize(self.0)
    }
}

impl Add for PhysicalAddress {
    type Output = Self;

    fn add(self, rhs: Self) -> Self::Output {
        Self(self.0 + rhs.0)
    }
}

impl Sub for PhysicalAddress {
    type Output = usize;

    fn sub(self, rhs: Self) -> Self::Output {
        (self.0 - rhs.0) as usize
    }
}

// Conversions

// impl FromRaw<u64> for PhysicalAddress {
//     fn from_raw(value: u64) -> Self {
//         Self(value)
//     }
// }
//
// impl FromRaw<usize> for PhysicalAddress {
//     fn from_raw(value: usize) -> Self {
//         Self(value as u64)
//     }
// }
//
// impl IntoRaw<u64> for PhysicalAddress {
//     fn into_raw(self) -> u64 {
//         self.0
//     }
// }
//
// impl IntoRaw<usize> for PhysicalAddress {
//     fn into_raw(self) -> usize {
//         self.0 as usize
//     }
// }

impl From<PhysicalAddress> for u64 {
    fn from(addr: PhysicalAddress) -> u64 {
        addr.0
    }
}

#[cfg(any(target_pointer_width = "64", rust_analyzer))]
impl From<PhysicalAddress> for usize {
    fn from(addr: PhysicalAddress) -> usize {
        addr.0 as usize
    }
}

// Ranges

impl Step for PhysicalAddress {
    fn steps_between(_start: &Self, _end: &Self) -> (usize, Option<usize>) {
        todo!()
    }

    fn forward_checked(start: Self, count: usize) -> Option<Self> {
        start.0.checked_add(count as u64).map(Self)
    }

    fn backward_checked(_start: Self, _count: usize) -> Option<Self> {
        todo!()
    }
}

// fmt

impl fmt::LowerHex for PhysicalAddress {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::LowerHex::fmt(&self.0, f)
    }
}
