/******************************************************************************
 * Spine Runtimes Software License v2.5
 *
 * Copyright (c) 2013-2016, Esoteric Software
 * All rights reserved.
 *
 * You are granted a perpetual, non-exclusive, non-sublicensable, and
 * non-transferable license to use, install, execute, and perform the Spine
 * Runtimes software and derivative works solely for personal or internal
 * use. Without the written permission of Esoteric Software (see Section 2 of
 * the Spine Software License Agreement), you may not (a) modify, translate,
 * adapt, or develop new applications using the Spine Runtimes or otherwise
 * create derivative works or improvements of the Spine Runtimes or (b) remove,
 * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
 * or other intellectual property or proprietary rights notices on or in the
 * Software, including any copy thereof. Redistributions in binary or source
 * form must include this license and terms.
 *
 * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF
 * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *****************************************************************************/

#include "GLUtils.h"

#include <stddef.h>

#include <spine/extension.h>

#include <TargetConditionals.h>
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
#include <OpenGLES/ES2/gl.h>
#include <OpenGLES/ES2/glext.h>
#else
#include <OpenGL/gl.h>
#include <OpenGL/glu.h>
#endif

#include <stdio.h>

#define STRINGIFY(A)  #A
#define MAX_VERTICES 64000
#define MAX_INDICES 64000

const char* TWO_COLOR_TINT_VERTEX_SHADER = STRINGIFY(
attribute vec4 a_position;
attribute vec4 a_color;
attribute vec4 a_color2;
attribute vec2 a_texCoords;

\n#ifdef GL_ES\n
varying lowp vec4 v_light;
varying lowp vec4 v_dark;
varying mediump vec2 v_texCoord;
\n#else\n
varying vec4 v_light;
varying vec4 v_dark;
varying vec2 v_texCoord;
\n#endif\n

void main() {
	v_light = a_color;
	v_dark = a_color2;
	v_texCoord = a_texCoords;
	gl_Position = a_position;
}
);

const char* TWO_COLOR_TINT_FRAGMENT_SHADER = STRINGIFY(
\n#ifdef GL_ES\n
precision lowp float;
\n#endif\n
													   
uniform sampler2D texture;

varying vec4 v_light;
varying vec4 v_dark;
varying vec2 v_texCoord;

void main() {
   vec4 texColor = texture2D(texture, v_texCoord);
   float alpha = texColor.a * v_light.a;
   gl_FragColor.a = alpha;
   gl_FragColor.rgb = ((texColor.a - 1.0) * v_dark.a + 1.0 - texColor.rgb) * v_dark.rgb + texColor.rgb * v_light.rgb;
}
);

spMesh* spMesh_create(uint32_t numVertices, uint32_t numIndices) {
	spMesh* mesh = MALLOC(spMesh, 1);
	mesh->vertices = MALLOC(spVertex, numVertices);
	mesh->indices = MALLOC(unsigned short, numIndices);
	mesh->numVertices = numVertices;
	mesh->numIndices = numIndices;
	mesh->numAllocatedVertices = 0;
	mesh->numAllocatedIndices = 0;
	return mesh;
}

void spMesh_allocatePart(spMesh* mesh, spMeshPart* part, uint32_t numVertices, uint32_t numIndices, uint32_t textureHandle, uint32_t srcBlend, uint32_t dstBlend) {
	if (mesh->numVertices < mesh->numAllocatedVertices + numVertices) {
		mesh->numVertices = mesh->numAllocatedVertices + numVertices;
		mesh->vertices = REALLOC(mesh->vertices, spVertex, mesh->numVertices);
	}
	if (mesh->numIndices < mesh->numAllocatedIndices + numIndices) {
		mesh->numIndices = mesh->numAllocatedIndices + numIndices;
		mesh->indices = REALLOC(mesh->indices, unsigned short, mesh->numIndices);
	}
	
	part->mesh = mesh;
	part->startVertex = mesh->numAllocatedVertices;
	part->numIndices = numIndices;
	part->startIndex = mesh->numAllocatedIndices;
	part->numVertices = numVertices;
	part->textureHandle = textureHandle;
	part->srcBlend = srcBlend;
	part->dstBlend = dstBlend;
	
	mesh->numAllocatedVertices += numVertices;
	mesh->numAllocatedIndices += numIndices;
}

