/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// OpenGL ES 2.0 code

#include <jni.h>

#include <android/log.h>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>

#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <stdint.h>

#include "glcamera.h"
#include "image_pool.h"
using namespace cv;
#define  LOG_TAG    "libandroid-opencv"
#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)

static void printGLString(const char *name, GLenum s) {
	const char *v = (const char *) glGetString(s);
	LOGI("GL %s = %s\n", name, v);
}

static void checkGlError(const char* op) {
	for (GLint error = glGetError(); error; error = glGetError()) {
		LOGI("after %s() glError (0x%x)\n", op, error);
	}
}

static const char gVertexShader[] = "attribute vec4 a_position;   \n"
	"attribute vec2 a_texCoord;   \n"
	"varying vec2 v_texCoord;     \n"
	"void main()                  \n"
	"{                            \n"
	"   gl_Position = a_position; \n"
	"   v_texCoord = a_texCoord;  \n"
	"}                            \n";

static const char gFragmentShader[] =
		"precision mediump float;                            \n"
			"varying vec2 v_texCoord;                            \n"
			"uniform sampler2D s_texture;                        \n"
			"void main()                                         \n"
			"{                                                   \n"
			"  gl_FragColor = texture2D( s_texture, v_texCoord );\n"
			"}                                                   \n";

const GLfloat gTriangleVertices[] = { 0.0f, 0.5f, -0.5f, -0.5f, 0.5f, -0.5f };
GLubyte testpixels[4 * 3] = { 255, 0, 0, // Red
		0, 255, 0, // Green
		0, 0, 255, // Blue
		255, 255, 0 // Yellow
		};

GLuint glcamera::createSimpleTexture2D(GLuint _textureid, GLubyte* pixels,
		int width, int height, int channels) {

	// Bind the texture
	glActiveTexture(GL_TEXTURE0);
	checkGlError("glActiveTexture");
	// Bind the texture object
	glBindTexture(GL_TEXTURE_2D, _textureid);
	checkGlError("glBindTexture");

	GLenum format;
	switch (channels) {
	case 3:
		format = GL_RGB;
		break;
	case 1:
		format = GL_LUMINANCE;
		break;
	case 4:
		format = GL_RGBA;
		break;
	}
	// Load the texture
	glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format,
			GL_UNSIGNED_BYTE, pixels);

	checkGlError("glTexImage2D");
	// Set the filtering mode
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );

	return _textureid;

}

GLuint glcamera::loadShader(GLenum shaderType, const char* pSource) {
	GLuint shader = glCreateShader(shaderType);
	if (shader) {
		glShaderSource(shader, 1, &pSource, NULL);
		glCompileShader(shader);
		GLint compiled = 0;
		glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
		if (!compiled) {
			GLint infoLen = 0;
			glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
			if (infoLen) {
				char* buf = (char*) malloc(infoLen);
				if (buf) {
					glGetShaderInfoLog(shader, infoLen, NULL, buf);
					LOGE("Could not compile shader %d:\n%s\n",
							shaderType, buf);
					free(buf);
				}
				glDeleteShader(shader);
				shader = 0;
			}
		}
	}
	return shader;
}

GLuint glcamera::createProgram(const char* pVertexSource,
		const char* pFragmentSource) {
	GLuint vertexShader = loadShader(GL_VERTEX_SHADER, pVertexSource);
	if (!vertexShader) {
		return 0;
	}

	GLuint pixelShader = loadShader(GL_FRAGMENT_SHADER, pFragmentSource);
	if (!pixelShader) {
		return 0;
	}

	GLuint program = glCreateProgram();
	if (program) {
		glAttachShader(program, vertexShader);
		checkGlError("glAttachShader");
		glAttachShader(program, pixelShader);
		checkGlError("glAttachShader");
		glLinkProgram(program);
		GLint linkStatus = GL_FALSE;
		glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
		if (linkStatus != GL_TRUE) {
			GLint bufLength = 0;
			glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength);
			if (bufLength) {
				char* buf = (char*) malloc(bufLength);
				if (buf) {
					glGetProgramInfoLog(program, bufLength, NULL, buf);
					LOGE("Could not link program:\n%s\n", buf);
					free(buf);
				}
			}
			glDeleteProgram(program);
			program = 0;
		}
	}
	return program;
}

