use std::ffi::{c_char, c_int, c_uint};
use std::mem::{self, MaybeUninit};
use std::ptr;

#[repr(C)]
pub struct Flags {
    skip_next_animation: bool,
    hidden: bool,
    emulated: bool,
    bouncing: bool,
}

#[repr(C)]
pub struct Application {
    main_window: x11::xlib::Window,
    main_window_desc: *mut crate::window::Window,
    menu: *mut crate::menu::Menu,
    app_icon: *mut crate::app_icon::AppIcon,
    refcount: c_int,
    last_focused: *mut crate::window::Window,
    last_workspace: c_int,
    urgent_bounce_timer: crate::wings::WMHandlerID,
    flags: Flags,
}

fn make_main_window_desc(
    dpy: *mut x11::xlib::Display,
    scr: *mut crate::screen::Screen,
    window: x11::xlib::Window,
) -> *mut crate::window::Window {
    if dpy.is_null() || scr.is_null() {
        return ptr::null_mut();
    }
    let mut attr = MaybeUninit::<x11::xlib::XWindowAttributes>::uninit();
    if unsafe { x11::xlib::XGetWindowAttributes(dpy, window, attr.as_mut_ptr()) } != 0 {
        return ptr::null_mut();
    }
    let attr = unsafe { attr.assume_init() };

    unsafe {
        let win = crate::window::ffi::wWindowCreate();
        crate::window::ffi::wWindowSetScreenPtr(win, scr);
        crate::window::ffi::wWindowSetMainWindow(win, window);
        crate::window::ffi::wWindowSetWmHints(win, x11::xlib::XGetWMHints(dpy, window));

        let mut wm_class: *mut c_char = ptr::null_mut();
        let mut wm_instance: *mut c_char = ptr::null_mut();
        crate::properties::ffi::PropGetWMClass(window, &mut wm_class, &mut wm_instance);
        crate::window::ffi::wWindowSetWmClass(win, wm_class);
        crate::window::ffi::wWindowSetWmInstance(win, wm_instance);

        crate::defaults::ffi::wDefaultFillAttributes(
            wm_instance,
            wm_class,
            crate::window::ffi::wWindowGetUserFlagsMut(win),
            crate::window::ffi::wWindowGetDefinedUserFlagsMut(win),
            1,
        );

        x11::xlib::XSelectInput(
            dpy,
            window,
            attr.your_event_mask | x11::xlib::PropertyChangeMask | x11::xlib::StructureNotifyMask,
        );
        return win;
    }
}

impl Application {
    pub fn of_window(
        dpy: *mut x11::xlib::Display,
        window: x11::xlib::Window,
    ) -> Option<*mut Self> {
        if dpy.is_null() || window == 0 {
            return None;
        }
        let global_app_win = unsafe { crate::global::ffi::wGlobalGetContext().app_win };

        let mut app = MaybeUninit::<*mut Application>::uninit();
        if unsafe {
            x11::xlib::XFindContext(
                dpy,
                window,
                global_app_win,
                mem::transmute(app.as_mut_ptr()),
            )
        } == 0 {
            return Some(unsafe { app.assume_init() });
        } else {
            return None;
        }
    }

