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

#ifndef FENNEC_PLATFORM_INTERFACE_PLATFORM_H
#define FENNEC_PLATFORM_INTERFACE_PLATFORM_H

#include <fennec/containers/list.h>
#include <fennec/langproc/strings/cstring.h>
#include <fennec/langproc/strings/string.h>
#include <fennec/lang/typeuuid.h>
#include <fennec/platform/interface/fwd.h>

/*
 * This class should resemble the target platform as a whole. By itself, it will represent the OS, i.e. Linux, Windows,
 * etc.
 *
 * We want everything to be type agnostic from the top level down
 *
 * The general structure is:
 * Platform -> Display Protocol -> GFX Context    -> GFX API
 *                              -> Window         -> GFX Surface
 *                              -> Input Protocol
 *
 * For example, let's say we are on Linux, using Wayland and OpenGL with EGL
 * EGL will know that we are using OpenGL, but won't know we are using Wayland
 * OpenGL won't know or care that Linux, EGL, or Wayland is being used
 * Wayland won't know or care that OpenGL or EGL is being used
 * Linux won't know or care that OpenGL, Wayland, or EGL are in use
 *
 * Why do we want everything to be type agnostic?
 * Let's say someone wants to add a DirectX extension for Windows. They shouldn't have to touch the
 * Windows platform class or Win32 display manager class at all to write the implementation. This
 * allows the extension to remain entirely independent of the engine which allows you to just drop
 * it into your project, and it will work as expected. This should also keep version compatibility
 * issues to the absolute minimum.
 *
 * We can allow this by notifying the platform of all available drivers at startup and give them priorities.
 * Then, the platform will load the highest priority first, only falling back when a driver fails to load or is
 * deemed incompatible for whatever reason.
 *
 *
 */

namespace fennec
{

class platform {
public:
	using shared_object = struct shared_object;
	using symbol        = void*;

	const string name;
	const uint64_t uuid;

	virtual ~platform() = default;

	// Dynamically linked objects
	virtual shared_object* load_object(const cstring& file)  = 0;
	virtual void           unload_object(shared_object* obj) = 0;
	virtual symbol         find_symbol(shared_object* obj, const cstring& name) = 0;

	virtual void initialize(); // Initialize Drivers and Contexts
	virtual void shutdown(); // Close Drivers and Contexts

	display* get_display() { return _display; }

protected:
	template<typename PlatformT>
	explicit platform(const cstring& name, PlatformT*)
		: name(name)
		, uuid(typeuuid<PlatformT>()) {
		auto& globals = _get_globals();
		assertf(globals.singleton == nullptr, "Conflicting Platform Definitions.");
		globals.singleton = this;
	}

	virtual void load_display();

	display* _display;

private:
	platform(const platform&) = delete;

// Static Stuff ========================================================================================================

public:
	using display_ctor = display*     (*)(platform*);
	using   input_ctor = inputdevice* (*)(display*);
	using  gfxctx_ctor = gfxcontext*  (*)(display*);

	template<typename ctor>
	struct driver {
		int  priority;
		ctor constructor;
	};

	struct global_context {
		platform*                  singleton;
		list<driver<display_ctor>> displays;
		list<driver<input_ctor>>   inputs;
		list<driver<gfxctx_ctor>>  graphics;

		global_context()
			: singleton(nullptr) {
		}
	};

	static void add_driver(display_ctor ctor, int priority);
	static void add_driver(input_ctor   ctor, int priority);
	static void add_driver(gfxctx_ctor  ctor, int priority);

private:
	static global_context& _get_globals();

public:
	static const global_context& get_globals() {
		return _get_globals();
	}

	static platform* instance() {
		return _get_globals().singleton;
	}
};

}

#endif // FENNEC_PLATFORM_INTERFACE_PLATFORM_H
