use std::ffi::CString;
use std::ops::{Deref, DerefMut};
use std::os::fd::{AsRawFd, OwnedFd};
use std::sync::Arc;

use zbus::zvariant;

use crate::{BinaryData, SafeConversion};

#[derive(Debug)]
pub struct SharedMemory {
    memfd: OwnedFd,
    pub mmap: memmap::MmapMut,
}

impl SharedMemory {
    pub fn new(size: u64) -> Self {
        let memfd = nix::sys::memfd::memfd_create(
            &CString::new("glycin-frame").unwrap(),
            nix::sys::memfd::MemFdCreateFlag::MFD_CLOEXEC
                | nix::sys::memfd::MemFdCreateFlag::MFD_ALLOW_SEALING,
        )
        .expect("Failed to create memfd");

        nix::unistd::ftruncate(&memfd, size.try_into().expect("Required memory too large"))
            .expect("Failed to set memfd size");

        let raw_fd = memfd.as_raw_fd();
        let mmap = unsafe { memmap::MmapMut::map_mut(raw_fd) }.expect("Mailed to mmap memfd");

        Self { mmap, memfd }
    }

    pub fn into_binary_data(self) -> BinaryData {
        let owned_fd = zvariant::OwnedFd::from(self.memfd);
        BinaryData {
            memfd: Arc::new(owned_fd),
        }
    }
}

impl<T: AsRef<[u8]>> From<T> for SharedMemory {
    fn from(value: T) -> Self {
        let mut shared_memory = SharedMemory::new(
            value
                .as_ref()
                .len()
                .try_u64()
                .expect("Required memory too large"),
        );

        shared_memory.copy_from_slice(value.as_ref());

        shared_memory
    }
}

impl<T: AsRef<[u8]>> From<T> for BinaryData {
    fn from(value: T) -> Self {
        SharedMemory::from(value).into_binary_data()
    }
}

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

    fn deref(&self) -> &[u8] {
        self.mmap.deref()
    }
}

impl DerefMut for SharedMemory {
    fn deref_mut(&mut self) -> &mut [u8] {
        self.mmap.deref_mut()
    }
}
