#include "stdafx.h"
#include "Surface.h"
#include "Graphics.h"

#ifdef GUI_GTK

namespace gui {

#ifdef DEBUG

	static const char *msgType(GLenum type) {
		switch (type) {
		case GL_DEBUG_TYPE_ERROR:
			return "** GL ERROR **";
		case GL_DEBUG_TYPE_OTHER:
			return "other";
		case GL_DEBUG_TYPE_PERFORMANCE:
			return "performance";
		default:
			return "";
		}
	}

	static const char *msgSeverity(GLenum sev) {
		switch (sev) {
		case GL_DEBUG_SEVERITY_LOW:
			return "low";
		case GL_DEBUG_SEVERITY_MEDIUM:
			return "medium";
		case GL_DEBUG_SEVERITY_HIGH:
			return "high";
		case GL_DONT_CARE:
			return "don't care";
		default:
			return "<unknown>";
		}
	}

	static void GLAPIENTRY onGlError(GLenum source, GLenum type, GLuint id, GLenum severity,
							GLsizei length, const GLchar* message, const void* userParam)
	{
		fprintf(stderr, "GL CALLBACK: type = 0x%x (%s), severity = 0x%x (%s), message = %s\n",
				type, msgType(type), severity, msgSeverity(severity), message);
	}

#endif


	GLSurface::GLSurface(Size size, Float scale, GLDevice::Context *context)
		: Surface3D(size, scale), context(context) {

		forceMakeCurrent();

#ifdef DEBUG
		// Enable debug output
		glEnable(GL_DEBUG_OUTPUT);
		glDebugMessageCallback(&onGlError, 0);
#endif

		glGenFramebuffers(1, &framebuffer);
		glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);

		// Create the color buffer.
		glGenRenderbuffers(1, &colorbuffer);
		glBindRenderbuffer(GL_RENDERBUFFER, colorbuffer);
		glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8, size.w, size.h);

		// Create the depth buffer.
		glGenRenderbuffers(1, &depthbuffer);
		glBindRenderbuffer(GL_RENDERBUFFER, depthbuffer);
		glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT32F, size.w, size.h);

		// Bind them to our framebuffer.
		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER, colorbuffer);
		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER, depthbuffer);

		// Set our viewport.
		// Note: Coordinate systems differ on OpenGL and D3D.
		// OpenGL seems to have viewport between -1 and 1, while D3D has between -0.5 and 0.5.
		glViewport(0, 0, size.w, size.h);
	}

	GLSurface::~GLSurface() {
		forceMakeCurrent();

		// Delete things we have allocated.
		glDeleteRenderbuffers(1, &colorbuffer);
		glDeleteRenderbuffers(1, &depthbuffer);
		glDeleteFramebuffers(1, &framebuffer);

		// Delete the context.
		context->unref();
	}

	void GLSurface::makeCurrent() {
		gdk_gl_context_make_current(context->context);
	}

	void GLSurface::forceMakeCurrent() {
		gdk_gl_context_clear_current();
		gdk_gl_context_make_current(context->context);
	}

	void GLSurface::resize(Size size, Float scale) {
		this->size = size;
		this->scale = scale;

		forceMakeCurrent();

		// Resize render buffers:
		glBindRenderbuffer(GL_RENDERBUFFER, colorbuffer);
		glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8, size.w, size.h);

		glBindRenderbuffer(GL_RENDERBUFFER, depthbuffer);
		glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT32F, size.w, size.h);

		// Resize the viewport.
		glViewport(0, 0, size.w, size.h);
	}

	Graphics3D *GLSurface::createGraphics(Engine &e) {
		return new (e) GLGraphics(*this, context->id);
	}

	Surface::PresentStatus GLSurface::present(bool waitForVSync) {
		// We can not bind the same EGL context from multiple threads. So un-bind it from here first.
		gdk_gl_context_clear_current();
		return pRepaint;
	}

	void GLSurface::repaint(RepaintParams *params) {
		gdk_gl_context_make_current(context->context);
		gdk_cairo_draw_from_gl(params->cairo, params->window,
							colorbuffer, GL_RENDERBUFFER, 1 /* scale */,
							0, 0, int(size.w), int(size.h));
		// Flush GL state from the copy to GTK. Otherwise, we might see flickering.
		glFlush();
		// Note: gdk_cairo_draw_from_gl generally switches GL context to the paint context, so we
		// don't need to switch contexts.
	}

	Image *GLSurface::grabImage(Engine &e) {
		makeCurrent();

		// Allocate an Image to store data inside.
		Image *result = new (e) Image(size);

		// Make sure the proper framebuffer is bound.
		glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
		glReadPixels(0, 0, (GLsizei)size.w, (GLsizei)size.h, GL_RGBA, GL_UNSIGNED_BYTE, result->buffer());

		// Flip vertically.
		result->vflip();

		return result;
	}

}

#endif
