#![feature(
    maybe_uninit_slice,
    slice_ptr_get,
    step_trait,
    const_trait_impl,
    maybe_uninit_as_bytes,
    maybe_uninit_write_slice,
    negative_impls
)]
#![no_std]

extern crate alloc;

use core::{
    alloc::Layout,
    fmt,
    marker::PhantomData,
    mem::{size_of, MaybeUninit},
    ops::{Deref, DerefMut},
    slice::SliceIndex,
};

use address::Virtualize;
use kernel_arch::{mem::PhysicalMemoryAllocator, Architecture, ArchitectureImpl};
use libk_mm_interface::{
    address::{AsPhysicalAddress, PhysicalAddress},
    table::{MapAttributes, TableAllocator},
};
use phys::GlobalPhysicalAllocator;
use yggdrasil_abi::error::Error;

pub mod address;
pub mod device;
pub mod phys;
pub mod pointer;
pub mod process;

#[cfg(any(target_os = "none", rust_analyzer))]
pub mod heap;

pub use libk_mm_interface::{table, PageFaultKind};

pub struct TableAllocatorImpl;

impl TableAllocator for TableAllocatorImpl {
    fn allocate_page_table() -> Result<PhysicalAddress, Error> {
        phys::alloc_page()
    }

    unsafe fn free_page_table(address: PhysicalAddress) {
        phys::free_page(address)
    }
}

// TODO find a way to integrate this nicely with Architecture?
pub const L3_PAGE_SIZE: usize = 1 << 12;

#[cfg(not(target_arch = "x86"))]
pub const L2_PAGE_SIZE: usize = 1 << 21;
#[cfg(target_arch = "x86")]
pub const L2_PAGE_SIZE: usize = 1 << 22;

pub enum VirtualPage {
    OnDemand,
    Immediate(PhysicalAddress),
}

pub struct OnDemandPage {
    pub physical: PhysicalAddress,
    pub writeable: bool,
}

pub trait PageProvider: Send + Sync {
    fn ondemand_fetch(&self, offset: u64) -> Result<OnDemandPage, Error>;

    fn get_page(&self, offset: u64) -> Result<VirtualPage, Error>;

    fn release_page(&self, offset: u64, phys: PhysicalAddress, dirty: bool) -> Result<(), Error>;

    fn clone_page(
        &self,
        offset: u64,
        src_phys: PhysicalAddress,
        src_attrs: MapAttributes,
    ) -> Result<PhysicalAddress, Error>;
}

pub struct PageBox<
    T: ?Sized,
    A: PhysicalMemoryAllocator<Address = PhysicalAddress> = GlobalPhysicalAllocator,
> {
    value: *mut T,
    page_count: usize,
    _pd: PhantomData<A>,
}

pub struct PageSlice<T> {
    data: [T],
}

impl<T> PageBox<T, GlobalPhysicalAllocator> {
    pub fn new(init: T) -> Result<PageBox<T>, Error> {
        PageBox::new_in(init)
    }

    pub fn new_slice(item: T, count: usize) -> Result<PageBox<[T]>, Error>
    where
        T: Copy,
    {
        PageBox::new_slice_in(item, count)
    }

    pub fn new_slice_with<F: Fn(usize) -> T>(f: F, count: usize) -> Result<PageBox<[T]>, Error> {
        PageBox::new_slice_in_with(f, count)
    }

    pub fn new_uninit() -> Result<PageBox<MaybeUninit<T>>, Error> {
        PageBox::new_uninit_in()
    }

    pub fn new_uninit_slice(count: usize) -> Result<PageBox<[MaybeUninit<T>]>, Error> {
        PageBox::new_uninit_slice_in(count)
    }

    pub fn new_zeroed_slice(count: usize) -> Result<PageBox<[MaybeUninit<T>]>, Error> {
        let (base, page_count) = PageBox::<MaybeUninit<T>>::alloc_slice(count, true)?;
        let base_virt_ptr = base.virtualize() as *mut MaybeUninit<T>;
        let value = core::ptr::slice_from_raw_parts_mut(base_virt_ptr, count);
        let result = PageBox {
            value,
            page_count,
            _pd: PhantomData,
        };
        result.trace_created();
        Ok(result)
    }