    pub unsafe fn new(
        dpy: *mut x11::xlib::Display,
        wwin: *mut crate::window::Window,
        // 1 if USER_MENU is def'd, else 0
        _enable_user_menu: bool,
    ) -> *mut Self {
        let main_window = unsafe { crate::window::ffi::wWindowGetMainWindow(wwin) };
        let scr = unsafe { crate::window::ffi::wWindowGetScreenPtr(wwin) };
        if main_window == 0 || main_window == unsafe {
            crate::screen::ffi::wScreenGetRootWin(scr)
        } {
            return ptr::null_mut();
        }

        {
            let mut root: x11::xlib::Window = 0;
            let mut x: c_int = 0;
            let mut y: c_int = 0;
            let mut width: c_uint = 0;
            let mut height: c_uint = 0;
            let mut border_width: c_uint = 0;
            let mut depth: c_uint = 0;
	    /* check if the window is valid */
            if unsafe {
                x11::xlib::XGetGeometry(
                    dpy,
                    main_window,
                    &mut root,
                    &mut x,
                    &mut y,
                    &mut width,
                    &mut height,
                    &mut border_width,
                    &mut depth,
                ) == 0
            } {
                return ptr::null_mut();
            }
        }

        let wapp = match Application::of_window(dpy, main_window) {
            None => return ptr::null_mut(),
            Some(a) => a,
        };
        if !wapp.is_null() {
            let app = unsafe { &mut *wapp };
            app.refcount += 1;
            let app_icon = unsafe { ffi::wApplicationGetAppIcon(wapp) };
            if unsafe {
                !app_icon.is_null() &&
                    crate::app_icon::ffi::wAppIconIsDocked(app_icon) != 0 &&
                    crate::app_icon::ffi::wAppIconIsRelaunching(app_icon) != 0 &&
                    !crate::window::ffi::wWindowGetFakeGroup(crate::application::ffi::wApplicationGetMainWindowDesc(wapp)).is_null()
            } {
                unsafe { crate::dock::ffi::wDockFinishLaunch(app_icon); }
            }
            return wapp;
        }

        let main_window_desc = make_main_window_desc(dpy, scr, main_window);
        if main_window_desc.is_null() {
            return ptr::null_mut();
        }
        unsafe {
            crate::window::ffi::wWindowSetFakeGroup(
                main_window_desc,
                crate::window::ffi::wWindowGetFakeGroup(wwin),
            );
            crate::window::ffi::wWindowSetNetIconImage(
                main_window_desc,
                crate::wrlib::ffi::RRetainImage(
                    crate::window::ffi::wWindowGetNetIconImage(wwin),
                ));
        }

        {
            let leader = Application::of_window(dpy, main_window)
                .unwrap_or(ptr::null_mut());
            if !leader.is_null() {
                unsafe { (*leader).main_window = main_window; }
            }
        }

        let menu = unsafe { crate::app_menu::ffi::wAppMenuGet(scr, main_window) };
        // user menu feature is disabled for now
        // let menu =
        //     if enable_user_menu && menu.is_null() {
        //         unsafe { crate::user_menu::ffi::wUserMenuGet(scr, main_window_desc) }
        //     } else {
        //         menu
        //     };

        let defined_user_flags = unsafe {
            crate::window::ffi::wWindowGetDefinedUserFlags(main_window_desc)
        };
        let user_flags = unsafe {
            crate::window::ffi::wWindowGetUserFlags(main_window_desc)
        };
        let client_flags = unsafe {
            crate::window::ffi::wWindowGetClientFlags(main_window_desc)
        };

        let (hidden, emulated) = unsafe {
            let hidden = if crate::window::ffi::wWindowAttributesGetStartHidden(defined_user_flags) != 0 {
                crate::window::ffi::wWindowAttributesGetStartHidden(user_flags) != 0
            } else {
                crate::window::ffi::wWindowAttributesGetStartHidden(client_flags) != 0
            };

            let emulated = if crate::window::ffi::wWindowAttributesGetEmulated(defined_user_flags) != 0 {
                crate::window::ffi::wWindowAttributesGetEmulated(user_flags) != 0
            } else {
                crate::window::ffi::wWindowAttributesGetEmulated(client_flags) != 0
            };

            (hidden, emulated)
        };

        let mut app = Box::new(Application {
            main_window,
            main_window_desc,
            menu,
            app_icon: ptr::null_mut(),
            refcount: 1,
            last_focused: ptr::null_mut(),
            last_workspace: 0,
            urgent_bounce_timer: ptr::null_mut(),
            flags: Flags {
                skip_next_animation: false,
                hidden,
                emulated,
                bouncing: false,
            },
        });

        /* application descriptor */
        unsafe {
            x11::xlib::XSaveContext(
                dpy,
                main_window,
                crate::global::ffi::wGlobalGetContext().app_win,
                mem::transmute(app.as_mut()),
            );

            crate::app_icon::ffi::create_appicon_for_application(app.as_mut(), wwin);

            Box::leak(app)
        }
    }
}

