// =====================================================================================================================
//  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/platform/window_manager.h>

#include <fennec/core/logger.h>
#include <fennec/platform/interface/platform.h>
#include <fennec/platform/interface/display_server.h>
#include <fennec/platform/interface/window.h>
#include <fennec/threading/lock_guard.h>

namespace fennec {

window_manager::window_manager(platform* platform)
	: _platform(platform)
	, _commands(max_commands) {}

window_manager::~window_manager() {
	shutdown();
}

void window_manager::initialize() {
	if (_display) {
		return;
	}

	// Get the list of registered display servers
	display_server::entrylist_t display_servers = display_server::get_type_list();

	// Find first valid server
	while (not display_servers.is_empty()) {
		const display_server::entry it = display_servers.front();
		display_servers.pop();

		unique_ptr<display_server> server = unique_ptr(it.ctor(_platform));
		server->connect();
		if (server->connected()) {
			logger::log(logger::info, format("Selected {} for the display server.", server->get_type().name()));
			_display = move(server);
			break;
		}
	}

	assertf(_display, "Failed to select a display server!");

	_thread = thread::current();

	logger::log(logger::info, format("Initializing Window Manager on thread: {:#016x}.", _thread));
}

void window_manager::shutdown() {
	if (not _display) {
		return;
	}

	assertf(_thread == thread::current(), "Attempted to shutdown Window Manager on a different thread!");
	lock_guard guard(_lock);

	// Cleanup Windows
	for (auto& window : _windows) {
		window->shutdown();
		window.reset();
	}
	_windows.clear();

	// Cleanup Display Server
	_display->disconnect();
	_display.reset();
}

void window_manager::dispatch() {
	if (not _display) {
		return;
	}

	assertf(_thread == thread::current(), "Attempted to dispatch Window Manager on a different thread!");
	lock_guard guard(_lock);

	size_t n = _commands.size();
	while (n--) {
		_commands.pop();
	}

	_display->dispatch();
}

window_id window_manager::create_window(const window::config& config, window_id parent) {
	if (not _display) {
		return nullid;
	}

	assertf(_thread == thread::current(), "Attempted to create a window on a different thread!");
	lock_guard guard(_lock);

	window* p = parent == nullid ? nullptr : _windows[parent].get();
	const window_id id = _windows.emplace(_display->create_window(config, p));
	_windows[id]->initialize();
	return id;
}

void window_manager::begin_frame(window_id window) {
	if (not _display) {
		return;
	}

	assertf(_thread == thread::current(), "Attempted to set window context on a different thread!");
	lock_guard guard(_lock);

	_windows[window]->begin_frame();
}

void window_manager::end_frame(window_id window) {
	if (not _display) {
		return;
	}

	assertf(_thread == thread::current(), "Attempted to set window context on a different thread!");
	lock_guard guard(_lock);

	_windows[window]->end_frame();
}

void window_manager::close(window_id window) {
	if (not _display) {
		return;
	}

	assertf(_thread == thread::current(), "Attempted to set window context on a different thread!");
	lock_guard guard(_lock);

	_windows[window]->shutdown();
	_windows[window] = nullptr;
	_windows.erase(window);
}

window_id window_manager::_parent(window_id id) const {
	window_id it = 0;
	for (const auto& window : _windows) {
		if (window.get() == _windows[id]->parent) {
			return it;
		}
		++it;
	}
	return nullid;
}

window_id window_manager::_root(window_id id) const {
	window_id it = 0;
	for (const auto& window : _windows) {
		if (window.get() == _windows[id]->root) {
			return it;
		}
		++it;
	}
	return nullid;
}

ivec2 window_manager::_get_size(window_id id) const {
	return _windows[id]->get_size();
}

ivec2 window_manager::_get_position(window_id id) const {
	return _windows[id]->get_position();
}

irect window_manager::_get_bounds(window_id id) const {
	return _windows[id]->get_bounds();
}

bool window_manager::_check_state(window_id id, uint8_t state) const {
	switch (state) {
		case window::state_running:   return _windows[id]->is_running();
		case window::state_child:     return _windows[id]->is_child();
		case window::state_suspended: return _windows[id]->is_suspended();
		case window::state_visible:   return _windows[id]->is_visible();
		default: return false;
	}
}

bool window_manager::_get_flag(window_id id, uint8_t flag) const {
	return _windows[id]->get_flag(flag);
}

bool window_manager::_set_flag(window_id, uint8_t, bool) {
	return false;
}

} // fennec