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

#include <fennec/platform/opengl/egl/context.h>

#include <fennec/platform/opengl/glad/gl.h>

namespace fennec
{

static const cstring& egl_translate_type(EGLint type) {
	static constexpr cstring opengl = "OpenGL";
	static constexpr cstring gles   = "GLES";
	static constexpr cstring openvg = "OpenVG"; // this should never be used
	switch (type) {
		default:
		case EGL_OPENGL_API:    return opengl;
		case EGL_OPENGL_ES_API: return gles;
		case EGL_OPENVG_API:    return openvg;
	}

}

eglcontext::eglcontext(display_server* display)
	: glcontext(display)
	, _egldisplay(nullptr)
	, _eglcontext(nullptr)
	, _eglconfig(nullptr)
	, _eglvmajor(0)
	, _eglvminor(0)
	, _eglctype(0) {

	EGLint config_attrs[] = {
		EGL_SURFACE_TYPE,
		EGL_WINDOW_BIT,

		EGL_RENDERABLE_TYPE,
		EGL_OPENGL_BIT, // 4
		EGL_NONE
	};
	static constexpr size_t api_index = 4;

	EGLint context_attrs[] = {
		EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR,
		EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR,
		EGL_NONE
	};

	// Load EGL bindings
	assertf(gladLoaderLoadEGL(_egldisplay), "Unable to load EGL Bindings.");

	// Get the EGL display from the display server
	_egldisplay = eglGetDisplay(display->get_native_handle());
	assertf(_egldisplay, "Failed to connect to egl display.");

	// Initialize EGL
	assertf(eglInitialize(_egldisplay, &_eglvmajor, &_eglvminor), "Failed to initialize egl.");
	logger::log(logger::info, format("Initialized EGL v{}.{}", _eglvmajor, _eglvminor));

	// Reload EGL bindings
	assertf(gladLoaderLoadEGL(_egldisplay), "Unable to load EGL Bindings.");

	// Attempt to bind to OpenGL or GLES
	if (not eglBindAPI(EGL_OPENGL_API)) {
		config_attrs[api_index] = EGL_OPENGL_ES_BIT;
		assertf(eglBindAPI(EGL_OPENGL_ES_API), "Failed to find suitable OpenGL API.");
	}

	// Select a configuration
	EGLint n;
	assertf(eglChooseConfig(_egldisplay, config_attrs, &_eglconfig, 1, &n), "Failed to find suitable OpenGL configuration.");

	// Create a context from the configuration and display
	_eglcontext = eglCreateContext(_egldisplay, _eglconfig, EGL_NO_CONTEXT, context_attrs);
	assertf(_eglcontext, "Failed to create OpenGL Context.");

	// Make the created context the current context to query some info
	eglMakeCurrent(_egldisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, _eglcontext);

	// Load OpenGL bindings
	assertf(gladLoaderLoadGL(), "Unable to load OpenGL Bindings.");

	// Query version information
	eglQueryContext(_egldisplay, _eglcontext, EGL_CONTEXT_CLIENT_TYPE, &_eglctype);
	glGetIntegerv(GL_MAJOR_VERSION, &_eglvmajor);
	glGetIntegerv(GL_MINOR_VERSION, &_eglvminor);

	// Set the version fields
	version.major = _eglvmajor;
	version.minor = _eglvminor;
	version.patch = 0;
	version.meta  = _eglctype;

	logger::log(logger::alert, format("Created OpenGL Context: {} v{}.{}", egl_translate_type(version.meta), version.major, version.minor));
}

eglcontext::~eglcontext() {
	// Ensure we are in the correct context
	eglMakeCurrent(_egldisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, _eglcontext);

	// Unload GL bindings
	gladLoaderUnloadGL();

	// Cleanup the context
	eglMakeCurrent(_egldisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
	eglDestroyContext(_egldisplay, _eglcontext);
	eglTerminate(_egldisplay);
	eglReleaseThread();

	// Unload EGL bindings
	gladLoaderUnloadEGL();

	// Reset data members
	_egldisplay = nullptr;
	_eglconfig  = nullptr;
	_eglcontext = nullptr;
	_eglvmajor  = 0;
	_eglvminor  = 0;

	// Reset version
	version.major = 0;
	version.minor = 0;
	version.patch = 0;
	version.meta  = 0;
}

bool eglcontext::is_valid() {
	return _egldisplay != nullptr;
}

}
