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

use std::boxed::Box as Box_;
use std::os::unix::io::{AsFd, AsRawFd};
use std::ptr;

use glib::{Error, prelude::*};
use glib::{GString, translate::*};

use crate::{DesktopAppInfo, ffi};
use gio::AppLaunchContext;

impl DesktopAppInfo {
    #[doc(alias = "g_desktop_app_info_search")]
    pub fn search(search_string: &str) -> Vec<Vec<GString>> {
        unsafe {
            let out = ffi::g_desktop_app_info_search(search_string.to_glib_none().0);

            if out.is_null() {
                return Vec::new();
            }

            let mut ret = Vec::new();
            let mut it = 0;
            loop {
                let tmp: *mut *mut libc::c_char = *out.offset(it);

                if tmp.is_null() {
                    break;
                }
                let v: Vec<GString> = FromGlibPtrContainer::from_glib_full(tmp);
                ret.push(v);
                it += 1;
            }

            glib::ffi::g_free(out as *mut libc::c_void);
            ret
        }
    }
}

pub trait DesktopAppInfoExtManual: IsA<DesktopAppInfo> {
    #[cfg_attr(docsrs, doc(cfg(all(feature = "v2_58", unix))))]
    #[doc(alias = "g_desktop_app_info_launch_uris_as_manager_with_fds")]
    #[allow(clippy::too_many_arguments)]
    #[allow(clippy::type_complexity)]
    fn launch_uris_as_manager_with_fds<P: IsA<AppLaunchContext>>(
        &self,
        uris: &[&str],
        launch_context: Option<&P>,
        spawn_flags: glib::SpawnFlags,
        user_setup: Option<Box_<dyn FnOnce() + 'static>>,
        pid_callback: Option<&mut dyn FnMut(&DesktopAppInfo, glib::Pid)>,
        stdin_fd: Option<impl AsFd>,
        stdout_fd: Option<impl AsFd>,
        stderr_fd: Option<impl AsFd>,
    ) -> Result<(), Error> {
        let user_setup_data: Box_<Option<Box_<dyn FnOnce() + 'static>>> = Box_::new(user_setup);
        unsafe extern "C" fn user_setup_func(user_data: glib::ffi::gpointer) {
            unsafe {
                let callback: Box_<Option<Box_<dyn FnOnce() + 'static>>> =
                    Box_::from_raw(user_data as *mut _);
                let callback = (*callback).expect("cannot get closure...");
                callback()
            }
        }
        let user_setup = if user_setup_data.is_some() {
            Some(user_setup_func as _)
        } else {
            None
        };
        let pid_callback_data: Option<&mut dyn FnMut(&DesktopAppInfo, glib::Pid)> = pid_callback;
        unsafe extern "C" fn pid_callback_func(
            appinfo: *mut ffi::GDesktopAppInfo,
            pid: glib::ffi::GPid,
            user_data: glib::ffi::gpointer,
        ) {
            unsafe {
                let appinfo = from_glib_borrow(appinfo);
                let pid = from_glib(pid);
                let callback = user_data as *mut Option<&mut dyn FnMut(&DesktopAppInfo, glib::Pid)>;
                if let Some(ref mut callback) = *callback {
                    callback(&appinfo, pid)
                } else {
                    panic!("cannot get closure...")
                };
            }
        }
        let pid_callback = if pid_callback_data.is_some() {
            Some(pid_callback_func as _)
        } else {
            None
        };
        let super_callback0: Box_<Option<Box_<dyn FnOnce() + 'static>>> = user_setup_data;
        let super_callback1: &Option<&mut dyn FnMut(&DesktopAppInfo, glib::Pid)> =
            &pid_callback_data;

        let stdin_raw_fd = stdin_fd.map_or(-1, |fd| fd.as_fd().as_raw_fd());
        let stdout_raw_fd = stdout_fd.map_or(-1, |fd| fd.as_fd().as_raw_fd());
        let stderr_raw_fd = stderr_fd.map_or(-1, |fd| fd.as_fd().as_raw_fd());
        unsafe {
            let mut error = ptr::null_mut();
            let _ = ffi::g_desktop_app_info_launch_uris_as_manager_with_fds(
                self.as_ref().to_glib_none().0,
                uris.to_glib_none().0,
                launch_context.map(|p| p.as_ref()).to_glib_none().0,
                spawn_flags.into_glib(),
                user_setup,
                Box_::into_raw(super_callback0) as *mut _,
                pid_callback,
                super_callback1 as *const _ as *mut _,
                stdin_raw_fd,
                stdout_raw_fd,
                stderr_raw_fd,
                &mut error,
            );
            if error.is_null() {
                Ok(())
            } else {
                Err(from_glib_full(error))
            }
        }
    }
}

impl<O: IsA<DesktopAppInfo>> DesktopAppInfoExtManual for O {}
