use std::ffi::CStr;
use std::marker::PhantomPinned;
use std::pin::Pin;
use std::ptr::{addr_of, addr_of_mut, null_mut, NonNull};
use spa_sys::{spa_callbacks, spa_hook, spa_list};
use pipewire_wrapper_proc_macro::{RawWrapper, Wrapper};
use crate::spa::list::{List, ListElement, ListRef};
use crate::wrapper::RawWrapper;
#[derive(RawWrapper, Debug)]
#[repr(transparent)]
pub struct HookRef {
    #[raw]
    raw: spa_sys::spa_hook,
}
#[derive(Wrapper, Debug)]
pub struct Hook {
    #[raw_wrapper]
    ref_: NonNull<HookRef>,
    hook: spa_hook,
    pinned: PhantomPinned,
}
#[derive(RawWrapper, Debug)]
#[repr(transparent)]
pub struct HookListRef {
    #[raw]
    raw: spa_sys::spa_hook_list,
}
#[derive(RawWrapper, Debug)]
#[repr(transparent)]
pub struct CallbacksRef {
    #[raw]
    raw: spa_sys::spa_callbacks,
}
#[derive(RawWrapper, Debug)]
#[repr(transparent)]
pub struct InterfaceRef {
    #[raw]
    raw: spa_sys::spa_interface,
}
impl InterfaceRef {
    pub fn type_(&self) -> Option<&CStr> {
        unsafe { self.raw.type_.as_ref().map(|ptr| CStr::from_ptr(ptr)) }
    }
    pub fn version(&self) -> u32 {
        self.raw.version
    }
    pub fn cb(&self) -> &CallbacksRef {
        unsafe { CallbacksRef::from_raw_ptr(&self.raw.cb as *const spa_callbacks) }
    }
    pub fn version_min(&self, version_min: u32) -> bool {
        version_min == 0 || self.version() > version_min - 1
    }
}
impl CallbacksRef {
    pub fn funcs<M>(&self) -> *const M {
        self.raw.funcs as *const M
    }
    pub fn data(&self) -> *mut ::std::os::raw::c_void {
        self.raw.data
    }
    pub fn init<M>(&mut self, funcs: *const M, data: *mut ::std::os::raw::c_void) {
        self.raw.funcs = funcs as *const _;
        self.raw.data = data;
    }
}
impl ListElement for HookRef {
    fn as_list_ptr(&self) -> *mut spa_list {
        addr_of!(self.raw.link) as *mut _
    }
    fn from_list_ptr(ptr: *mut spa_list) -> *mut Self {
        ptr as *mut Self
    }
}
impl List for HookListRef {
    type Elem = HookRef;
    fn as_list_ptr(&self) -> *mut spa_list {
        addr_of!(self.raw.list) as *mut _
    }
    fn from_list_ptr(ptr: *mut spa_list) -> *mut Self {
        ptr as *mut Self
    }
}
impl HookRef {
    pub fn remove(&mut self) {
        unsafe {
            ListElement::remove(self);
        }
        if let Some(removed_callback) = self.raw.removed {
            unsafe {
                removed_callback(self.as_raw_ptr());
            }
        }
    }
}
impl HookListRef {
    pub fn clean(&mut self) {
        unsafe {
            while let Some(first) = self.first() {
                HookRef::remove(first)
            }
        }
    }
}
impl Drop for Hook {
    fn drop(&mut self) {
        self.remove()
    }
}
impl Hook {
    pub fn new() -> Pin<Box<Self>> {
        let spa_hook = spa_hook {
            link: spa_list {
                next: null_mut(),
                prev: null_mut(),
            },
            cb: spa_callbacks {
                funcs: null_mut(),
                data: null_mut(),
            },
            removed: None,
            priv_: null_mut(),
        };
        let mut hook = Box::new(Self {
            ref_: NonNull::dangling(),
            hook: spa_hook,
            pinned: PhantomPinned,
        });
        unsafe {
            hook.ref_ = NonNull::new(addr_of_mut!(hook.hook) as *mut HookRef).unwrap();
            hook.init_detached();
        }
        Box::into_pin(hook)
    }
}