//GLuint textureID;

bool glcamera::setupGraphics(int w, int h) {
	printGLString("Version", GL_VERSION);
	printGLString("Vendor", GL_VENDOR);
	printGLString("Renderer", GL_RENDERER);
	printGLString("Extensions", GL_EXTENSIONS);

	LOGI("setupGraphics(%d, %d)", w, h);
	gProgram = createProgram(gVertexShader, gFragmentShader);
	if (!gProgram) {
		LOGE("Could not create program.");
		return false;
	}
	gvPositionHandle = glGetAttribLocation(gProgram, "a_position");
	gvTexCoordHandle = glGetAttribLocation(gProgram, "a_texCoord");

	gvSamplerHandle = glGetAttribLocation(gProgram, "s_texture");

	// Use tightly packed data
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

	// Generate a texture object
	glGenTextures(1, &textureID);
	textureID = createSimpleTexture2D(textureID, testpixels, 2, 2, 3);

	checkGlError("glGetAttribLocation");
	LOGI("glGetAttribLocation(\"vPosition\") = %d\n",
			gvPositionHandle);

	glViewport(0, 0, w, h);
	checkGlError("glViewport");
	return true;
}

void glcamera::renderFrame() {

	GLfloat vVertices[] = { -1.0f, 1.0f, 0.0f, // Position 0
			0.0f, 0.0f, // TexCoord 0
			-1.0f, -1.0f, 0.0f, // Position 1
			0.0f, 1.0f, // TexCoord 1
			1.0f, -1.0f, 0.0f, // Position 2
			1.0f, 1.0f, // TexCoord 2
			1.0f, 1.0f, 0.0f, // Position 3
			1.0f, 0.0f // TexCoord 3
			};
	GLushort indices[] = { 0, 1, 2, 0, 2, 3 };
	GLsizei stride = 5 * sizeof(GLfloat); // 3 for position, 2 for texture

	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
	checkGlError("glClearColor");

	glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
	checkGlError("glClear");

	glUseProgram(gProgram);
	checkGlError("glUseProgram");

	// Load the vertex position
	glVertexAttribPointer(gvPositionHandle, 3, GL_FLOAT, GL_FALSE, stride,
			vVertices);
	// Load the texture coordinate
	glVertexAttribPointer(gvTexCoordHandle, 2, GL_FLOAT, GL_FALSE, stride,
			&vVertices[3]);

	glEnableVertexAttribArray(gvPositionHandle);
	glEnableVertexAttribArray(gvTexCoordHandle);

	// Bind the texture
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, textureID);

	// Set the sampler texture unit to 0
	glUniform1i(gvSamplerHandle, 0);

	glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices);

	//checkGlError("glVertexAttribPointer");
	//glEnableVertexAttribArray(gvPositionHandle);
	//checkGlError("glEnableVertexAttribArray");
	//glDrawArrays(GL_TRIANGLES, 0, 3);
	//checkGlError("glDrawArrays");
}

void glcamera::init(int width, int height) {
	newimage = false;
	nimg = Mat();
	setupGraphics(width, height);

}

void glcamera::step() {
	if (newimage && !nimg.empty()) {

		textureID = createSimpleTexture2D(textureID,
				nimg.ptr<unsigned char> (0), nimg.rows, nimg.cols,
				nimg.channels());
		newimage = false;
	}
	renderFrame();

}

void glcamera::setTextureImage(Ptr<Mat> img) {

	//int p2 = (int)(std::log(img->size().width)/0.69315);
	int sz = 256;//std::pow(2,p2);
	Size size(sz, sz);

	resize(*img, nimg, size,cv::INTER_NEAREST);

	newimage = true;

}

void glcamera::drawMatToGL(int idx, image_pool* pool) {

	Ptr<Mat> img = pool->getImage(idx);

	if (img.empty())
		return; //no image at input_idx!

	setTextureImage(img);

}

glcamera::glcamera():newimage(false) {
	LOGI("glcamera constructor");
}
glcamera::~glcamera() {
	LOGI("glcamera destructor");
}