// =====================================================================================================================
//  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/>.
// =====================================================================================================================

///
/// \file fennec/platform/window_manager.h
/// \brief
///
///
/// \details
/// \author Medusa Slockbower
///
/// \copyright Copyright © 2025 - 2026 Medusa Slockbower ([GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html))
///
///


#ifndef FENNEC_PLATFORM_WINDOWMANAGER_H
#define FENNEC_PLATFORM_WINDOWMANAGER_H

#include <fennec/platform/interface/forward.h>

#include <fennec/memory/pointers.h>
#include <fennec/containers/object_pool.h>
#include <fennec/containers/variant.h>
#include <fennec/platform/interface/window.h>
#include <fennec/threading/lock_guard.h>
#include <fennec/threading/mpscq.h>
#include <fennec/threading/mutex.h>
#include <fennec/threading/thread.h>

namespace fennec
{

using window_id = size_t; //!< type representing an id for a window

///
/// \brief class for handling display servers and windows
class window_manager {
// Definitions & Constants =============================================================================================
public:

	/// \name Constants
	/// @{

	static constexpr window_id nullid = -1; //!< constant representing a null window

	/// @}

private:
	enum command_ : uint8_t {
		command_set_flag = 0,
		command_resize
	};

	struct flag_val {
		uint8_t index;
		bool    val;
	};

	using cmd_data_t = variant<flag_val, ivec2>;

	struct command {
		uint8_t    cmd;
		window_id  win;
		cmd_data_t data;
	};

	static constexpr size_t max_commands = 512;

	using server_t        = unique_ptr<display_server>;
	using window_t        = unique_ptr<window>;
	using window_pool_t   = object_pool<window_t>;
	using command_queue_t = mpscq<command>;
	using config          = window::config;


// Constructors & Destructor ===========================================================================================
public:

	/// \name Constructors & Destructor
	/// @{

	///
	/// \brief constructor
	/// \param platform the platform
	window_manager(platform* platform);

	///
	/// \brief destructor
	~window_manager();

	///
	/// \brief Copy Constructor
	/// \details Deleted, no semantics for copying a platform
	window_manager(const window_manager&) = delete;

	/// @}


// Initialization & Update =============================================================================================
public:

	/// \name Initialization & Update
	/// @{

	///
	/// \brief Initialization
	///
	/// \details Initializes the windowing system on the current thread.
	void initialize();


	///
	/// \brief Shutdown
	///
	/// \details Shuts down the windowing system, must be called on the same thread as `window_manager::initialize`.
	void shutdown();

	///
	/// \brief Command Dispatch
	///
	/// \details Dispatches commands to the server, must be called on the same thread as `window_manager::initialize`.
	void dispatch();

	/// @}


// Operations ==========================================================================================================
public:

	/// \name Operations
	/// @{

	///
	/// \brief Create a new window.
	///
	/// \details Must be called on the same thread as `window_manager::initialize`.
	/// \param config The configuration for the new window.
	/// \param parent The parent window.
	window_id create_window(const config& config, window_id parent = nullid);

	///
	/// \brief Start a frame for a window.
	/// \param window The window.
	///
	/// \details Begins a new frame for the specified window. This will make the context and surface associated with
	///          this window the new current context. Must be called on the same thread as `window_manager::initialize`.
	void begin_frame(window_id window);

	///
	/// \brief Ends a frame for the window.
	/// \param window The window.
	///
	/// \details Ends the current frame for the specified window. This will swap the front and back buffers for the
	///          window. Must be called on the same thread as `window_manager::initialize`.
	void end_frame(window_id window);

	///
	/// \brief Closes a window.
	/// \param window The window.
	///
	/// \details Closes the window, cleaning up the surface associated with the window.
	void close(window_id window);

	/// @}


// Window Properties ===================================================================================================
public:

	/// \name Window Properties
	/// @{

