// Take a look at the license at the top of the repository in the LICENSE file.

// TODO: support marshaller.

use std::{mem, ptr, slice};

use libc::{c_uint, c_void};

use crate::{prelude::*, translate::*, value::FromValue, Type, Value};

wrapper! {
    #[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
    #[doc(alias = "GClosure")]
    pub struct Closure(Shared<gobject_ffi::GClosure>);

    match fn {
        ref => |ptr| {
            gobject_ffi::g_closure_ref(ptr);
            gobject_ffi::g_closure_sink(ptr);
        },
        unref => |ptr| gobject_ffi::g_closure_unref(ptr),
        type_ => || gobject_ffi::g_closure_get_type(),
    }
}

#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub struct RustClosure(Closure);

impl RustClosure {
    // rustdoc-stripper-ignore-next
    /// Creates a new closure around a Rust closure.
    ///
    /// See [`glib::closure!`](macro@crate::closure) for a way to create a closure with concrete
    /// types.
    ///
    /// # Panics
    ///
    /// Invoking the closure with wrong argument types or returning the wrong return value type
    /// will panic.
    ///
    /// # Example
    ///
    /// ```
    /// use glib::prelude::*;
    ///
    /// let closure = glib::RustClosure::new(|values| {
    ///     let x = values[0].get::<i32>().unwrap();
    ///     Some((x + 1).to_value())
    /// });
    ///
    /// assert_eq!(
    ///     closure.invoke::<i32>(&[&1i32]),
    ///     2,
    /// );
    /// ```
    #[doc(alias = "g_closure_new")]
    pub fn new<F: Fn(&[Value]) -> Option<Value> + Send + Sync + 'static>(callback: F) -> Self {
        Self(Closure::new(callback))
    }

    // rustdoc-stripper-ignore-next
    /// Creates a new closure around a Rust closure.
    ///
    /// See [`glib::closure_local!`](crate::closure_local) for a way to create a closure with
    /// concrete types.
    ///
    /// # Panics
    ///
    /// Invoking the closure with wrong argument types or returning the wrong return value type
    /// will panic.
    ///
    /// Invoking the closure from a different thread than this one will panic.
    #[doc(alias = "g_closure_new")]
    pub fn new_local<F: Fn(&[Value]) -> Option<Value> + 'static>(callback: F) -> Self {
        Self(Closure::new_local(callback))
    }

    // rustdoc-stripper-ignore-next
    /// Invokes the closure with the given arguments.
    ///
    /// For invalidated closures this returns the "default" value of the return type. For nullable
    /// types this is `None`, which means that e.g. requesting `R = String` will panic will `R =
    /// Option<String>` will return `None`.
    ///
    /// # Panics
    ///
    /// The argument types and return value type must match the ones expected by the closure or
    /// otherwise this function panics.
    #[doc(alias = "g_closure_invoke")]
    pub fn invoke<R: TryFromClosureReturnValue>(&self, values: &[&dyn ToValue]) -> R {
        let values = values
            .iter()
            .copied()
            .map(ToValue::to_value)
            .collect::<smallvec::SmallVec<[_; 10]>>();

        R::try_from_closure_return_value(self.invoke_with_values(R::static_type(), &values))
            .expect("Invalid return value")
    }

    // rustdoc-stripper-ignore-next
    /// Invokes the closure with the given arguments.
    ///
    /// For invalidated closures this returns the "default" value of the return type.
    ///
    /// # Panics
    ///
    /// The argument types and return value type must match the ones expected by the closure or
    /// otherwise this function panics.
    #[doc(alias = "g_closure_invoke")]
    pub fn invoke_with_values(&self, return_type: Type, values: &[Value]) -> Option<Value> {
        unsafe { self.0.invoke_with_values(return_type, values) }
    }

    // rustdoc-stripper-ignore-next
    /// Invalidates the closure.
    ///
    /// Invoking an invalidated closure has no effect.
    #[doc(alias = "g_closure_invalidate")]
    pub fn invalidate(&self) {
        self.0.invalidate();
    }
}

impl From<RustClosure> for Closure {
    #[inline]
    fn from(c: RustClosure) -> Self {
        c.0
    }
}

impl AsRef<Closure> for RustClosure {
    #[inline]
    fn as_ref(&self) -> &Closure {
        &self.0
    }
}

impl AsRef<Closure> for Closure {
    #[inline]
    fn as_ref(&self) -> &Closure {
        self
    }
}