    /// # Safety
    ///
    /// Unsafe: converts a raw physical address back into a [PageBox]. Only intended to be used
    /// for allocations previously converted to [PhysicalAddress] via [PageBox::into_physical_raw].
    pub unsafe fn from_physical_raw(address: PhysicalAddress) -> PageBox<T> {
        PageBox::from_physical_raw_in(address)
    }

    pub fn from_slice(slice: &[T]) -> Result<PageBox<[T]>, Error>
    where
        T: Copy,
    {
        let mut uninit = Self::new_uninit_slice(slice.len())?;
        uninit[..].write_copy_of_slice(slice);
        Ok(unsafe { uninit.assume_init_slice() })
    }
}

impl<T, A: PhysicalMemoryAllocator<Address = PhysicalAddress>> PageBox<T, A> {
    #[inline]
    fn alloc_slice(count: usize, zeroed: bool) -> Result<(PhysicalAddress, usize), Error> {
        // TODO hardcoded page sizes
        let layout = Layout::array::<T>(count).unwrap();
        let page_count = layout.size().div_ceil(L3_PAGE_SIZE);
        let base = A::allocate_contiguous_pages(page_count)?;
        if zeroed {
            let ptr = base.virtualize() as *mut u8;
            let slice = unsafe { core::slice::from_raw_parts_mut(ptr, page_count * L3_PAGE_SIZE) };
            slice.fill(0);
        }
        Ok((base, page_count))
    }

    #[inline]
    fn alloc() -> Result<(PhysicalAddress, usize), Error> {
        let page_count = size_of::<T>().div_ceil(L3_PAGE_SIZE);
        let phys = A::allocate_contiguous_pages(page_count)?;
        Ok((phys, page_count))
    }

    pub fn new_in(init: T) -> Result<PageBox<T, A>, Error> {
        let (base, page_count) = PageBox::<T, A>::alloc()?;
        let value = base.virtualize() as *mut T;

        unsafe {
            value.write(init);
        }

        let result = PageBox {
            value,
            page_count,
            _pd: PhantomData,
        };
        result.trace_created();
        Ok(result)
    }

    pub fn new_slice_in(item: T, count: usize) -> Result<PageBox<[T], A>, Error>
    where
        T: Copy,
    {
        let (base, page_count) = PageBox::<T, A>::alloc_slice(count, false)?;
        let base_virt_ptr = base.virtualize() as *mut T;
        let value = core::ptr::slice_from_raw_parts_mut(base_virt_ptr, count);

        for i in 0..count {
            unsafe {
                value.get_unchecked_mut(i).write(item);
            }
        }

        let result = PageBox {
            value,
            page_count,
            _pd: PhantomData,
        };
        result.trace_created();
        Ok(result)
    }

    pub fn new_slice_in_with<F: Fn(usize) -> T>(
        f: F,
        count: usize,
    ) -> Result<PageBox<[T], A>, Error> {
        let mut value = PageBox::<T, A>::new_uninit_slice_in(count)?;

        for i in 0..count {
            value[i].write(f(i));
        }

        Ok(unsafe { value.assume_init_slice() })
    }

    pub fn new_uninit_in() -> Result<PageBox<MaybeUninit<T>, A>, Error> {
        let (base, page_count) = PageBox::<MaybeUninit<T>, A>::alloc()?;
        let value = base.virtualize() as *mut MaybeUninit<T>;
        let result = PageBox {
            value,
            page_count,
            _pd: PhantomData,
        };
        result.trace_created();
        Ok(result)
    }

    pub fn new_uninit_slice_in(count: usize) -> Result<PageBox<[MaybeUninit<T>], A>, Error> {
        let (base, page_count) = PageBox::<MaybeUninit<T>, A>::alloc_slice(count, false)?;
        let base_virt_ptr = base.virtualize() as *mut MaybeUninit<T>;
        let value = core::ptr::slice_from_raw_parts_mut(base_virt_ptr, count);
        let result = PageBox {
            value,
            page_count,
            _pd: PhantomData,
        };
        result.trace_created();
        Ok(result)
    }

    /// # Safety
    ///
    /// Unsafe: converts a raw physical address back into a [PageBox]. Only intended to be used
    /// for allocations previously converted to [PhysicalAddress] via [PageBox::into_physical_raw].
    pub unsafe fn from_physical_raw_in(address: PhysicalAddress) -> PageBox<T, A> {
        let page_count = size_of::<T>().div_ceil(L3_PAGE_SIZE);
        let value = address.virtualize() as *mut T;
        PageBox {
            value,
            page_count,
            _pd: PhantomData,
        }
    }
}

