#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>

#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/cursorfont.h>
#include <err.h>

#define lenof(x) (sizeof(x)/sizeof(*(x)))

Display *disp;
int screen;
Window root;

int verbose = 0;

Window find_client_win(Window w)
{
    Window *queue;
    int qhead, qtail, qsize;

    Atom type;
    int format;
    unsigned long nitems;
    unsigned long bytes;
    unsigned char *prop;

    Atom wm_state_atom = XInternAtom(disp, "WM_STATE", false);

    qsize = 128;
    queue = malloc(qsize * sizeof(Window *));
    qhead = qtail = 0;
    queue[qtail++] = w;

    while (qhead < qtail) {
        w = queue[qhead++];
        if (wm_state_atom != None &&
            XGetWindowProperty(disp, w, wm_state_atom, 0, 0, false,
                               AnyPropertyType, &type, &format, &nitems,
                               &bytes, &prop) == Success && type != None) {
            free(queue);
            return w;
        } else {
            Window root, parent, *children;
            unsigned i, nchildren;

            XFree(prop);

            if (XQueryTree(disp, w, &root, &parent, &children, &nchildren) &&
                children != NULL) {
                for (i = 0; i < nchildren; i++) {
                    if (qtail >= qsize) {
                        qsize = qtail * 3 / 2;
                        queue = realloc(queue, qsize * sizeof(Window *));
                    }
                    queue[qtail++] = children[i];
                }
                XFree(children);
            }
        }
    }
    return None;
}

bool select_window(Window *out)
{
    Cursor cursor = XCreateFontCursor(disp, XC_crosshair);
    if (XGrabPointer(disp, root, false /* owner_events */, ButtonPressMask,
                     GrabModeAsync /* pointer */, GrabModeAsync /* keyboard */,
                     root, cursor, CurrentTime) != GrabSuccess) {
        warnx("Failed to grab pointer");
        return false;
    }

    Window w;

    while (1) {
        XEvent ev;
        XNextEvent(disp, &ev);

        if (ev.type == ButtonPress) {
            if (ev.xbutton.button == 1) {
                w = ev.xbutton.subwindow;
                if (verbose > 1)
                    printf("Selected window id: %08lx\n", (unsigned long)w);
                break;
            } else {
                /* user abort */
                return false;
            }
        }
    }

    XUngrabPointer(disp, CurrentTime);

    *out = find_client_win(w);
    return true;
}

int main(int argc, char **argv)
{
    const char *displayname = NULL;
    Window window = None;

    /* parse the command line arguments */
    while (--argc > 0) {
	const char *p = *++argv;

	if (!strcmp(p, "-display") || !strcmp(p, "-disp")) {
	    if (argc <= 0)
		errx(1, "option '%s' expects a parameter", p);
	    displayname = (--argc, *++argv);
	} else if (!strcmp(p, "--help")) {
            printf("\
usage:   xafter [window id]\n\
then:    left-click to select a window if a window id was not specified\n\
options: -display DISP  choose which X display to connect to\n\
also:    --help         display this help text\n\
");
            return 0;
	} else if (*p == '-') {
	    errx(1, "unrecognised option '%s'", p);
	} else if (window == None) {
            char *end = NULL;
            unsigned long value = strtoul(p, &end, 0);
            if (*p && !*end)
                window = (Window)value;
            if (window == None)
                errx(1, "unable to parse '%s' as a window id", p);
        } else {
	    errx(1, "unexpected argument '%s'", p);
	}
    }

    disp = XOpenDisplay(displayname);
    if (!disp)
	errx(1, "unable to open display");
    screen = DefaultScreen(disp);
    root = RootWindow(disp, screen);

    if (window == None) {
        if (!select_window(&window))
            return 0;                  /* user abort, not an error */
        if (window == None)
            errx(1, "no valid window selected");
    }

    XSelectInput(disp, window, StructureNotifyMask);

    while (1) {
        XEvent ev;
        XNextEvent(disp, &ev);
        if (ev.type == DestroyNotify && ev.xdestroywindow.window == window)
            break;
    }

    return 0;
}
