
#include <stdio.h>
#include <Platform/PlatformDisplay.h>
#include <Spectre/System/Log.h>

#include "glad_glx.h"
#include "Xlib.h"
#include "GLXContext.h"

namespace sp {

static bool loadGL() {

	static bool init = false;
	if (!init) {
		int ver;
		init = true;

		ver = gladLoaderLoadGL();
		if (!ver) {
			return false;
		}

		Log::info("GLX: Opengl Version %d.%d loaded.",
			GLAD_VERSION_MAJOR(ver), GLAD_VERSION_MINOR(ver));
	}

	return true;
}

// Ensure that OpenGL extensions are loaded.
static bool ensureExtensionsLoaded(::Display* disp, int screen)
{
	char msg[1024];
	static bool init = false;

	if (!init) {
		int ver;
		init = true;

		ver = gladLoaderLoadGLX(disp, screen);
		if (!ver) {
			Log::warn("GLX: Could not load GLX extensions");
			return false;
		}

		snprintf(msg, 1024, "GLX: GLX Version %d.%d loaded",
			GLAD_VERSION_MAJOR(ver), GLAD_VERSION_MINOR(ver));

		// Check for atleast version 1.4
		if (ver < 10004) {
			Log::error("%s but atleast version 1.4 is needed.", msg);
			return false;
		}

		Log::info("%s.", msg);
	}

	return true;
}

GLXContext::GLXContext() :
m_win	(0),
m_ctx	(NULL)
{
}

GLXContext::~GLXContext()
{
	destroy();
}

bool GLXContext::create(const PlatformDisplay* display)
{
	// Destroy any previous context first.
	destroy();

	m_win = (::Window) display->getHandle();

	if (!createGLContext()) {
		destroy();
		return false;
	}

	return true;
}

bool GLXContext::createGLContext()
{
	::Display* disp = Xlib::getDisplay();
	::GLXFBConfig *fbc;
	::XVisualInfo *info;
	int fbcount;

	GLint vi_attr[] = {
		GLX_RGBA,
		GLX_DEPTH_SIZE, 24,
		GLX_DOUBLEBUFFER, 1,
		GLX_RED_SIZE, 1,
		GLX_GREEN_SIZE, 1,
		GLX_BLUE_SIZE, 1,
		None
	};

	GLint ctx_attr[] = {
        GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
        GLX_CONTEXT_MINOR_VERSION_ARB, 2,
        GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
        None
    };

	// Ensure glx extensions are loaded.
	if (!ensureExtensionsLoaded(disp, DefaultScreen(disp))) {
		return false;
	}

	info = ::glXChooseVisual(disp, DefaultScreen(disp), vi_attr);
	if (info == NULL) {
		Log::warn("GLX: Could not find a valid VisualInfo.");
		return false;
	}

	// Setup GL settings.
    fbc = glXChooseFBConfig(disp, DefaultScreen(disp), (const int*) info->visual, &fbcount);
	if (fbc == NULL) {
		Log::warn("GLX: Could not find FB Config.");
		return false;
	}

	// Create context.
	m_ctx = glXCreateContextAttribsARB(disp, fbc[0], NULL, GL_TRUE, ctx_attr);
	if (m_ctx == NULL) {
		Log::warn("GLX: Failed to create context.");
		return false;
	}

    glXMakeCurrent(disp, m_win, m_ctx);

	// Load OpenGL
	if (!loadGL()) {
		Log::warn("GLX: Could not load OpenGL");
		return false;
	}

	return true;
}

void GLXContext::destroy()
{
	if (m_ctx) {
		::glXDestroyContext(Xlib::getDisplay(), m_ctx);
		m_ctx = NULL;
	}

	m_win = None;
}

bool GLXContext::activate()
{
	if (m_win && m_ctx) {
		return ::glXMakeCurrent(Xlib::getDisplay(), m_win, m_ctx);
	}
	return false;
}

bool GLXContext::deactivate()
{
	return ::glXMakeCurrent(Xlib::getDisplay(), None, NULL);
}

bool GLXContext::isActive() const
{
	return ::glXGetCurrentContext() == m_ctx;
}

bool GLXContext::setSwapInterval(int interval)
{
	::Display *disp = Xlib::getDisplay();
	ensureExtensionsLoaded(disp, DefaultScreen(disp));

	if (GLAD_GLX_MESA_swap_control) {
		return glXSwapIntervalMESA(interval);
	}

	if (GLAD_GLX_SGI_swap_control) {
		return glXSwapIntervalSGI(interval);
	}

	return false;
}

void GLXContext::setSize(unsigned int width, unsigned int height)
{
	// TODO: Not GLX specific! move to generic GLContext class.
	if (activate()) {
		glViewport(0, 0, width, height);
	}
}

void GLXContext::setSize(const Vector2u size)
{
	setSize(size.x, size.y);
}

void GLXContext::swapBuffers()
{
	glXSwapBuffers(Xlib::getDisplay(), m_win);
}

} // namespace sp
