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

#include <cstring>
#include <iostream>

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

namespace animia::internal::wayland {

/* zwlr-foreign-toplevel-management-v1 implementation */
static void zwlr_foreign_handle_handle_title(void* data, struct zwlr_foreign_toplevel_handle_v1* handle,
                                             const char* title) {
	if (title)
		reinterpret_cast<Window*>(data)->text = title;
}

static void zwlr_foreign_handle_handle_app_id(void* data, struct zwlr_foreign_toplevel_handle_v1* handle,
                                              const char* app_id) {
	if (app_id)
		reinterpret_cast<Window*>(data)->class_name = app_id;
}

static void zwlr_foreign_handle_handle_done(void* data, struct zwlr_foreign_toplevel_handle_v1* handle) {
	if (handle)
		zwlr_foreign_toplevel_handle_v1_destroy(handle);
}

static void zwlr_foreign_handle_handle_state(void* data, struct zwlr_foreign_toplevel_handle_v1* handle,
                                             struct wl_array*) {
}

static void zwlr_foreign_handle_handle_parent(void* data, struct zwlr_foreign_toplevel_handle_v1* handle,
                                              struct zwlr_foreign_toplevel_handle_v1*) {
}

static void zwlr_foreign_handle_handle_output_enter(void* data, struct zwlr_foreign_toplevel_handle_v1* handle,
                                                    struct wl_output*) {
}

static void zwlr_foreign_handle_handle_output_leave(void* data, struct zwlr_foreign_toplevel_handle_v1* handle,
                                                    struct wl_output*) {
}

static void zwlr_foreign_handle_handle_closed(void* data, struct zwlr_foreign_toplevel_handle_v1* handle) {
}

static const struct zwlr_foreign_toplevel_handle_v1_listener zwlr_handle_listener = {
    .title = zwlr_foreign_handle_handle_title,
    .app_id = zwlr_foreign_handle_handle_app_id,
    .output_enter = zwlr_foreign_handle_handle_output_enter,
    .output_leave = zwlr_foreign_handle_handle_output_leave,
    .state = zwlr_foreign_handle_handle_state,
    .done = zwlr_foreign_handle_handle_done,
    .closed = zwlr_foreign_handle_handle_closed,
    .parent = zwlr_foreign_handle_handle_parent
};

static void zwlr_toplevel_manager_handle_toplevel(void* data, struct zwlr_foreign_toplevel_manager_v1* manager,
                                                  struct zwlr_foreign_toplevel_handle_v1* handle) {
	std::vector<Window>* windows = reinterpret_cast<std::vector<Window>*>(data);
	if (!windows)
		return;

	windows->push_back({0});
	zwlr_foreign_toplevel_handle_v1_add_listener(handle, &zwlr_handle_listener, &*windows->end());
}

static void zwlr_toplevel_manager_handle_finished(void*, struct zwlr_foreign_toplevel_manager_v1*) {
}

static const struct zwlr_foreign_toplevel_manager_v1_listener zwlr_toplevel_manager_listener = {
    .toplevel = zwlr_toplevel_manager_handle_toplevel,
    .finished = zwlr_toplevel_manager_handle_finished,
};

/* 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_identifier(void* data, ext_foreign_toplevel_handle_v1* handle, const char* identifier) {
	if (identifier) {
		reinterpret_cast<Window*>(data)->id = util::StringToInt(identifier, 0);
	}
}

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 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 = nullptr;
	struct zwlr_foreign_toplevel_manager_v1* zwlr_toplevel_mgr = nullptr;
	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)) {
		if (global->zwlr_toplevel_mgr)
			return; // we don't need this then

		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);
	} else if (!std::strcmp(interface, zwlr_foreign_toplevel_manager_v1_interface.name)) {
		if (global->ext_toplevel_list || version < 3)
			return; // we don't need this then

		global->zwlr_toplevel_mgr = reinterpret_cast<struct zwlr_foreign_toplevel_manager_v1*>(
		    wl_registry_bind(registry, name, &zwlr_foreign_toplevel_manager_v1_interface, 1));

		zwlr_foreign_toplevel_manager_v1_add_listener(global->zwlr_toplevel_mgr, &zwlr_toplevel_manager_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 && !sync->global.zwlr_toplevel_mgr) {
			std::cerr << "animia/wayland: Wayland server doesn't support ext-foreign-toplevel-list-v1 nor "
			             "wlr-foreign-toplevel-management-unstable-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;
}

} // namespace animia::internal::wayland
