#include "stdafx.h"
#include "GLSurface.h"
#include "Graphics.h"
#include "RenderMgr.h"
#include "Exception.h"
#include "App.h"
#include "Window.h"
#include "TextMgr.h"

#ifdef GUI_ENABLE_CAIRO_GL

namespace gui {

	CairoGLSurface::CairoGLSurface(Size size, Float scale, CairoGLContext *context)
		: CairoSurface(context->id, size, scale, null), context(context), texture(0) {

		int width = size.w;
		int height = size.h;

		gdk_gl_context_clear_current();
		gdk_gl_context_make_current(context->context);

		glGenTextures(1, &texture);

		glBindTexture(GL_TEXTURE_2D, texture);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

		// If we don't use ES, we need GL_BGRA instead of GL_RGBA
		// glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);

		surface = cairo_gl_surface_create_for_texture(context->device, CAIRO_CONTENT_COLOR, texture, width, height);
		device = cairo_create(surface);
	}

	CairoGLSurface::~CairoGLSurface() {
		cairo_destroy(device);
		device = null;
		cairo_surface_destroy(surface);
		surface = null;

		gdk_gl_context_clear_current();
		gdk_gl_context_make_current(context->context);
		glDeleteTextures(1, &texture);

		context->unref();
	}

	WindowGraphics *CairoGLSurface::createGraphics(Engine &e) {
		return new (e) CairoGraphics(*this, id, true);
	}

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

		int width = size.w;
		int height = size.h;

		cairo_destroy(device);
		cairo_surface_destroy(surface);

		// The clear is since Cairo will switch GL context from time to time, turning make_context
		// into a noop some times, when it really should not be a noop.
		gdk_gl_context_clear_current();
		gdk_gl_context_make_current(context->context);

		// Save the old active texture. Otherwise Cairo will be very confused.
		GLint oldTexture;
		glGetIntegerv(GL_TEXTURE_BINDING_2D, &oldTexture);

		// Now, we can bind the proper texture and resize it.
		glBindTexture(GL_TEXTURE_2D, texture);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);

		// Restore the old one. Otherwise Cairo will be unhappy.
		glBindTexture(GL_TEXTURE_2D, oldTexture);

		// ...and finally re-create the surface.
		surface = cairo_gl_surface_create_for_texture(context->device, CAIRO_CONTENT_COLOR, texture, width, height);
		device = cairo_create(surface);
	}

	Surface::PresentStatus CairoGLSurface::present(bool waitForVSync) {
		glFlush();
		return pRepaint;
	}

	void CairoGLSurface::repaint(RepaintParams *params) {
		// Note: For some reason we get terrible artifacts if we switch the GL context here.
		// Even though the manual states that we should select the appropriate GL context before
		// doing anything, the implementation does not require that. It quickly selects its own
		// GL context.

		// gdk_gl_context_clear_current();
		// gdk_gl_context_make_current(context->context);
		gdk_cairo_draw_from_gl(params->cairo, params->window,
							texture, GL_TEXTURE, 1 /* scale */,
							0, 0, int(size.w), int(size.h));
		// Make sure everything is flushed now. Otherwise, we might see flickering when we start re-painting again.
		glFlush();
	}

	Image *CairoGLSurface::grabImage(Engine &e) {
		gdk_gl_context_clear_current();
		gdk_gl_context_make_current(context->context);

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

		// Make sure the proper framebuffer is bound.
		glGetTextureImage(texture, 1, GL_RGBA, GL_UNSIGNED_BYTE, result->bufferSize(), result->buffer());

		// Flip vertically.
		TODO(L"Maybe need vflip?");
		// result->vflip();

		return result;
	}

}

#endif
