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

#include <iostream>
#include <cstring>

#include "animia/win/wayland/ext-foreign-toplevel-list-v1.h"
#include <wayland-client.h>

namespace animia::internal::wayland {

static void noop() {}

/* ext_foreign_handle stuff */
static void ext_foreign_handle_handle_app_id(void* data, struct ext_foreign_toplevel_handle_v1* handle, const char* app_id) {
	if (app_id)
		reinterpret_cast<Window*>(data)->class_name = app_id;
}

static void ext_foreign_handle_handle_title(void* data, struct ext_foreign_toplevel_handle_v1* handle, const char* title) {
	if (title)
		reinterpret_cast<Window*>(data)->text = title;
}

static void ext_foreign_handle_handle_done(void* data, struct ext_foreign_toplevel_handle_v1* handle) {
	if (handle)
		ext_foreign_toplevel_handle_v1_destroy(handle);
}

static void ext_foreign_handle_handle_closed(void*, struct ext_foreign_toplevel_handle_v1*) {}
static void ext_foreign_handle_handle_identifier(void*, ext_foreign_toplevel_handle_v1*, const char*) {}

static const struct ext_foreign_toplevel_handle_v1_listener ext_handle_listener = {
	.closed     = ext_foreign_handle_handle_closed,
	.done       = ext_foreign_handle_handle_done,
	.title      = ext_foreign_handle_handle_title,
	.app_id     = ext_foreign_handle_handle_app_id,
	.identifier = ext_foreign_handle_handle_identifier // for now
};

static void ext_toplevel_list_handle_toplevel(void* data, struct ext_foreign_toplevel_list_v1* list, struct ext_foreign_toplevel_handle_v1* handle) {
	std::vector<Window>* windows = reinterpret_cast<std::vector<Window>*>(data);
	if (!windows)
		return;

	windows->push_back({0});
	ext_foreign_toplevel_handle_v1_add_listener(handle, &ext_handle_listener, &*windows->end());
}

static void ext_toplevel_list_handle_finished(void*, ext_foreign_toplevel_list_v1*) {}

static const struct ext_foreign_toplevel_list_v1_listener ext_toplevel_list_listener = {
	.toplevel = ext_toplevel_list_handle_toplevel,
	.finished = ext_toplevel_list_handle_finished
};

/* -- Global data -- */
struct global_data {
	struct ext_foreign_toplevel_list_v1* ext_toplevel_list;
	std::vector<Window> windows;
};

static void registry_handle_global(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version) {
	struct global_data* global = reinterpret_cast<struct global_data*>(data);

	if (!std::strcmp(interface, ext_foreign_toplevel_list_v1_interface.name)) {
		// bind to ext-foreign-toplevel-list-v1
		global->ext_toplevel_list = reinterpret_cast<struct ext_foreign_toplevel_list_v1*>(wl_registry_bind(registry, name, &ext_foreign_toplevel_list_v1_interface, 1));
		ext_foreign_toplevel_list_v1_add_listener(global->ext_toplevel_list, &ext_toplevel_list_listener, &global->windows);
	}
}

static void registry_handle_global_remove(void*, wl_registry*, uint32_t) {}

static const struct wl_registry_listener registry_listener = {
	.global        = registry_handle_global,
	.global_remove = registry_handle_global_remove
};

/* BAH! humbug... */
struct sync_data {
	bool loop = true;
	int sync = 0;

	struct global_data global = {0};

	struct wl_callback* callback = nullptr;
	struct wl_display* display = nullptr;
};

static void sync_handle_done (void* data, struct wl_callback* callback, uint32_t other_data);
static const struct wl_callback_listener sync_callback_listener = {
	.done = sync_handle_done,
};

static void sync_handle_done (void* data, struct wl_callback* callback, uint32_t other_data) {
	struct sync_data* sync = reinterpret_cast<struct sync_data*>(data);

	wl_callback_destroy(callback);
	sync->callback = nullptr;

	if (sync->sync == 0) {
		if (!sync->global.ext_toplevel_list) {
			std::cerr << "animia/wayland: Wayland server doesn't support ext-foreign-toplevel-list-v1!" << std::endl;
			sync->loop = false;
			return;
		}

		sync->sync++;
		sync->callback = wl_display_sync(sync->display);
		wl_callback_add_listener(sync->callback, &sync_callback_listener, sync);

		/* we may need another sync here if there are protocol extensions for
		 * ext_foreign_toplevel_list.
		 *
		 * more info: https://git.sr.ht/~leon_plickat/lswt/tree/toplevel-info/item/lswt.c
		*/
	} else {
		/* we've received everything we need! */
		sync->loop = false;
	}
}

/* here comes the actual function we can use */
bool EnumerateWindows(window_proc_t window_proc) {
	struct sync_data sync = {
		.display = wl_display_connect(NULL)
	};

	if (!sync.display) {
		std::cerr << "animia/wayland: Can't connect to display" << std::endl;
		return false;
	}

	struct global_data global;

	struct wl_registry* registry = wl_display_get_registry(sync.display);
	wl_registry_add_listener(registry, &registry_listener, &sync.global);

	sync.callback = wl_display_sync(sync.display);
	wl_callback_add_listener(sync.callback, &sync_callback_listener, &sync);

	while (sync.loop && wl_display_dispatch(sync.display));

	for (const auto& window : global.windows)
		if (!window_proc({0}, window))
			return false;

	wl_display_disconnect(sync.display);

	return true;
}

}
