//! RISC-V page table entries.

use core::fmt;

use ax_memory_addr::PhysAddr;

use crate::{GenericPTE, MappingFlags};

bitflags::bitflags! {
    /// Page-table entry flags.
    #[derive(Debug)]
    pub struct PTEFlags: usize {
        /// Whether the PTE is valid.
        const V =   1 << 0;
        /// Whether the page is readable.
        const R =   1 << 1;
        /// Whether the page is writable.
        const W =   1 << 2;
        /// Whether the page is executable.
        const X =   1 << 3;
        /// Whether the page is accessible to user mode.
        const U =   1 << 4;
        /// Designates a global mapping.
        const G =   1 << 5;
        /// Indicates the virtual page has been read, written, or fetched from
        /// since the last time the A bit was cleared.
        const A =   1 << 6;
        /// Indicates the virtual page has been written since the last time the
        /// D bit was cleared.
        const D =   1 << 7;
        // xuantie-c9xx specific flags
        // SO, C, B defined both in XuanTie-Openc910 and XuanTie-C906
        // SH only defined in XuanTie-Openc910
        /// SO – Strong ordered memory (1 << 63)
        #[cfg(feature = "xuantie-c9xx")]
        const SO =  (1 << 63);
        /// C – Cacheable  (1 << 62)
        #[cfg(feature = "xuantie-c9xx")]
        const C =   (1 << 62);
        /// B – Bufferable (1 << 61)
        #[cfg(feature = "xuantie-c9xx")]
        const B =   (1 << 61);
        /// SH – Shareable  (1 << 60)
        #[cfg(feature = "xuantie-c9xx")]
        const SH =  (1 << 60);

        /// xuantie-c9xx device memory flags
        #[cfg(feature = "xuantie-c9xx")]
        const XUANTIE_C9XX_DEVICE = Self::SO.bits() | Self::B.bits();
        /// xuantie-c9xx normal memory flags
        #[cfg(feature = "xuantie-c9xx")]
        const XUANTIE_C9XX_NORMAL = Self::C.bits() | Self::B.bits() | Self::SH.bits();
    }
}

impl From<PTEFlags> for MappingFlags {
    fn from(f: PTEFlags) -> Self {
        let mut ret = Self::empty();
        if !f.contains(PTEFlags::V) {
            return ret;
        }
        if f.contains(PTEFlags::R) {
            ret |= Self::READ;
        }
        if f.contains(PTEFlags::W) {
            ret |= Self::WRITE;
        }
        if f.contains(PTEFlags::X) {
            ret |= Self::EXECUTE;
        }
        if f.contains(PTEFlags::U) {
            ret |= Self::USER;
        }
        ret
    }
}

impl From<MappingFlags> for PTEFlags {
    fn from(f: MappingFlags) -> Self {
        if f.is_empty() {
            return Self::empty();
        }
        let mut ret = Self::V | Self::A | Self::D;
        if f.contains(MappingFlags::READ) {
            ret |= Self::R;
        }
        if f.contains(MappingFlags::WRITE) {
            ret |= Self::W;
        }
        if f.contains(MappingFlags::EXECUTE) {
            ret |= Self::X;
        }
        if f.contains(MappingFlags::USER) {
            ret |= Self::U;
        }

        #[cfg(feature = "xuantie-c9xx")]
        if f.contains(MappingFlags::DEVICE) {
            ret |= Self::XUANTIE_C9XX_DEVICE;
        } else {
            ret |= Self::XUANTIE_C9XX_NORMAL;
        }

        ret
    }
}

/// Sv39 and Sv48 page table entry for RV64 systems.
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct Rv64PTE(u64);

impl Rv64PTE {
    // bits 10..54
    const PHYS_ADDR_MASK: u64 = (1 << 54) - (1 << 10);

    /// Creates an empty descriptor with all bits set to zero.
    pub const fn empty() -> Self {
        Self(0)
    }
}

impl GenericPTE for Rv64PTE {
    fn new_page(paddr: PhysAddr, mflags: MappingFlags, _is_huge: bool) -> Self {
        let flags = PTEFlags::from(mflags);
        debug_assert!(flags.intersects(PTEFlags::R | PTEFlags::X));
        Self(flags.bits() as u64 | ((paddr.as_usize() >> 2) as u64 & Self::PHYS_ADDR_MASK))
    }

    fn new_table(paddr: PhysAddr) -> Self {
        Self(PTEFlags::V.bits() as u64 | ((paddr.as_usize() >> 2) as u64 & Self::PHYS_ADDR_MASK))
    }

    fn paddr(&self) -> PhysAddr {
        PhysAddr::from(((self.0 & Self::PHYS_ADDR_MASK) << 2) as usize)
    }

    fn flags(&self) -> MappingFlags {
        PTEFlags::from_bits_truncate(self.0 as usize).into()
    }

    fn set_paddr(&mut self, paddr: PhysAddr) {
        self.0 = (self.0 & !Self::PHYS_ADDR_MASK)
            | ((paddr.as_usize() as u64 >> 2) & Self::PHYS_ADDR_MASK);
    }

    fn set_flags(&mut self, flags: MappingFlags, _is_huge: bool) {
        let flags = PTEFlags::from(flags);
        debug_assert!(flags.intersects(PTEFlags::R | PTEFlags::X));
        self.0 = (self.0 & Self::PHYS_ADDR_MASK) | flags.bits() as u64;
    }

    fn bits(self) -> usize {
        self.0 as usize
    }

    fn is_unused(&self) -> bool {
        self.0 == 0
    }

    fn is_present(&self) -> bool {
        PTEFlags::from_bits_truncate(self.0 as usize).contains(PTEFlags::V)
    }

    fn is_huge(&self) -> bool {
        PTEFlags::from_bits_truncate(self.0 as usize).intersects(PTEFlags::R | PTEFlags::X)
    }

    fn clear(&mut self) {
        self.0 = 0
    }
}

impl fmt::Debug for Rv64PTE {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let mut f = f.debug_struct("Rv64PTE");
        f.field("raw", &self.0)
            .field("paddr", &self.paddr())
            .field("flags", &self.flags())
            .finish()
    }
}