void spMesh_clearParts(spMesh* mesh) {
	mesh->numAllocatedIndices = 0;
	mesh->numAllocatedVertices = 0;
}

void spMesh_dispose(spMesh* mesh) {
	FREE(mesh->vertices);
	FREE(mesh->indices);
	FREE(mesh);
}

GLuint compileShader(GLenum shaderType, const char* shaderSource) {
	GLuint shader = glCreateShader(shaderType);
	glShaderSource(shader, 1, &shaderSource, 0);
	glCompileShader(shader);
	GLint status;
	glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
	if (!status) {
		GLsizei length;
		glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length);
		if (length < 1) {
			printf("Unknown error while compiling shader\n");
			exit(-1);
		} else {
			char* log = MALLOC(char, length);
			glGetShaderInfoLog(shader, length, 0, log);
			printf("Error compiling shader: %s\n", log);
			exit(-1);
		}
	}
	return shader;
}

spShader* spShader_create(const char* vertexShaderSource, const char* fragmentShaderSource) {
	GLuint vertexShader = compileShader(GL_VERTEX_SHADER, vertexShaderSource);
	GLuint fragmentShader = compileShader(GL_FRAGMENT_SHADER, fragmentShaderSource);
	
	GLuint program = glCreateProgram();
	glAttachShader(program, vertexShader);
	glAttachShader(program, fragmentShader);
	glLinkProgram(program);
	
	GLint status;
	glGetProgramiv(program, GL_LINK_STATUS, &status);
	if (!status) {
		printf("Unknown error while linking program\n");
		exit(-1);
	}
	
	spShader* shader = MALLOC(spShader, 1);
	shader->program = program;
	shader->vertexShader = vertexShader;
	shader->fragmentShader = fragmentShader;
	return shader;
}

void spShader_dispose(spShader* shader) {
	glDeleteProgram(shader->program);
	glDeleteShader(shader->vertexShader);
	glDeleteShader(shader->fragmentShader);
	FREE(shader);
}

spTwoColorBatcher* spTwoColorBatcher_create() {
	spTwoColorBatcher* batcher = MALLOC(spTwoColorBatcher, 1);
	
	batcher->shader = spShader_create(TWO_COLOR_TINT_VERTEX_SHADER, TWO_COLOR_TINT_FRAGMENT_SHADER);
	batcher->positionAttributeLocation = glGetAttribLocation(batcher->shader->program, "a_position");
	batcher->colorAttributeLocation = glGetAttribLocation(batcher->shader->program, "a_color");
	batcher->color2AttributeLocation = glGetAttribLocation(batcher->shader->program, "a_color2");
	batcher->texCoordsAttributeLocation = glGetAttribLocation(batcher->shader->program, "a_texCoords");
	batcher->textureUniformLocation = glGetUniformLocation(batcher->shader->program, "texture");
	
	glGenBuffers(1, &batcher->vertexBufferHandle);
	glGenBuffers(1, &batcher->indexBufferHandle);
	batcher->verticesBuffer = MALLOC(spVertex, MAX_VERTICES);
	batcher->indicesBuffer = MALLOC(unsigned short, MAX_INDICES);
	batcher->numIndices = 0;
	batcher->numVertices = 0;
	batcher->lastTextureHandle = -1;
	batcher->lastSrcBlend = -1;
	batcher->lastDstBlend = -1;
	return batcher;
}