impl Closure {
    // rustdoc-stripper-ignore-next
    /// Creates a new closure around a Rust closure.
    ///
    /// Note that [`RustClosure`] provides more convenient and non-unsafe API for invoking
    /// closures. This type mostly exists for FFI interop.
    ///
    /// # Panics
    ///
    /// Invoking the closure with wrong argument types or returning the wrong return value type
    /// will panic.
    ///
    ///
    /// # Example
    ///
    /// ```
    /// use glib::prelude::*;
    ///
    /// let closure = glib::Closure::new(|values| {
    ///     let x = values[0].get::<i32>().unwrap();
    ///     Some((x + 1).to_value())
    /// });
    ///
    /// // Invoking non-Rust closures is unsafe because of possibly missing
    /// // argument and return value type checks.
    /// let res = unsafe {
    ///     closure
    ///         .invoke_with_values(glib::Type::I32, &[1i32.to_value()])
    ///         .and_then(|v| v.get::<i32>().ok())
    ///         .expect("Invalid return value")
    /// };
    ///
    /// assert_eq!(res, 2);
    /// ```
    #[doc(alias = "g_closure_new")]
    pub fn new<F: Fn(&[Value]) -> Option<Value> + Send + Sync + 'static>(callback: F) -> Self {
        unsafe { Self::new_unsafe(callback) }
    }

    // rustdoc-stripper-ignore-next
    /// Creates a new closure around a Rust closure.
    ///
    /// Note that [`RustClosure`] provides more convenient and non-unsafe API for invoking
    /// closures. This type mostly exists for FFI interop.
    ///
    /// # Panics
    ///
    /// Invoking the closure with wrong argument types or returning the wrong return value type
    /// will panic.
    ///
    /// Invoking the closure from a different thread than this one will panic.
    #[doc(alias = "g_closure_new")]
    pub fn new_local<F: Fn(&[Value]) -> Option<Value> + 'static>(callback: F) -> Self {
        let callback = crate::thread_guard::ThreadGuard::new(callback);

        unsafe { Self::new_unsafe(move |values| (callback.get_ref())(values)) }
    }

    // rustdoc-stripper-ignore-next
    /// Creates a new closure around a Rust closure.
    ///
    /// # Safety
    ///
    /// The captured variables of the closure must stay valid as long as the return value of this
    /// constructor does, and it must be valid to call the closure from any thread that is used by
    /// callers.
    #[doc(alias = "g_closure_new")]
    pub unsafe fn new_unsafe<F: Fn(&[Value]) -> Option<Value>>(callback: F) -> Self {
        unsafe extern "C" fn marshal<F>(
            _closure: *mut gobject_ffi::GClosure,
            return_value: *mut gobject_ffi::GValue,
            n_param_values: c_uint,
            param_values: *const gobject_ffi::GValue,
            _invocation_hint: *mut c_void,
            marshal_data: *mut c_void,
        ) where
            F: Fn(&[Value]) -> Option<Value>,
        {
            let values = if n_param_values == 0 {
                &[]
            } else {
                slice::from_raw_parts(param_values as *const _, n_param_values as usize)
            };
            let callback: &F = &*(marshal_data as *mut _);
            let result = callback(values);

            if return_value.is_null() {
                assert!(
                    result.is_none(),
                    "Closure returned a return value but the caller did not expect one"
                );
            } else {
                let return_value = &mut *(return_value as *mut Value);
                match result {
                    Some(result) => {
                        assert!(
                            result.type_().is_a(return_value.type_()),
                            "Closure returned a value of type {} but caller expected {}",
                            result.type_(),
                            return_value.type_()
                        );
                        *return_value = result;
                    }
                    None if return_value.type_() == Type::INVALID => (),
                    None => {
                        panic!(
                            "Closure returned no value but the caller expected a value of type {}",
                            return_value.type_()
                        );
                    }
                }
            }
        }

        unsafe extern "C" fn finalize<F>(
            notify_data: *mut c_void,
            _closure: *mut gobject_ffi::GClosure,
        ) where
            F: Fn(&[Value]) -> Option<Value>,
        {
            let _callback: Box<F> = Box::from_raw(notify_data as *mut _);
            // callback is dropped here.
        }

        // Due to bitfields we have to do our own calculations here for the size of the GClosure:
        // - 4: 32 bits in guint bitfields at the beginning
        // - padding due to alignment needed for the following pointer
        // - 3 * size_of<*mut c_void>: 3 pointers
        // We don't store any custom data ourselves in the GClosure
        let size = u32::max(4, mem::align_of::<*mut c_void>() as u32)
            + 3 * mem::size_of::<*mut c_void>() as u32;
        let closure = gobject_ffi::g_closure_new_simple(size, ptr::null_mut());
        let callback = Box::new(callback);
        let ptr: *mut F = Box::into_raw(callback);
        let ptr: *mut c_void = ptr as *mut _;
        gobject_ffi::g_closure_set_meta_marshal(closure, ptr, Some(marshal::<F>));
        gobject_ffi::g_closure_add_finalize_notifier(closure, ptr, Some(finalize::<F>));
        from_glib_none(closure)
    }

    // rustdoc-stripper-ignore-next
    /// Invokes the closure with the given arguments.
    ///
    /// For invalidated closures this returns the "default" value of the return type.
    ///
    /// # Safety
    ///
    /// The argument types and return value type must match the ones expected by the closure or
    /// otherwise the behaviour is undefined.
    ///
    /// Closures created from Rust via e.g. [`Closure::new`] will panic on type mismatches but
    /// this is not guaranteed for closures created from other languages.
    #[doc(alias = "g_closure_invoke")]
    pub unsafe fn invoke_with_values(&self, return_type: Type, values: &[Value]) -> Option<Value> {
        let mut result = if return_type == Type::UNIT {
            Value::uninitialized()
        } else {
            Value::from_type(return_type)
        };
        let result_ptr = if return_type == Type::UNIT {
            ptr::null_mut()
        } else {
            result.to_glib_none_mut().0
        };

        gobject_ffi::g_closure_invoke(
            self.to_glib_none().0,
            result_ptr,
            values.len() as u32,
            mut_override(values.as_ptr()) as *mut gobject_ffi::GValue,
            ptr::null_mut(),
        );

        if return_type == Type::UNIT {
            None
        } else {
            Some(result)
        }
    }

    // rustdoc-stripper-ignore-next
    /// Invalidates the closure.
    ///
    /// Invoking an invalidated closure has no effect.
    #[doc(alias = "g_closure_invalidate")]
    pub fn invalidate(&self) {
        unsafe {
            gobject_ffi::g_closure_invalidate(self.to_glib_none().0);
        }
    }
}

