use std::{
    mem,
    ops::{Deref, DerefMut, Range},
    ptr::NonNull,
};

use yggdrasil_rt::mem::{MappingFlags, MappingSource};

use crate::error::Error;

pub struct Mapping {
    data: NonNull<[u8]>,
}

impl Mapping {
    pub fn new(size: usize, flags: MappingFlags) -> Result<Mapping, Error> {
        let size = (size + 0xFFF) & !0xFFF;
        let base =
            unsafe { yggdrasil_rt::sys::map_memory(None, size, flags, &MappingSource::Anonymous) }
                .map_err(Error::MemoryMap)?;
        let base_ptr =
            unsafe { NonNull::new_unchecked(core::ptr::with_exposed_provenance_mut(base)) };
        let data = NonNull::slice_from_raw_parts(base_ptr, size);

        Ok(Self { data })
    }

    pub fn leak(self) -> usize {
        let base = self.data.addr().into();
        mem::forget(self);
        base
    }

    pub fn qword(&mut self, offset: u64) -> NonNull<i64> {
        unsafe { self.data.as_non_null_ptr().add(offset as usize).cast() }
    }

    pub fn dword(&mut self, offset: u64) -> NonNull<i32> {
        unsafe { self.data.as_non_null_ptr().add(offset as usize).cast() }
    }

    pub fn base(&self) -> usize {
        self.data.addr().into()
    }

    pub fn range(&self) -> Range<usize> {
        self.base()..self.base() + self.len()
    }
}

impl Drop for Mapping {
    fn drop(&mut self) {
        let base = self.data.addr().into();
        let size = self.data.len();

        unsafe {
            yggdrasil_rt::sys::unmap_memory(base, size).ok();
        }
    }
}

impl Deref for Mapping {
    type Target = [u8];

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

impl DerefMut for Mapping {
    fn deref_mut(&mut self) -> &mut Self::Target {
        unsafe { self.data.as_mut() }
    }
}