	///
	/// \param window The window.
	/// \returns the parent window
	window_id get_parent(window_id window) const {
		lock_guard guard(_lock);
		return _parent(window);
	}

	///
	/// \param window The window.
	/// \returns the nearest top-level window in the hierarchy
	window_id get_root(window_id window) const {
		lock_guard guard(_lock);
		return _root(window);
	}

	/// @}


// Window Bounds =======================================================================================================
public:

	/// \name Window Bounds
	/// @{

	///
	/// \param window The window.
	/// \returns the size of the window
	ivec2 get_size(window_id window) const {
		lock_guard guard(_lock);
		return _get_size(window);
	}

	///
	/// \param window The window.
	/// \returns the position of the window
	ivec2 get_position(window_id window) const {
		lock_guard guard(_lock);
		return _get_position(window);
	}

	///
	/// \param window The window.
	/// \returns the bounds of the window
	irect get_bounds(window_id window) const {
		lock_guard guard(_lock);
		return _get_bounds(window);
	}

	/// @}


// Window Status =======================================================================================================
public:

	/// \name Window Status
	/// @{

	///
	/// \brief tests if the window is visible
	///
	/// \param window The window.
	/// \returns \emph{true} if the window is visible, \emph{false} otherwise
	bool is_visible(window_id window)   const {
		lock_guard guard(_lock);
		return _check_state(window, window::state_visible);
	}

	///
	/// \brief tests if the window is a child
	///
	/// \param window The window.
	/// \returns \emph{true} if the window is a child, \emph{false} otherwise
	bool is_child(window_id window)     const {
		lock_guard guard(_lock);
		return _check_state(window, window::state_child);
	}

	///
	/// \brief tests if the window is running
	///
	/// \param window The window.
	/// \returns \emph{true} if the window is running, \emph{false} otherwise
	bool is_running(window_id window)   const {
		lock_guard guard(_lock);
		return _check_state(window, window::state_running);
	}

	///
	/// \brief tests if the window is suspended
	///
	/// \param window The window.
	/// \returns \emph{true} if the window is suspended, \emph{false} otherwise
	bool is_suspended(window_id window) const {
		lock_guard guard(_lock);
		return _check_state(window, window::state_suspended);
	}

	/// @}


// Window Behaviour Flags ==============================================================================================
public:

	/// \name Window Behaviour Flags
	/// @{

	///
	/// \brief tests a specific flag
	///
	/// \param window The window.
	/// \param flag the flag from `window::flag_`
	/// \returns \emph{true} if the flag is set, \emph{false} otherwise
	bool get_flag(window_id window, uint8_t flag) {
		lock_guard guard(_lock);
		return _get_flag(window, flag);
	}


	///
	/// \brief check if the window is flagged to always be on top of other windows
	///
	/// \param window The window.
	/// \returns \emph{true} if set, \emph{false} otherwise
	bool is_always_on_top(window_id window) {
		return get_flag(window, window::flag_always_on_top);
	}

	///
	/// \brief check if the window is flagged to have no window decorations
	///
	/// \param window The window.
	/// \returns \emph{true} if set, \emph{false} otherwise
	bool is_borderless(window_id window) {
		return get_flag(window, window::flag_borderless);
	}

	///
	/// \brief check if the window is flagged to be modal
	///
	/// \param window The window.
	/// \returns \emph{true} if set, \emph{false} otherwise
	bool is_modal(window_id window) {
		return get_flag(window, window::flag_modal);
	}

	///
	/// \brief check if the window is flagged to pass mouse input to windows underneath from the same application
	///
	/// \param window The window.
	/// \returns \emph{true} if set, \emph{false} otherwise
	bool is_passing_mouse(window_id window) {
		return get_flag(window, window::flag_pass_mouse);
	}

	///
	/// \brief check if the window is flagged as a popup
	///
	/// \param window The window.
	/// \returns \emph{true} if set, \emph{false} otherwise
	bool is_popup(window_id window) {
		return get_flag(window, window::flag_popup);
	}

