use std::{
    ffi::c_void,
    io,
    ops::{Deref, DerefMut},
    os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd},
    ptr::null_mut,
};

use crate::sys;

pub struct SharedMemoryImpl {
    fd: OwnedFd,
    size: usize,
}

pub struct FileMappingImpl {
    fd: OwnedFd,
    pointer: *mut c_void,
    size: usize,
}

impl sys::SharedMemory for SharedMemoryImpl {
    type Mapping = FileMappingImpl;

    fn map(self) -> io::Result<Self::Mapping> {
        <FileMappingImpl as sys::FileMapping>::map(self.fd, self.size, true)
    }

    fn new(size: usize) -> io::Result<Self> {
        let fd = unsafe { libc::memfd_create(c"cross-shm".as_ptr(), libc::MFD_CLOEXEC) };
        if fd < 0 {
            return Err(io::Error::last_os_error());
        }
        let fd = unsafe { OwnedFd::from_raw_fd(fd) };
        if unsafe { libc::ftruncate(fd.as_raw_fd(), size as i64) } != 0 {
            return Err(io::Error::last_os_error());
        }
        Ok(Self { fd, size })
    }
}

impl AsRawFd for SharedMemoryImpl {
    fn as_raw_fd(&self) -> RawFd {
        self.fd.as_raw_fd()
    }
}

impl sys::FileMapping for FileMappingImpl {
    fn map<F: Into<OwnedFd>>(file: F, size: usize, write: bool) -> io::Result<Self> {
        let fd: OwnedFd = file.into();
        let mut flags = libc::PROT_READ;
        if write {
            flags |= libc::PROT_WRITE;
        }
        let pointer =
            unsafe { libc::mmap(null_mut(), size, flags, libc::MAP_SHARED, fd.as_raw_fd(), 0) };
        if pointer == libc::MAP_FAILED {
            return Err(io::Error::last_os_error());
        }
        Ok(Self { fd, pointer, size })
    }
}

impl Drop for FileMappingImpl {
    fn drop(&mut self) {
        unsafe {
            libc::munmap(self.pointer, self.size);
        }
    }
}

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

    fn deref(&self) -> &Self::Target {
        unsafe { core::slice::from_raw_parts(self.pointer.cast(), self.size) }
    }
}

impl DerefMut for FileMappingImpl {
    fn deref_mut(&mut self) -> &mut Self::Target {
        unsafe { core::slice::from_raw_parts_mut(self.pointer.cast(), self.size) }
    }
}

impl AsRawFd for FileMappingImpl {
    fn as_raw_fd(&self) -> RawFd {
        self.fd.as_raw_fd()
    }
}