pub mod ffi {
    use super::Application;
    use std::ffi::c_int;
    use std::{mem, ptr};

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn wApplicationGetMainWindow(app: *mut Application) -> x11::xlib::Window {
        unsafe {
            (*app).main_window
        }
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn wApplicationGetMenu(app: *mut Application) -> *mut crate::menu::Menu {
        unsafe {
            (*app).menu
        }
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn wApplicationSetMenu(app: *mut Application, menu: *mut crate::menu::Menu) {
        unsafe {
            (*app).menu = menu;
        }
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn wApplicationGetMainWindowDesc(app: *mut Application) -> *mut crate::window::Window {
        unsafe {
            (*app).main_window_desc
        }
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn wApplicationGetAppIcon(app: *mut Application) -> *mut crate::app_icon::AppIcon {
        unsafe {
            (*app).app_icon
        }
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn wApplicationSetAppIcon(app: *mut Application, icon: *mut crate::app_icon::AppIcon) {
        unsafe {
            (*app).app_icon = icon;
        }
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn wApplicationGetLastWorkspace(app: *mut Application) -> c_int {
        unsafe {
            (*app).last_workspace
        }
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn wApplicationSetLastWorkspace(app: *mut Application, workspace: c_int) {
        unsafe {
            (*app).last_workspace = workspace;
        }
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn wApplicationGetLastFocused(app: *mut Application) -> *mut crate::window::Window {
        unsafe {
            (*app).last_focused
        }
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn wApplicationSetLastFocused(app: *mut Application, win: *mut crate::window::Window) {
        unsafe {
            (*app).last_focused = win;
        }
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn wApplicationGetUrgentBounceTimer(app: *mut Application) -> crate::wings::WMHandlerID {
        unsafe {
            (*app).urgent_bounce_timer
        }
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn wApplicationHasUrgentBounceTimer(app: *mut Application) -> c_int {
        unsafe {
            if (*app).urgent_bounce_timer.is_null() { 0 } else { 1 }
        }
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn wApplicationClearUrgentBounceTimer(app: *mut Application) {
        unsafe {
            if (*app).urgent_bounce_timer.is_null() {
                return;
            }
            crate::wings::ffi::WMDeleteTimerHandler((*app).urgent_bounce_timer);
            (*app).urgent_bounce_timer = ptr::null_mut();
        }
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn wApplicationSetUrgentBounceTimer(app: *mut Application, timer: crate::wings::WMHandlerID) {
        unsafe {
            (*app).urgent_bounce_timer = timer;            
        }
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn wApplicationGetSkipNextAnimation(app: *mut Application) -> c_int {
        unsafe {
            (*app).flags.skip_next_animation.into()
        }
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn wApplicationSetSkipNextAnimation(app: *mut Application, skip: c_int) {
        unsafe {
            (*app).flags.skip_next_animation = skip != 0;
        }
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn wApplicationIsHidden(app: *mut Application) -> c_int {
        unsafe {
            (*app).flags.hidden.into()
        }
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn wApplicationSetHidden(app: *mut Application, hidden: c_int) {
        unsafe {
            (*app).flags.hidden = hidden != 0;
        }
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn wApplicationIsEmulated(app: *mut Application) -> c_int {
        unsafe {
            (*app).flags.emulated.into()
        }
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn wApplicationSetEmulated(app: *mut Application, emulated: c_int) {
        unsafe {
            (*app).flags.emulated = emulated != 0;
        }
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn wApplicationIsBouncing(app: *mut Application) -> c_int {
        unsafe {
            (*app).flags.bouncing.into()
        }
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn wApplicationSetBouncing(app: *mut Application, bouncing: c_int) {
        unsafe {
            (*app).flags.bouncing = bouncing != 0
        }
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn wApplicationIncrementRefcount(app: *mut Application) {
        unsafe {
            (*app).refcount += 1;
        }
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn wApplicationHasLiveRefcount(app: *mut Application) -> c_int {
        unsafe {
            ((*app).refcount > 1).into()
        }
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn wApplicationClearRefcount(app: *mut Application) {
        unsafe {
            (*app).refcount = 0;
        }
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn wApplicationOfRust(
        dpy: *mut x11::xlib::Display,
        window: x11::xlib::Window,
    ) -> *mut Application {
        Application::of_window(dpy, window).unwrap_or(ptr::null_mut())
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn wApplicationActivate(app: *mut Application) {
        unsafe {
            let app_icon = wApplicationGetAppIcon(app);
            if !app_icon.is_null() {
                let icon = crate::app_icon::ffi::wAppIconGetIcon(app_icon);
                crate::icon::ffi::wIconSetHighlighted(icon, true.into());
                crate::app_icon::ffi::wAppIconPaint(app_icon);
            }
        }
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn wApplicationDeactivate(app: *mut Application) {
        unsafe {
            let app_icon = wApplicationGetAppIcon(app);
            let icon = crate::app_icon::ffi::wAppIconGetIcon(app_icon);
            crate::icon::ffi::wIconSetHighlighted(icon, false.into());
            crate::app_icon::ffi::wAppIconPaint(app_icon);
        }
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn wApplicationCreateRust(
        dpy: *mut x11::xlib::Display,
        wwin: *mut crate::window::Window,
        enable_user_menu: c_int,
    ) -> *mut Application {
        unsafe {
            Application::new(
                dpy,
                wwin,
                enable_user_menu != 0,
            )
        }
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn wApplicationDestroyRust(
        dpy: *mut x11::xlib::Display,
        wapp: *mut Application,
    ) {
        let app = unsafe { &mut *wapp };

        app.refcount -= 1;
        if app.refcount > 0 {
            return;
        }

        if !app.urgent_bounce_timer.is_null() {
            unsafe { crate::wings::ffi::WMDeleteTimerHandler(app.urgent_bounce_timer); }
            app.urgent_bounce_timer = ptr::null_mut();
        }
        if app.flags.bouncing {
            /* event.c:handleDestroyNotify forced this destroy
               and thereby overlooked the bounce callback */
            app.refcount = 1;
            return;
        }

        unsafe {
            let wwin = crate::window::ffi::wWindowFor(
                crate::window::ffi::wWindowGetClientWin(app.main_window_desc),
            );

            let global_app_win = crate::global::ffi::wGlobalGetContext().app_win;
            let global_client_win = crate::global::ffi::wGlobalGetContext().client_win;
            x11::xlib::XDeleteContext(dpy, app.main_window, global_app_win);
            crate::app_menu::ffi::wAppMenuDestroy(app.menu);
            crate::app_icon::ffi::removeAppIconFor(wapp);
            crate::window::ffi::wWindowDestroy(app.main_window_desc);
            if !wwin.is_null() {
                /* undelete client window context that was deleted in wWindowDestroy */
                x11::xlib::XSaveContext(
                    dpy,
                    crate::window::ffi::wWindowGetClientWin(wwin),
                    global_client_win,
                    mem::transmute(wwin),
                );
            }
            let _ = Box::from_raw(wapp);
        }
    }
}
