// =====================================================================================================================
//  fennec, a free and open source game engine
//  Copyright © 2025 - 2026  Medusa Slockbower
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <https://www.gnu.org/licenses/>.
// =====================================================================================================================

#include <fennec/core/logger.h>
#include <fennec/math/relational.h>
#include <fennec/platform/interface/display_server.h>

#include <fennec/renderers/interface/gfxsurface.h>
#include <fennec/renderers/interface/gfxcontext.h>

#include <fennec/platform/linux/wayland/server.h>
#include <fennec/platform/linux/wayland/window.h>
#include <fennec/platform/linux/wayland/lib/wayland.h>
#include <fennec/platform/linux/wayland/lib/headers/xdg-shell-client-protocols.h>
#include <fennec/platform/linux/wayland/libdecor/libdecor.h>

#if FENNEC_HAS_LIBDECOR
#include <fennec/platform/linux/wayland/libdecor/libdecor.h>
#endif

using namespace fennec;

wayland_window::wayland_window(display_server* server, const config& cfg, window* parent)
	: window_base(server, cfg, parent)
	, surface(nullptr)
	, xdgsurface(nullptr) {
}

wayland_window::~wayland_window() {

}

void wayland_window::set_size(const ivec2&) {

}

void wayland_window::initialize() {
	static constexpr wl_surface_listener surface_listener = {
		.enter                      = _wl_surface_listen_enter,
		.leave                      = _wl_surface_listen_leave,
		.preferred_buffer_scale     = _wl_surface_listen_preferred_buffer_scale,
		.preferred_buffer_transform = _wl_surface_listen_preferred_buffer_transform
	};

	static constexpr xdg_surface_listener xdg_surface_listener = {
		.configure = _xdg_surface_listen_configure
	};

	static constexpr xdg_toplevel_listener xdg_toplevel_listener = {
		.configure        = _xdg_toplevel_listen_configure,
		.close            = _xdg_toplevel_listen_close,
		.configure_bounds = _xdg_toplevel_listen_configure_bounds,
		.wm_capabilities  = _xdg_toplevel_listen_wm_capabilities
	};

	static constexpr wl_callback_listener frame_callback_listener = {
		.done = _wl_frame_listen_done
	};

	if (is_visible()) {
		return;
	}

	if (is_running()) {
		// reshow window
		return;
	}

	wayland_server* wl_server = static_cast<wayland_server*>(server);

	surface = wl_compositor_create_surface(wl_server->compositor);
	wl_surface_add_listener(surface, &surface_listener, this);

#if FENNEC_HAS_LIBDECOR
	static libdecor_frame_interface libdecor_frame_listener = {
		.configure     = _libdecor_frame_listen_configure,
		.close         = _libdecor_frame_listen_close,
		.commit        = _libdecor_frame_listen_commit,
		.dismiss_popup = _libdecor_frame_listen_dismiss_popup,

		.reserved0 = nullptr,
		.reserved1 = nullptr,
		.reserved2 = nullptr,
		.reserved3 = nullptr,
		.reserved4 = nullptr,
		.reserved5 = nullptr,
		.reserved6 = nullptr,
		.reserved7 = nullptr,
		.reserved8 = nullptr,
		.reserved9 = nullptr,
	};

	gfx_surface = unique_ptr(wl_server->get_gfx_context()->create_surface(this));

	if (wl_server->has_libdecor) {

		libdecorframe = libdecor_decorate(wl_server->libdecor, surface, &libdecor_frame_listener, this);
		libdecor_frame_set_app_id(libdecorframe, cfg.title.cstr());
		libdecor_frame_set_title(libdecorframe, cfg.title.cstr());
		libdecor_frame_map(libdecorframe);

		xdgsurface  = libdecor_frame_get_xdg_surface(libdecorframe);
		xdgtoplevel = libdecor_frame_get_xdg_toplevel(libdecorframe);
	} else
#endif
	{
		xdgsurface = xdg_wm_base_get_xdg_surface(wl_server->xdg, surface);
		xdg_surface_add_listener(xdgsurface, &xdg_surface_listener, this);

		xdgtoplevel = xdg_surface_get_toplevel(xdgsurface);
		xdg_toplevel_add_listener(xdgtoplevel, &xdg_toplevel_listener, this);

		frame_callback = wl_surface_frame(surface);
		wl_callback_add_listener(frame_callback, &frame_callback_listener, this);

		wl_surface_commit(surface);
	}

	wl_display_roundtrip(wl_server->display);
	wl_display_roundtrip(wl_server->display);

	state.flags.set(state_visible);
	state.flags.set(state_running);
}

void wayland_window::shutdown() {
	if (not is_running()) {
		return;
	}

	wayland_server* wl_server = static_cast<wayland_server*>(server);

#if FENNEC_HAS_LIBDECOR
	if (libdecorframe) {
		libdecor_frame_unref(libdecorframe);
		xdgtoplevel = nullptr;
		xdgsurface  = nullptr;
	}
#endif

	if (gfx_surface) {
		gfx_surface.reset();
	}

	if (frame_callback) {
		wl_callback_destroy(frame_callback);
		frame_callback = nullptr;
	}

	if (xdgtoplevel) {
		xdg_toplevel_destroy(xdgtoplevel);
		xdgtoplevel = nullptr;
	}

	if (xdgsurface) {
		xdg_surface_destroy(xdgsurface);
		xdgsurface = nullptr;
	}

	if (surface) {
		wl_surface_destroy(surface);
		surface = nullptr;
	}

	wl_display_roundtrip(wl_server->display);

	state.flags.clear(state_visible);
	state.flags.clear(state_running);
}

