#include "stdafx.h"
#include "Surface.h"
#include "App.h"
#include "RenderMgr.h"
#include "Window.h"
#include "Exception.h"
#include "Graphics.h"
#include "PangoMgr.h"
#include "Core/Convert.h"

#ifdef GUI_ENABLE_SKIA

namespace gui {

	// Needs to match the setup below.
	static const int stencilBits = 8;
	// This does not always seem to work when set to something else than 1. Skia can do
	// anti-aliasing anyway, so it is fine.
	static const int msaa = 1;

	SkiaSurface::SkiaSurface(Size size, Float scale, SkiaContext *context)
		: Surface2D(size, scale), context(context) {

		gdk_gl_context_make_current(context->context);

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

		glGenRenderbuffers(1, &colorbuffer);
		glGenRenderbuffers(1, &stencilbuffer);

		glBindRenderbuffer(GL_RENDERBUFFER, colorbuffer);
		glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8, size.w, size.h);

		// TODO: We might be able to use an entire 32 bits per pixel for the stencil? It seems like
		// Skia does not use the depth part of it anyway. Or perhaps just 8bpp for the stencil buffer?
		glBindRenderbuffer(GL_RENDERBUFFER, stencilbuffer);
		glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, size.w, size.h);

		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER, colorbuffer);
		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER, stencilbuffer);
		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT_EXT, GL_RENDERBUFFER, stencilbuffer);

		glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);


		GrGLFramebufferInfo info;
		info.fFBOID = framebuffer;
		info.fFormat = GL_RGB8;
		target = GrBackendRenderTargets::MakeGL(size.w, size.h, msaa, stencilBits, info);
		surface = SkSurfaces::WrapBackendRenderTarget(context->skia.get(), target,
													kBottomLeft_GrSurfaceOrigin,
													kRGB_888x_SkColorType,
													nullptr, nullptr);

		canvas = surface->getCanvas();
	}

	SkiaSurface::~SkiaSurface() {
		context->clearCurrent();
		gdk_gl_context_clear_current();
		gdk_gl_context_make_current(context->context);

		// Clear the surface now to avoid any errors when it flushes its buffers.
		surface = sk_sp<SkSurface>();

		glDeleteRenderbuffers(1, &colorbuffer);
		glDeleteRenderbuffers(1, &stencilbuffer);
		glDeleteFramebuffers(1, &framebuffer);

		context->unref();
	}

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

		// This dance is since Skia may change the current context, and since we and Gtk caches the
		// currently active context.
		context->clearCurrent();
		gdk_gl_context_clear_current();
		gdk_gl_context_make_current(context->context);
		glBindRenderbuffer(GL_RENDERBUFFER, colorbuffer);
		glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8, size.w, size.h);

		glBindRenderbuffer(GL_RENDERBUFFER, stencilbuffer);
		glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, size.w, size.h);

		// Clear out the old surface.
		surface = sk_sp<SkSurface>();

		// Create the surface again.
		GrGLFramebufferInfo info;
		info.fFBOID = framebuffer;
		info.fFormat = GL_RGB8;
		target = GrBackendRenderTargets::MakeGL(size.w, size.h, msaa, stencilBits, info);
		surface = SkSurfaces::WrapBackendRenderTarget(context->skia.get(), target,
														kBottomLeft_GrSurfaceOrigin,
														kRGB_888x_SkColorType,
														nullptr, nullptr);

		canvas = surface->getCanvas();
	}

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

	void SkiaSurface::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.
	}

	WindowGraphics *SkiaSurface::createGraphics(Engine &e) {
		return new (e) SkiaGraphics(*this, context->id);
	}

	Image *SkiaSurface::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
