// =====================================================================================================================
//  fennec, a free and open source game engine
//  Copyright © 2025  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/platform/linux/wayland/display.h>
#include <fennec/platform/linux/wayland/lib/loader.h>
#include <fennec/platform/linux/wayland/lib/wayland-client.h>

#include <fennec/lang/startup.h>

namespace fennec
{

static display* _create_wayland_display(platform* platform) {
	wayland_display* display = new wayland_display(platform);
	if (not display->connected()) {
		delete display, display = nullptr;
	}
	return display;
}

STATIC_CONSTRUCTOR(_wayland_init) {
	platform::add_driver(_create_wayland_display, 1);
}

wayland_display::wayland_display(platform* platform)
	: display(platform, "wayland", this)
	, _handle(nullptr)
	, _registry(nullptr)
	, _compositor(nullptr)
	, _shell(nullptr)
	, _seat(nullptr)
	, _shm(nullptr)
	, _fifo(false) {

	static constexpr wl_registry_listener listener = {
		listen_global, listen_global_remove
	};

	// Load libwayland.so
	libwayland::load_symbols(_platform);

	// Get handles
	_handle = wl_display_connect(nullptr);
	if (not _handle) {
		cleanup();
		return;
	}

	// Get the wayland registry
	_registry = wl_display_get_registry(_handle);
	if (not _registry) {
		cleanup();
		return;
	}

	// Add listener for interfaces
	wl_registry_add_listener(_registry, &listener, this);
	wl_display_roundtrip(_handle);

	// Check for fifo v1, wayland's builtin fifo only allows one frame to be in flight at a time
	if (not _fifo) {
		assert(_fifo, "Compositor does not support fifo-v1 protocol, falling back to other display protocols.");
		cleanup();
	}
}

wayland_display::~wayland_display() {
	if (_handle) {
		cleanup();
	}
}

bool wayland_display::connected() const {
	return _handle != nullptr;
}

window* wayland_display::create_window() {
	return nullptr;
}

void wayland_display::cleanup() {

	// Cleanup members
	if (_shell)      wl_shell_destroy(_shell);
	if (_compositor) wl_compositor_destroy(_compositor);
	if (_registry)   wl_registry_destroy(_registry);
	if (_handle) {
		wl_display_flush(_handle);
		wl_display_disconnect(_handle);
	}

	_shell = nullptr;
	_compositor = nullptr;
	_registry = nullptr;
	_handle = nullptr;

	libwayland::unload_symbols(_platform);
}

void wayland_display::listen_global(void* data, wl_registry* registry, uint32_t name, const char* itfc, uint32_t version) {
	static constexpr wl_seat_listener seat_listener = {
		listen_seat, nullptr
	};

	wayland_display* device = static_cast<wayland_display*>(data);
	const cstring interface = cstring(itfc, strlen(itfc) + 1);

	if (interface == "wl_compositor") {
		device->_compositor = static_cast<wl_compositor*>(wl_registry_bind(registry, name, wl_compositor_interface, version));
	} else if (interface == "wl_shell") {
		device->_shell = static_cast<wl_shell*>(wl_registry_bind(registry, name, wl_shell_interface, version));
	} else if (interface == "wl_seat") {
		device->_seat = static_cast<wl_seat*>(wl_registry_bind(registry, name, wl_seat_interface, version));
		wl_seat_add_listener(device->_seat, &seat_listener, device);
	} else if (interface == "wp_fifo_manager_v1") {
		device->_fifo = true;
	}

}

void wayland_display::listen_global_remove(void*, wl_registry*, uint32_t) {
}

void wayland_display::listen_seat(void*, wl_seat*, uint32_t) {

}

}
