#include "animia/win/x11.h"
#include "animia/win.h"
#include "animia.h"

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h> // XA_*

#include <cstdint>
#include <string>
#include <memory>
#include <set>

/* The code for this is very fugly because X11 uses lots of generic type names
   (i.e., Window, Display), so I have to use :: when defining vars to distinguish
   between Animia's types and X11's types */

namespace animia::internal::x11 {

/* should return UTF8_STRING or STRING */
static bool GetWindowPropertyAsString(::Display* display, ::Window window, ::Atom atom, std::string& result, ::Atom reqtype = AnyPropertyType) {
	int format;
	unsigned long leftover_bytes, num_of_items;
	::Atom type;
	unsigned char* data;
	if (!::XGetWindowProperty(display, window, atom, 0L, (~0L), False, reqtype,
	                       &type, &format, &num_of_items, &leftover_bytes, &data))
		return false;

	result = std::string((char*)data, num_of_items);

	return true;
}

/* this should return CARDINAL, a 32-bit integer */
static bool GetWindowPID(::Display* display, ::Window window, pid_t& result) {
	int format;
	unsigned long leftover_bytes, num_of_items;
	::Atom atom = ::XInternAtom(display, "_NET_WM_PID", False), type;
	unsigned char* data;

	if (!::XGetWindowProperty(display, window, atom, 0L, (~0L), False, XA_CARDINAL,
	                       &type, &format, &num_of_items, &leftover_bytes, &data))
		return false;

	result = static_cast<pid_t>(*(uint32_t*)data);

	return true;
}

static bool FetchName(::Display* display, ::Window window, std::string& result) {
	/* TODO: Check if XInternAtom created None or not... */
	if (GetWindowPropertyAsString(display, window, ::XInternAtom(display, "_NET_WM_NAME", False),
		                           result, ::XInternAtom(display, "UTF8_STRING", False)))
		return true;

	/* Fallback to XGetWMName() */
	XTextProperty text;

	{
		int status = ::XGetWMName(display, window, &text);
		if (!status || !text.value || !text.nitems)
			return false;
	}

	char** list;

	{
		int count;

		int status = ::XmbTextPropertyToTextList(display, &text, &list, &count);
		if (status < Success || !count || !*list)
			return false;
	}

	::XFree(text.value);

	result = *list;

	::XFreeStringList(list);

	return true;
}

bool X11WinTools::EnumerateWindows(window_proc_t window_proc) {
	if (!window_proc)
		return false;

	::Display* display = ::XOpenDisplay(nullptr);
	::Window root = DefaultRootWindow(display);

	unsigned int num_windows = 0;
	::Window* windows = nullptr;

	{
		::Window root_return;
		::Window parent_return;

		int status = ::XQueryTree(display, root, &root_return, &parent_return, &windows, &num_windows);
		if (status < Success)
			return false;
	}

	for (long k = 0; k < num_windows; k++) {
		const ::Window window = windows[k];

		Window win;
		win.id = window;
		{
			::XClassHint* hint = ::XAllocClassHint();
			if (::XGetClassHint(display, window, hint)) {
				win.class_name = hint->res_class;
				::XFree(hint);
			}
		}
		FetchName(display, windows[k], win.text);

		Process proc;
		GetWindowPID(display, window, proc.pid);

		if (!window_proc(proc, win))
			return false;
	}

	::XFree(windows);

	return true;
}

}
