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

#ifdef GUI_GTK

namespace gui {

	CairoSurface::CairoSurface(Nat id, Size size, Float scale)
		: Surface2D(size, scale), surface(null), device(null), id(id) {}


	CairoSurface::CairoSurface(Nat id, Size size, Float scale, cairo_surface_t *surface)
		: Surface2D(size, scale), surface(surface), device(null), id(id) {

		if (surface)
			device = cairo_create(surface);
	}

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

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

		cairo_destroy(device);
		cairo_surface_t *tmp = cairo_surface_create_similar(surface, CAIRO_CONTENT_COLOR, int(size.w), int(size.h));
		cairo_surface_destroy(surface);
		surface = tmp;
		device = cairo_create(surface);
	}

	Image *CairoSurface::grabImage(Engine &e) {
		Image *result = new (e) Image(size);
		// Need to keep the buffer on the stack, since we pass it to Cairo.
		byte *volatile buffer = null;
		atomicWrite(buffer, result->buffer());

		cairo_surface_t *output = cairo_image_surface_create_for_data(buffer, CAIRO_FORMAT_ARGB32,
																	(int)size.w, (int)size.h, (int)result->stride());

		cairo_t *c = cairo_create(output);
		cairo_set_source_surface(c, surface, 0, 0);
		cairo_paint(c);
		cairo_destroy(c);

		cairo_surface_destroy(output);

		// We need to convert the data...
		for (Nat y = 0; y < result->height(); y++) {
			// Interestingly enough it seems like pixels are flipped horizontally also.
			std::reverse((Nat *)result->buffer(0, y), (Nat *)result->buffer(result->width(), y));

			for (Nat x = 0; x < result->width(); x++) {
				// As specified by Cairo:
				byte *b = result->buffer(x, y);
				Nat pixel = *(Nat *)b;

				// Now, write according to our specification. Note, we don't really care about alpha
				// here since we render to a non-alpha buffer anyway.
				b[0] = (pixel >> 16) & 0xFF;
				b[1] = (pixel >>  8) & 0xFF;
				b[2] = (pixel >>  0) & 0xFF;
				b[3] = (pixel >> 24) & 0xFF;
			}
		}

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

		return result;
	}


	CairoBlitSurface::CairoBlitSurface(Nat id, Size size, Float scale, cairo_surface_t *surface)
		: CairoSurface(id, size, scale, surface) {}

	Surface::PresentStatus CairoBlitSurface::present(bool waitForVSync) {
		// We cannot do much here...
		return pRepaint;
	}

	void CairoBlitSurface::repaint(RepaintParams *params) {
		cairo_matrix_t matrix;
		cairo_get_matrix(params->cairo, &matrix);

		// Update the source matrix to represent the scale value.
		cairo_scale(params->cairo, 1/scale, 1/scale);

		// Then we can paint.
		cairo_set_source_surface(params->cairo, surface, 0, 0);
		cairo_paint(params->cairo);

		// Make sure the operation is not pending, this function is called inside a lock.
		cairo_surface_flush(cairo_get_group_target(params->cairo));

		// Restore old drawing matrix.
		cairo_set_matrix(params->cairo, &matrix);
	}

}

#endif