impl<T: ?Sized, A: PhysicalMemoryAllocator<Address = PhysicalAddress>> PageBox<T, A> {
    #[inline]
    pub fn as_ptr(&self) -> *const T {
        self.value as _
    }

    pub fn into_physical_raw(self) -> PhysicalAddress {
        let address = unsafe { self.as_physical_address() };
        core::mem::forget(self);
        address
    }

    #[inline]
    fn trace_created(&self) {
        log::trace!(
            "Alloc PageBox<{}> @ {:p}, {}",
            core::any::type_name::<T>(),
            self.value,
            self.page_count
        );
    }

    #[inline]
    fn trace_dropped(&self) {
        log::trace!(
            "Free PageBox<{}> @ {:p}, {}",
            core::any::type_name::<T>(),
            self.value,
            self.page_count
        );
    }
}

impl<T> PageBox<T> {
    pub fn from_iter_exact<I: IntoIterator<Item = T>>(it: I) -> Result<PageBox<[T]>, Error>
    where
        I::IntoIter: ExactSizeIterator,
    {
        let it = it.into_iter();
        let mut slice = PageBox::<T>::new_uninit_slice(it.len())?;
        for (i, item) in it.enumerate() {
            slice[i].write(item);
        }
        let slice = unsafe { slice.assume_init_slice() };
        Ok(slice)
    }
}

impl<T, A: PhysicalMemoryAllocator<Address = PhysicalAddress>> PageBox<[T], A> {
    pub fn as_slice(&self) -> &PageSlice<T> {
        unsafe { core::mem::transmute(&self[..]) }
    }

    pub fn as_slice_mut(&mut self) -> &mut PageSlice<T> {
        unsafe { core::mem::transmute(&mut self[..]) }
    }
}

impl<T, A: PhysicalMemoryAllocator<Address = PhysicalAddress>> PageBox<MaybeUninit<T>, A> {
    /// Consumes the [PageBox], returning a new one with [MaybeUninit] removed.
    ///
    /// # Safety
    ///
    /// See [MaybeUninit::assume_init_mut].
    pub unsafe fn assume_init(self) -> PageBox<T, A> {
        // SAFETY: Memory-safe, as:
        // 1. MaybeUninit<T> is transparent
        // 2. self.value still points to the same memory and is not deallocated
        let page_count = self.page_count;
        let value = MaybeUninit::assume_init_mut(&mut *self.value);

        // Prevent deallocation of the PageBox with MaybeUninit
        core::mem::forget(self);

        PageBox {
            value,
            page_count,
            _pd: PhantomData,
        }
    }

    pub fn as_bytes_mut(p: &mut Self) -> &mut PageSlice<MaybeUninit<u8>> {
        unsafe { core::mem::transmute(p.as_bytes_mut()) }
    }
}

impl<T, A: PhysicalMemoryAllocator<Address = PhysicalAddress>> PageBox<[MaybeUninit<T>], A> {
    /// Consumes the [PageBox], returning a new one with [MaybeUninit] removed.
    ///
    /// # Safety
    ///
    /// See [MaybeUninit::slice_assume_init_mut].
    pub unsafe fn assume_init_slice(self) -> PageBox<[T], A> {
        // SAFETY: Memory-safe, as:
        // 1. MaybeUninit<T> is transparent
        // 2. self.value still points to the same memory and is not deallocated
        let page_count = self.page_count;
        let value = (&mut *self.value).assume_init_mut();

        core::mem::forget(self);

        PageBox {
            value,
            page_count,
            _pd: PhantomData,
        }
    }

    /// Returns a reference to the slice data with [MaybeUninit] removed.
    ///
    /// # Safety
    ///
    /// See [MaybeUninit::slice_assume_init_ref]
    pub unsafe fn assume_init_slice_ref(&self) -> &[T] {
        (&*self.value).assume_init_ref()
    }

    /// Returns a mutable reference to the slice data with [MaybeUninit] removed.
    ///
    /// # Safety
    ///
    /// See [MaybeUninit::slice_assume_init_mut]
    pub unsafe fn assume_init_slice_mut(&mut self) -> &mut [T] {
        (&mut *self.value).assume_init_mut()
    }