	///
	/// \brief check if the window is flagged to be resizable
	///
	/// \param window The window.
	/// \returns \emph{true} if set, \emph{false} otherwise
	bool is_resizable(window_id window) {
		return get_flag(window, window::flag_resizable);
	}

	///
	/// \brief check if the window is flagged to be transparent
	///
	/// \param window The window.
	/// \returns \emph{true} if set, \emph{false} otherwise
	bool is_transparent(window_id window) {
		return get_flag(window, window::flag_transparent);
	}

	///
	/// \brief check if the window is flagged to be unfocusable
	///
	/// \param window The window.
	/// \returns \emph{true} if set, \emph{false} otherwise
	bool is_no_focus(window_id window) {
		return get_flag(window, window::flag_no_focus);
	}


	///
	/// \brief sets a specific flag
	///
	/// \param window The window.
	/// \param flag the flag from `window::flag_`
	/// \param val the value to set the flag to
	void set_flag(window_id window, uint8_t flag, bool val) {
		lock_guard guard(_lock);

		_commands.push(command {
			.cmd  = command_set_flag,
			.win  = window,
			.data = flag_val{ .index = flag, .val = val }
		});
	}

	///
	/// \brief sets whether to always be on top of other windows
	///
	/// \param window The window.
	/// \param val the value to set the flag to
	void set_always_on_top(window_id window, bool val) {
		return set_flag(window, window::flag_always_on_top, val);
	}

	///
	/// \brief sets whether to have no window decorations
	///
	/// \param window The window.
	/// \param val the value to set the flag to
	void set_borderless(window_id window, bool val) {
		return set_flag(window, window::flag_borderless, val);
	}

	///
	/// \brief sets whether to be modal
	///
	/// \param window The window.
	/// \param val the value to set the flag to
	void set_modal(window_id window, bool val) {
		return set_flag(window, window::flag_modal, val);
	}

	///
	/// \brief sets whether to pass mouse input to windows underneath from the same application
	///
	/// \param window The window.
	/// \param val the value to set the flag to
	void set_passing_mouse(window_id window, bool val) {
		return set_flag(window, window::flag_pass_mouse, val);
	}

	///
	/// \brief sets whether the window is a popup
	///
	/// \param window The window.
	/// \param val the value to set the flag to
	void set_popup(window_id window, bool val) {
		return set_flag(window, window::flag_popup, val);
	}

	///
	/// \brief sets whether to be resizable
	///
	/// \param window The window.
	/// \param val the value to set the flag to
	void set_resizable(window_id window, bool val) {
		return set_flag(window, window::flag_resizable, val);
	}

	///
	/// \brief sets whetherto be transparent
	///
	/// \param window The window.
	/// \param val the value to set the flag to
	void set_transparent(window_id window, bool val) {
		return set_flag(window, window::flag_transparent, val);
	}

	///
	/// \brief sets whether to be unfocusable
	///
	/// \param window The window.
	/// \param val the value to set the flag to
	void set_no_focus(window_id window, bool val) {
		return set_flag(window, window::flag_no_focus, val);
	}

	/// @}


// Private Member Variables ============================================================================================
private:
	mutable mutex   _lock;
	thread::id      _thread;
	platform*       _platform;
	server_t        _display;
	window_pool_t   _windows;
	command_queue_t _commands;


// Private Helpers =====================================================================================================
private:
	window_id _parent(window_id   id) const;
	window_id _root(window_id     id) const;

	ivec2 _get_size(window_id     id) const;
	ivec2 _get_position(window_id id) const;
	irect _get_bounds(window_id   id) const;

	bool _check_state(window_id id, uint8_t state) const;

	bool _get_flag(window_id id, uint8_t flag) const;
	bool _set_flag(window_id id, uint8_t flag, bool val);

};

} // fennec

#endif // FENNEC_PLATFORM_WINDOWMANAGER_H