//! ACPI memory IO and management functions

use core::{
    alloc::{AllocError, Allocator, GlobalAlloc, Layout},
    ptr::NonNull,
};

use libk_mm::{address::PhysicalAddress, device::DeviceMemoryMapping, heap::GLOBAL_HEAP};

#[derive(Clone, Copy)]
#[doc(hidden)]
pub struct AcpiAllocator;

unsafe impl Allocator for AcpiAllocator {
    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
        let ptr = unsafe { GLOBAL_HEAP.alloc(layout) };
        log::trace!("ACPI alloc: {:?} -> {:p}", layout, ptr);

        if ptr.is_null() {
            Err(AllocError)
        } else {
            unsafe {
                Ok(NonNull::slice_from_raw_parts(
                    NonNull::new_unchecked(ptr),
                    layout.size(),
                ))
            }
        }
    }

    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
        log::trace!("ACPI dealloc: {:?}, {:?}", ptr, layout);
        unsafe { GLOBAL_HEAP.dealloc(ptr.as_ptr(), layout) };
    }
}

// TODO don't map memory as device if not necessary
/// # Safety
///
/// Allows direct reads from physical memory, unsafe
pub unsafe fn read_memory<T>(address: PhysicalAddress) -> T {
    let io =
        unsafe { DeviceMemoryMapping::map(address, size_of::<T>(), Default::default()).unwrap() };
    let address = io.address();

    unsafe {
        if address % align_of::<T>() == 0 {
            (address as *const T).read_volatile()
        } else {
            (address as *const T).read_unaligned()
        }
    }
}

/// # Safety
///
/// Allows direct writes to physical memory, unsafe
pub unsafe fn write_memory<T>(address: PhysicalAddress, value: T) {
    let io =
        unsafe { DeviceMemoryMapping::map(address, size_of::<T>(), Default::default()).unwrap() };
    let address = io.address();

    unsafe {
        if address % align_of::<T>() == 0 {
            (address as *mut T).write_volatile(value)
        } else {
            (address as *mut T).write_unaligned(value)
        }
    }
}