    /// Fills a slice of MaybeUninit<T> with zeroes.
    ///
    /// # Safety
    ///
    /// Unsafe: will not drop possibly previously written data. Only meant for [Copy] and other
    /// trivial types.
    pub unsafe fn zero(p: &mut Self) {
        let ptr = p.as_mut_ptr() as *mut u8;
        let slice = core::slice::from_raw_parts_mut(ptr, p.page_count * L3_PAGE_SIZE);
        slice.fill(0);
    }
}

impl<T: ?Sized, A: PhysicalMemoryAllocator<Address = PhysicalAddress>> AsPhysicalAddress
    for PageBox<T, A>
{
    #[inline]
    unsafe fn as_physical_address(&self) -> PhysicalAddress {
        PhysicalAddress::from_virtualized(self.value.addr())
    }
}

impl<T: ?Sized, A: PhysicalMemoryAllocator<Address = PhysicalAddress>> Deref for PageBox<T, A> {
    type Target = T;

    #[inline(always)]
    fn deref(&self) -> &Self::Target {
        unsafe { &*self.value }
    }
}

impl<T: ?Sized, A: PhysicalMemoryAllocator<Address = PhysicalAddress>> DerefMut for PageBox<T, A> {
    #[inline(always)]
    fn deref_mut(&mut self) -> &mut Self::Target {
        unsafe { &mut *self.value }
    }
}

impl<T: ?Sized, A: PhysicalMemoryAllocator<Address = PhysicalAddress>> Drop for PageBox<T, A> {
    fn drop(&mut self) {
        self.trace_dropped();
        unsafe {
            core::ptr::drop_in_place(self.value);
        }
        // SAFETY: Safe, pointer obtained through "virtualize"
        let base = PhysicalAddress::from_virtualized(self.value.addr());
        for i in 0..self.page_count {
            // SAFETY: Safe, page allocated only by this PageBox
            unsafe {
                A::free_page(base.add(L3_PAGE_SIZE * i));
            }
        }
    }
}

impl<T: ?Sized, A: PhysicalMemoryAllocator<Address = PhysicalAddress>> fmt::Pointer
    for PageBox<T, A>
{
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.value.fmt(f)
    }
}

impl<T: ?Sized + fmt::Debug, A: PhysicalMemoryAllocator<Address = PhysicalAddress>> fmt::Debug
    for PageBox<T, A>
{
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Debug::fmt(self.deref(), f)
    }
}

impl<T: ?Sized + fmt::Display, A: PhysicalMemoryAllocator<Address = PhysicalAddress>> fmt::Display
    for PageBox<T, A>
{
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Display::fmt(self.deref(), f)
    }
}

unsafe impl<T: ?Sized + Send, A: PhysicalMemoryAllocator<Address = PhysicalAddress>> Send
    for PageBox<T, A>
{
}
unsafe impl<T: ?Sized + Sync, A: PhysicalMemoryAllocator<Address = PhysicalAddress>> Sync
    for PageBox<T, A>
{
}

impl<T> PageSlice<T> {
    pub fn subslice_mut<R: SliceIndex<[T], Output = [T]>>(
        &mut self,
        index: R,
    ) -> &mut PageSlice<T> {
        unsafe { core::mem::transmute(&mut self.data[index]) }
    }

    pub fn subslice<R: SliceIndex<[T], Output = [T]>>(&self, index: R) -> &PageSlice<T> {
        unsafe { core::mem::transmute(&self.data[index]) }
    }
}

impl<T> AsPhysicalAddress for PageSlice<T> {
    unsafe fn as_physical_address(&self) -> PhysicalAddress {
        PhysicalAddress::from_virtualized(self.data.as_ptr().addr())
    }
}

impl<T> Deref for PageSlice<T> {
    type Target = [T];

    fn deref(&self) -> &Self::Target {
        &self.data
    }
}

impl<T> DerefMut for PageSlice<T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.data
    }
}

pub fn flush_cache_data<T>(data: *const T, write: bool) {
    if write {
        ArchitectureImpl::store_barrier();
    }
    ArchitectureImpl::flush_virtual_range(data.addr()..data.addr() + size_of::<T>());
    ArchitectureImpl::memory_barrier();
}

pub fn flush_cache_data_slice<T>(data: *const [T], write: bool) {
    if write {
        ArchitectureImpl::store_barrier();
    }
    ArchitectureImpl::flush_virtual_range(data.addr()..data.addr() + size_of::<T>() * data.len());
    ArchitectureImpl::memory_barrier();
}