void spTwoColorBatcher_add(spTwoColorBatcher* batcher, spMeshPart mesh) {
	if (batcher->numVertices + mesh.numVertices > MAX_VERTICES || batcher->numIndices + mesh.numIndices > MAX_INDICES) {
		spTwoColorBatcher_flush(batcher);
	}
	
	if (batcher->lastTextureHandle != mesh.textureHandle || batcher->lastSrcBlend != mesh.srcBlend || batcher->lastDstBlend != mesh.dstBlend) {
		spTwoColorBatcher_flush(batcher);
	}
	
	spVertex* vertices = &batcher->verticesBuffer[batcher->numVertices];
	unsigned short* indices = &batcher->indicesBuffer[batcher->numIndices];
	
	memcpy(vertices, &mesh.mesh->vertices[mesh.startVertex], mesh.numVertices * sizeof(spVertex));
	unsigned short offset = (unsigned short)batcher->numVertices;
	for (int i = 0, j = mesh.startIndex, n = mesh.numIndices; i < n; i++, j++) {
		indices[i] = mesh.mesh->indices[j] + offset;
	}
	
	batcher->numIndices += mesh.numIndices;
	batcher->numVertices += mesh.numVertices;
	batcher->lastSrcBlend = mesh.srcBlend;
	batcher->lastDstBlend = mesh.dstBlend;
	batcher->lastTextureHandle = mesh.textureHandle;
}

void spTwoColorBatcher_flush(spTwoColorBatcher* batcher) {
	if (batcher->numVertices == 0 || batcher->numIndices == 0)
		return;
	
	glUseProgram(batcher->shader->program);
		
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, batcher->lastTextureHandle);
	glUniform1i(batcher->textureUniformLocation, 0);
	
	glBlendFunc(batcher->lastSrcBlend, batcher->lastDstBlend);
	
	glBindBuffer(GL_ARRAY_BUFFER, batcher->vertexBufferHandle);
	glBufferData(GL_ARRAY_BUFFER, sizeof(spVertex) * batcher->numVertices , batcher->verticesBuffer, GL_DYNAMIC_DRAW);
	
	glEnableVertexAttribArray(batcher->positionAttributeLocation);
	glEnableVertexAttribArray(batcher->colorAttributeLocation);
	glEnableVertexAttribArray(batcher->color2AttributeLocation);
	glEnableVertexAttribArray(batcher->texCoordsAttributeLocation);
	
	glVertexAttribPointer(batcher->positionAttributeLocation, 4, GL_FLOAT, GL_FALSE, sizeof(spVertex), (GLvoid*)offsetof(spVertex, x));
	glVertexAttribPointer(batcher->colorAttributeLocation, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(spVertex), (GLvoid*)offsetof(spVertex, color));
	glVertexAttribPointer(batcher->color2AttributeLocation, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(spVertex), (GLvoid*)offsetof(spVertex, color2));
	glVertexAttribPointer(batcher->texCoordsAttributeLocation, 2, GL_FLOAT, GL_FALSE, sizeof(spVertex), (GLvoid*)offsetof(spVertex, u));
	
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batcher->indexBufferHandle);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned short) * batcher->numIndices, batcher->indicesBuffer, GL_STATIC_DRAW);
	
	glDrawElements(GL_TRIANGLES, (GLsizei)batcher->numIndices, GL_UNSIGNED_SHORT, 0);
	
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	
	glUseProgram(0);
	glBindTexture(GL_TEXTURE_2D, 0);
	
	batcher->numIndices = 0;
	batcher->numVertices = 0;
	batcher->lastSrcBlend = -1;
	batcher->lastDstBlend = -1;
	batcher->lastTextureHandle = -1;
}

void spDisposeTwoColorBatcher(spTwoColorBatcher* batcher) {
	spShader_dispose(batcher->shader);
	glDeleteBuffers(1, &batcher->vertexBufferHandle);
	FREE(batcher->verticesBuffer);
	glDeleteBuffers(1, &batcher->indexBufferHandle);
	FREE(batcher->indicesBuffer);
}