pub trait IntoClosureReturnValue {
    fn into_closure_return_value(self) -> Option<Value>;
}

impl IntoClosureReturnValue for () {
    #[inline]
    fn into_closure_return_value(self) -> Option<Value> {
        None
    }
}

impl<T: Into<Value>> IntoClosureReturnValue for T {
    #[inline]
    fn into_closure_return_value(self) -> Option<Value> {
        Some(self.into())
    }
}

pub trait TryFromClosureReturnValue: StaticType + Sized + 'static {
    fn try_from_closure_return_value(v: Option<Value>) -> Result<Self, crate::BoolError>;
}

impl TryFromClosureReturnValue for () {
    #[inline]
    fn try_from_closure_return_value(v: Option<Value>) -> Result<Self, crate::BoolError> {
        match v {
            None => Ok(()),
            Some(v) => Err(bool_error!(
                "Invalid return value: expected (), got {}",
                v.type_()
            )),
        }
    }
}

impl<T: for<'a> FromValue<'a> + StaticType + 'static> TryFromClosureReturnValue for T {
    #[inline]
    fn try_from_closure_return_value(v: Option<Value>) -> Result<Self, crate::BoolError> {
        v.ok_or_else(|| {
            bool_error!(
                "Invalid return value: expected {}, got ()",
                T::static_type()
            )
        })
        .and_then(|v| {
            v.get_owned::<T>().map_err(|_| {
                bool_error!(
                    "Invalid return value: expected {}, got {}",
                    T::static_type(),
                    v.type_()
                )
            })
        })
    }
}

unsafe impl Send for Closure {}
unsafe impl Sync for Closure {}

#[cfg(test)]
mod tests {
    use std::sync::{
        atomic::{AtomicUsize, Ordering},
        Arc,
    };

    use super::*;

    #[allow(clippy::unnecessary_wraps)]
    fn closure_fn(values: &[Value]) -> Option<Value> {
        assert_eq!(values.len(), 2);
        let string_arg = values[0].get::<&str>();
        assert_eq!(string_arg, Ok("test"));
        let int_arg = values[1].get::<i32>();
        assert_eq!(int_arg, Ok(42));
        Some(24.to_value())
    }

    #[test]
    fn test_closure() {
        let call_count = Arc::new(AtomicUsize::new(0));

        let count = call_count.clone();
        let closure = RustClosure::new(move |values| {
            count.fetch_add(1, Ordering::Relaxed);
            assert_eq!(values.len(), 2);
            let string_arg = values[0].get::<&str>();
            assert_eq!(string_arg, Ok("test"));
            let int_arg = values[1].get::<i32>();
            assert_eq!(int_arg, Ok(42));
            None
        });
        closure.invoke::<()>(&[&"test", &42]);
        assert_eq!(call_count.load(Ordering::Relaxed), 1);

        closure.invoke::<()>(&[&"test", &42]);
        assert_eq!(call_count.load(Ordering::Relaxed), 2);

        closure.invalidate();
        closure.invoke::<()>(&[&"test", &42]);
        assert_eq!(call_count.load(Ordering::Relaxed), 2);

        let closure = RustClosure::new(closure_fn);
        let result = closure.invoke::<i32>(&[&"test", &42]);
        assert_eq!(result, 24);
        closure.invalidate();
        let result = closure.invoke::<i32>(&[&"test", &42]);
        assert_eq!(result, 0);
    }
}
