pub mod headless;

use headless::{xvfb::XvfbProcess, DisplayNumberRegistry};
use std::{
    env,
    ffi::{c_char, c_int, CStr, CString},
    ptr::{self, NonNull},
    sync::Mutex,
};
use wings_rs::WINGsP::{WMHandleEvent, WMInitializeApplication, WMReleaseApplication, WMScreen};

static APPLICATION_COUNT: Mutex<u32> = Mutex::new(0);

/// Stubbed wrapper for WMApplication.
pub struct HeadlessApplication {
    /// The headless X server process.
    pub xvfb: XvfbProcess,
    /// An open client handle to the headless X server.
    pub display: NonNull<x11::xlib::Display>,
    /// A fully initialized `WMScreen` for an application running on the
    /// headless X server.
    pub screen: NonNull<WMScreen>,
}

impl HeadlessApplication {
    /// Starts a headless X server and calls `WMInitializeApplication` if no
    /// WINGs application is active.
    pub fn new() -> Self {
        static PROGNAME: &'static CStr = c"Test@eqweq_ewq$eqw";

        let xvfb = XvfbProcess::start_default(
            DisplayNumberRegistry::next_unused_display()
                .expect("cannot allocate a value for DISPLAY"),
        )
        .expect("cannot start Xvfb");

        {
            let mut application_count = APPLICATION_COUNT.lock().unwrap();
            if *application_count == 0 {
                unsafe {
                    let mut argv: Vec<*mut c_char> =
                        vec![PROGNAME.as_ptr().cast::<c_char>() as *mut c_char];
                    let mut argc: c_int = 1;
                    WMInitializeApplication(
                        PROGNAME.as_ptr().cast::<c_char>(),
                        &mut argc as *mut _,
                        argv.as_mut_ptr(),
                    );
                }
            }
            *application_count += 1;
        }

        let display_str = CString::new(format!(":{}", xvfb.display())).unwrap();
        let display =
            NonNull::new(unsafe { x11::xlib::XOpenDisplay(display_str.as_ptr()) }).unwrap();
        let screen = WMScreen::new(&display).unwrap();

        HeadlessApplication {
            xvfb,
            display,
            screen,
        }
    }

    /// Pumps the WUtil and X11 event queues, hackily.
    ///
    /// Runs WUtil event handlers that should be run by `now`, pumps the X11
    /// event queue, dispatches the next pending event (if any), and runs
    /// WUtil idle handlers (if no X11 events are available).
    ///
    /// Returns `true` if more events are pending. (This does not account for
    /// WUtils timer events that might need to fire, since the future time when
    /// `pump_event_queue` will next be called cannot be known.)
    ///
    /// This is somewhat hacky (because it does not match the WINGs main loop
    /// logic exactly), so it should only be used by tests.
    pub fn pump_event_queue(&mut self, now: std::time::Instant) -> bool {
        let display = self.display.as_ptr();
        wutil_rs::handlers::with_global_handlers(|handlers| handlers.check_timer_handlers(now));

        unsafe {
            x11::xlib::XSync(display, 0);
            if x11::xlib::XPending(display) > 0 {
                let mut event = x11::xlib::XEvent { type_: 0 };
                x11::xlib::XNextEvent(display, &mut event as *mut _);
                WMHandleEvent(&mut event as *mut _);
            } else {
                wutil_rs::handlers::run_global_idle_handlers();
            }

            x11::xlib::XSync(display, 0);
            if x11::xlib::XPending(display) > 0 {
                return true;
            }
            wutil_rs::handlers::with_global_handlers(|handlers| handlers.has_idle_handlers())
        }
    }
}

impl Drop for HeadlessApplication {
    fn drop(&mut self) {
        unsafe {
            x11::xlib::XCloseDisplay(self.display.as_ptr());
            // Leak self.screen, since WINGs doesn't provide a cleanup function.
        }
        let mut application_count = APPLICATION_COUNT.lock().unwrap();
        if *application_count <= 1 {
            *application_count = 0;
            unsafe {
                WMReleaseApplication();
            }
        } else {
            *application_count -= 1;
        }
        self.xvfb.shutdown().unwrap();
    }
}

/// Simple wrapper for demo WINGs applications.
///
/// This handles calling `WMInitializeApplication`, which operates on a
/// singleton behind the scenes and wants to be passed command line arguments.
///
/// When the last instantiated `LiveApplication` is dropped,
/// `WMReleaseApplication` is called automatically.
pub struct LiveApplication {
    /// Live X11 `Display` that the application is running on.
    pub display: NonNull<x11::xlib::Display>,
    /// Fully intialized `WMScreen` for the application.
    ///
    /// This is leaked when the `LiveApplication` is dropped because WINGs does
    /// not provide a deletion function.
    pub screen: NonNull<WMScreen>,
}

impl LiveApplication {
    /// Creates a new application wrapper and initializes the global
    /// `WMApplication` if necessary.
    pub fn new(name: &str) -> Self {
        let mut application_count = APPLICATION_COUNT.lock().unwrap();
        if *application_count > 0 {
            panic!("application already started!");
        }
        let name = CString::new(name).expect("invalid program name");
        unsafe {
            let mut argv: Vec<CString> = vec![name];
            let name_ptr = argv[0].as_ptr().cast::<c_char>();
            for arg in env::args() {
                argv.push(CString::new(arg).expect("invalid argument string"));
            }
            let mut argv_ptrs: Vec<*mut c_char> = argv
                .iter_mut()
                .map(|a| a.as_ptr().cast::<c_char>() as *mut c_char)
                .collect();
            let mut argc: c_int = argv_ptrs.len().try_into().expect("invalid argument count");
            WMInitializeApplication(name_ptr, &mut argc as *mut _, argv_ptrs.as_mut_ptr());
        }
        *application_count = 1;

        let display = NonNull::new(unsafe { x11::xlib::XOpenDisplay(ptr::null_mut()) })
            .expect("could not connect to X11 display");
        let screen = WMScreen::new(&display).unwrap();

        LiveApplication { display, screen }
    }
}

impl Drop for LiveApplication {
    fn drop(&mut self) {
        unsafe {
            x11::xlib::XCloseDisplay(self.display.as_ptr());
            // Leak self.screen, since WINGs doesn't provide a cleanup function.
        }
        let mut application_count = APPLICATION_COUNT.lock().unwrap();
        if *application_count <= 1 {
            *application_count = 0;
            unsafe {
                WMReleaseApplication();
            }
        } else {
            *application_count -= 1;
        }
    }
}
