use super::MemObjectType;
use crate::{buffer::flags::MemFlags, context::RawContext, core::*, non_null_const};
use blaze_proc::docfg;
use opencl_sys::*;
use std::{ffi::c_void, mem::MaybeUninit, ptr::NonNull};
#[derive(Debug, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct RawMemObject(NonNull<c_void>);
impl RawMemObject {
    #[inline(always)]
    pub const unsafe fn from_id_unchecked(id: cl_mem) -> Self {
        Self(NonNull::new_unchecked(id))
    }
    #[inline(always)]
    pub const unsafe fn from_id(id: cl_mem) -> Option<Self> {
        match non_null_const(id) {
            Some(ptr) => Some(Self(ptr)),
            None => None,
        }
    }
    #[inline(always)]
    pub unsafe fn retain(&self) -> Result<()> {
        tri!(clRetainMemObject(self.id()));
        Ok(())
    }
    #[inline(always)]
    pub const fn id(&self) -> cl_mem {
        self.0.as_ptr()
    }
    #[inline(always)]
    pub const fn id_ref(&self) -> &cl_mem {
        unsafe { core::mem::transmute(&self.0) }
    }
    #[inline(always)]
    pub fn id_ref_mut(&mut self) -> &mut cl_mem {
        unsafe { core::mem::transmute(&mut self.0) }
    }
    #[inline(always)]
    pub fn ty(&self) -> Result<MemObjectType> {
        self.get_info(CL_MEM_TYPE)
    }
    #[docfg(feature = "cl1_1")]
    #[inline(always)]
    pub fn associated_memobject(&self) -> Result<Option<RawMemObject>> {
        let v = self.get_info::<cl_mem>(opencl_sys::CL_MEM_ASSOCIATED_MEMOBJECT)?;
        unsafe {
            if let Some(id) = Self::from_id(v) {
                id.retain()?;
                return Ok(Some(id));
            }
            return Ok(None);
        }
    }
    #[inline(always)]
    pub fn flags(&self) -> Result<MemFlags> {
        let flags = self.get_info(CL_MEM_FLAGS)?;
        Ok(MemFlags::from_bits(flags))
    }
    #[inline(always)]
    pub fn size(&self) -> Result<usize> {
        self.get_info(CL_MEM_SIZE)
    }
    #[inline(always)]
    pub fn host_ptr(&self) -> Result<Option<NonNull<c_void>>> {
        self.get_info(CL_MEM_HOST_PTR).map(NonNull::new)
    }
    #[inline(always)]
    pub fn map_count(&self) -> Result<u32> {
        self.get_info(CL_MEM_MAP_COUNT)
    }
    #[inline(always)]
    pub fn reference_count(&self) -> Result<u32> {
        self.get_info(CL_MEM_REFERENCE_COUNT)
    }
    #[inline(always)]
    pub fn context(&self) -> Result<RawContext> {
        let ctx = self.get_info::<cl_context>(CL_MEM_CONTEXT)?;
        unsafe {
            tri!(clRetainContext(ctx));
            Ok(RawContext::from_id_unchecked(ctx))
        }
    }
    #[docfg(feature = "cl1_1")]
    #[inline(always)]
    pub fn offset(&self) -> Result<usize> {
        self.get_info(opencl_sys::CL_MEM_OFFSET)
    }
    #[docfg(feature = "cl2")]
    #[inline(always)]
    pub fn uses_svm_pointer(&self) -> Result<bool> {
        let v = self.get_info::<opencl_sys::cl_bool>(opencl_sys::CL_MEM_USES_SVM_POINTER)?;
        Ok(v != 0)
    }
    #[inline]
    pub(super) fn get_info<O: Copy>(&self, ty: cl_mem_info) -> Result<O> {
        let mut result = MaybeUninit::<O>::uninit();
        unsafe {
            tri!(clGetMemObjectInfo(
                self.id(),
                ty,
                core::mem::size_of::<O>(),
                result.as_mut_ptr().cast(),
                core::ptr::null_mut()
            ));
            Ok(result.assume_init())
        }
    }
}
#[docfg(feature = "cl1_1")]
impl RawMemObject {
    #[inline(always)]
    pub fn on_destruct(&self, f: impl 'static + FnOnce() + Send) -> Result<()> {
        let f = Box::new(f) as Box<_>;
        self.on_destruct_boxed(f)
    }
    #[inline(always)]
    pub fn on_destruct_boxed(&self, f: Box<dyn FnOnce() + Send>) -> Result<()> {
        let data = Box::into_raw(Box::new(f));
        unsafe { self.on_destruct_raw(destructor_callback, data.cast()) }
    }
    #[inline(always)]
    pub unsafe fn on_destruct_raw(
        &self,
        f: unsafe extern "C" fn(memobj: cl_mem, user_data: *mut c_void),
        user_data: *mut c_void,
    ) -> Result<()> {
        tri!(opencl_sys::clSetMemObjectDestructorCallback(
            self.id(),
            Some(f),
            user_data
        ));
        Ok(())
    }
}
impl Clone for RawMemObject {
    #[inline(always)]
    fn clone(&self) -> Self {
        unsafe { tri_panic!(clRetainMemObject(self.id())) }
        Self(self.0)
    }
}
impl Drop for RawMemObject {
    #[inline(always)]
    fn drop(&mut self) {
        unsafe { tri_panic!(clReleaseMemObject(self.id())) }
    }
}
unsafe impl Send for RawMemObject {}
unsafe impl Sync for RawMemObject {}
#[cfg(feature = "cl1_1")]
unsafe extern "C" fn destructor_callback(_memobj: cl_mem, user_data: *mut c_void) {
    let f = *Box::from_raw(user_data as *mut Box<dyn FnOnce() + Send>);
    f()
}