#include "stdafx.h"
#include "Shader.h"
#include "Core/Convert.h"
#include "Exception.h"

#ifdef GUI_GTK

namespace gui {

	static GLuint createShader(GLenum shaderType, Str *source) {
		GLuint shader = glCreateShader(shaderType);

		const char *src = source->utf8_str();
		glShaderSource(shader, 1, &src, NULL);
		glCompileShader(shader);

		GLint result = GL_FALSE;
		int logLength = 0;
		glGetShaderiv(shader, GL_COMPILE_STATUS, &result);
		glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength);
		if (logLength > 0) {
			vector<char> messages(size_t(logLength) + 1);
			glGetShaderInfoLog(shader, logLength, NULL, &messages[0]);

			Str *m = new (source) Str(toWChar(source->engine(), &messages[0]));
			throw new (source) GuiError(TO_S(source, S("Failed to compile shader:\n") << source << S("\n\n") << m));
		}

		return shader;
	}

	GLVertexShader::GLVertexShader(GLSurface &surface, VertexShader *shader) : surface(surface) {
		surface.makeCurrent();

		// Create the shader.
		this->shader = createShader(GL_VERTEX_SHADER, shader->source(shaderGLSL));
	}

	GLVertexShader::~GLVertexShader() {
		surface.makeCurrent();
		glDeleteShader(shader);
	}


	GLPixelShader::GLPixelShader(GLSurface &surface, PixelShader *shader) : surface(surface) {
		surface.makeCurrent();

		this->shader = createShader(GL_FRAGMENT_SHADER, shader->source(shaderGLSL));
	}

	GLPixelShader::~GLPixelShader() {
		surface.makeCurrent();
		glDeleteShader(shader);
	}


	GLProgram::GLProgram(GLVertexShader *vertex, GLPixelShader *pixel, VertexDesc *inputDesc, Pipeline *pipeline)
		: surface(vertex->surface), constantBuffers(pipeline->bufferCount(), 0) {

		surface.makeCurrent();
		program = glCreateProgram();
		glAttachShader(program, vertex->shader);
		glAttachShader(program, pixel->shader);
		glLinkProgram(program);

		GLint result = GL_FALSE;
		int logLength = 0;
		glGetProgramiv(program, GL_LINK_STATUS, &result);
		glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength);
		if (logLength > 0) {
			vector<char> messages(size_t(logLength) + 1);
			glGetProgramInfoLog(program, logLength, NULL, &messages[0]);

			Engine &e = inputDesc->engine();
			Str *m = new (e) Str(toWChar(e, &messages[0]));
			throw new (e) GuiError(TO_S(e, S("Failed to link shader: ") << m));
		}

		// Create the input format.
		glCreateVertexArrays(1, &inputFormat);

		// Note: We use "Direct State Access" to avoid binding the buffer here. We should perhaps
		// use it in more places where applicable. Keeping track of global state is not fun...
		for (Nat i = 0; i < inputDesc->count(); i++) {
			const VertexDesc::Elem &elem = inputDesc->at(i);

			for (Nat component = 0; component < elem.components; component += 4) {
				Nat index = elem.identifier + component;

				glEnableVertexArrayAttrib(inputFormat, index);
				glVertexArrayAttribFormat(inputFormat, index,
										min(elem.components, Nat(4)), elem.integer ? GL_INT : GL_FLOAT, GL_FALSE,
										elem.offset);
				// Note: Third parameter is which *buffer* the binding originates from. This lets us
				// bind more than one input array!
				glVertexArrayAttribBinding(inputFormat, i, elem.slot);
			}
		}

		// Create buffers.
		glCreateBuffers((GLuint)pipeline->bufferCount(), &constantBuffers[0]);

		for (size_t i = 0; i < pipeline->bufferCount(); i++) {
			const GcArray<Byte> *src = pipeline->buffer(i);

			glNamedBufferStorage(constantBuffers[i], src->count(), src->v, GL_DYNAMIC_STORAGE_BIT);
		}
		glBindBuffer(GL_UNIFORM_BUFFER, 0);
	}

	void GLProgram::update(Pipeline *pipeline, DirtyRange dirty) {
		surface.makeCurrent();

		dirty.end = min(dirty.end, Nat(constantBuffers.size()));

		for (Nat i = dirty.begin; i < dirty.end; i++) {
			const GcArray<Byte> *src = pipeline->buffer(i);

			glNamedBufferSubData(constantBuffers[i], 0, src->count(), src->v);
		}
	}

	GLProgram::~GLProgram() {
		surface.makeCurrent();
		glDeleteProgram(program);
		glDeleteVertexArrays(1, &inputFormat);
		glDeleteBuffers((GLuint)constantBuffers.size(), &constantBuffers[0]);
	}

}

#endif