void* wayland_window::get_native_handle() {
	return surface;
}

bool wayland_window::set_flag(uint8_t flag, bool value) {

	// Do nothing if already set
	if (cfg.flags.test(flag) == value) {
		return false;
	}

	if (not is_running()) {
		return false;
	}

	switch (flag) {
		case flag_always_on_top:
			break;

		case flag_borderless:
#if FENNEC_HAS_LIBDECOR
			if (libdecorframe) {
				const bool vis = libdecor_frame_is_visible(libdecorframe);
				const bool tgt = not value;

				if (vis != tgt) {
					libdecor_frame_set_visibility(libdecorframe, tgt);
				}
			} else
#endif // FENNEC_HAS_LIBDECOR
			{

			}
			break;

		case flag_modal:
			break;

		case flag_pass_mouse:
			break;

		case flag_popup:
			break;

		case flag_resizable:
			break;

		case flag_transparent:
			break;

		case flag_no_focus:
			break;


		default:
			logger::log(logger::error, "Invalid flag passed to window::set_flag.");
			break;
	}

	// Store the value
	cfg.flags.store(flag, value);
	return true;
}


// Private Helpers =====================================================================================================

void wayland_window::_update_size(const ivec2& size) {
	//bool size_changed = any(notEqual(size, state.rect.size));

	state.rect.size = size;

	if (gfx_surface) {
		gfx_surface->resize(size);
	}

#if FENNEC_HAS_LIBDECOR
	if (libdecorframe) {
		libdecor_state* state = libdecor_state_new(size.x, size.y);
		libdecor_frame_commit(libdecorframe, state, libdecorcfg);
		libdecor_state_free(state);
		libdecorcfg = nullptr;
	}
#endif
}


// Listeners ===========================================================================================================

// Surface Listeners

void wayland_window::_wl_surface_listen_enter(void*, wl_surface*, wl_output*) {}
void wayland_window::_wl_surface_listen_leave(void*, wl_surface*, wl_output*) {}
void wayland_window::_wl_surface_listen_preferred_buffer_scale(void*, wl_surface*, int32_t) {}
void wayland_window::_wl_surface_listen_preferred_buffer_transform(void*, wl_surface*, uint32_t) {}


// Frame Listeners

void wayland_window::_wl_frame_listen_done(void*, wl_callback*, uint32_t) {}


// XDG Listeners

void wayland_window::_xdg_surface_listen_configure(void*, xdg_surface* xdg, uint32_t serial) {
	xdg_surface_ack_configure(xdg, serial);
}

void wayland_window::_xdg_toplevel_listen_configure(void*, xdg_toplevel*, int32_t, int32_t, wl_array*) {}
void wayland_window::_xdg_toplevel_listen_configure_bounds(void*, xdg_toplevel*, int32_t, int32_t) {}

void wayland_window::_xdg_toplevel_listen_close(void* data, xdg_toplevel*) {
	wayland_window* window = static_cast<wayland_window*>(data);
	window->shutdown();

}

void wayland_window::_xdg_toplevel_listen_wm_capabilities(void*, xdg_toplevel*, wl_array*) {}


// Libdecor Listeners

#if FENNEC_HAS_LIBDECOR

void wayland_window::_libdecor_frame_listen_configure(libdecor_frame* frame, libdecor_configuration* cfg, void* data) {
	wayland_window* window = static_cast<wayland_window*>(data);

	ivec2 size = window->state.rect.size;
	libdecor_configuration_get_content_size(cfg, frame, &size.x, &size.y);
	size.x = size.x == 0 ? window->state.rect.size.x : size.x;
	size.y = size.y == 0 ? window->state.rect.size.y : size.y;

	size.x = size.x == 0 ? window->cfg.rect.size.x : size.x;
	size.y = size.y == 0 ? window->cfg.rect.size.y : size.y;

	assertf(size.x != 0 or size.y != 0, "Invalid window size!");

	libdecor_window_state state = LIBDECOR_WINDOW_STATE_NONE;

	window->state.mode = mode_windowed;
	window->state.flags.clear(state_suspended);
	window->libdecorcfg = cfg;

	if (libdecor_configuration_get_window_state(cfg, &state)) {
		if (state & LIBDECOR_WINDOW_STATE_MAXIMIZED) {
			window->state.mode = mode_maximized;
		}

		if (state & LIBDECOR_WINDOW_STATE_FULLSCREEN) {
			window->state.mode = mode_fullscreen;
		}

		if (state & LIBDECOR_WINDOW_STATE_SUSPENDED) {
			window->state.flags.set(state_suspended);
		}
	}

	window->_update_size(size);
}

void wayland_window::_libdecor_frame_listen_close(libdecor_frame*, void* data) {
	wayland_window* window = static_cast<wayland_window*>(data);
	window->shutdown();
}

void wayland_window::_libdecor_frame_listen_commit(libdecor_frame*, void* data) {
	wayland_window* window = static_cast<wayland_window*>(data);
	window->gfx_surface->swap();
}

void wayland_window::_libdecor_frame_listen_dismiss_popup(libdecor_frame*, const char*, void*) {}

#endif

