#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES/egl.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>

#include <cutils/log.h>
#include <ui/FramebufferNativeWindow.h>
#include <ui/EGLUtils.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "base.h"
#include "factor.h"

GLuint ui32Vbo = 0;
#define VERTEX_ARRAY 0

float pfMatrix1[] = {
	1.0f, 0.0f, 0.0f, 0.0f,
	0.0f, 1.0f, 0.0f, 0.0f,
	0.0f, 0.0f, 1.0f, 0.0f,
	0.0f, 0.0f, 0.0f, 1.0f,
};
float pfMatrix2[] = {
	1.0f, 0.0f, 0.0f, 0.0f,
	0.0f, 1.0f, 0.0f, 0.0f,
	0.0f, 0.0f, 1.0f, 0.0f,
	0.0f, 0.0f, 0.0f, 1.0f,
};
float pfMatrix3[] = {
	1.0f, 0.0f, 0.0f, 0.0f,
	0.0f, 1.0f, 0.0f, 0.0f,
	0.0f, 0.0f, 1.0f, 0.0f,
	0.0f, 0.0f, 0.0f, 1.0f,
};
float pfMatrix4[] = {
	1.0f, 0.0f, 0.0f, 0.0f,
	0.0f, 1.0f, 0.0f, 0.0f,
	0.0f, 0.0f, 1.0f, 0.0f,
	0.0f, 0.0f, 0.0f, 1.0f,
};

// A vertex shader
const char* pszVertShader = "\
	attribute highp vec4 myVertex; \
	uniform mediump mat4 myPMVMatrix1; \
	uniform mediump mat4 myPMVMatrix2; \
	uniform mediump mat4 myPMVMatrix3; \
	uniform mediump mat4 myPMVMatrix4; \
	void main(void)\
	{\
		gl_Position = myPMVMatrix1 * myPMVMatrix2 * myVertex; \
	}";
// A fragment shader
const char* pszFragShader = "\
	void main (void)\
	{\
		gl_FragColor = vec4(1.0, 1.0, 0.66, 1.0); \
	}";

//static float rand_max = (float)RAND_MAX;

float offload_to_gpu(int N, struct FactorSet *fs, float *A, float *B, float *T)
{
	EGLConfig eglConfigPbuffer = 0;
	EGLSurface eglSurfacePbuffer = 0;
	GLuint pBufferTexture = 0;
	EGLContext eglContextPbuffer = 0;
	EGLDisplay eglDisplay;
	GLfloat buff[16];
	struct timespec start[2], stop[2];
	int n = N/4;
	int i32Location = 0;
	int i, j, k;

	clock_gettime(CLOCK_MONOTONIC, &start[0]);

	EGLint attribListPbuffer[] = { 
		EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
		EGL_RED_SIZE, 4,
		EGL_GREEN_SIZE, 4,
		EGL_BLUE_SIZE, 4,
		EGL_DEPTH_SIZE, 16, 
		EGL_NONE
	};  

	EGLint pBufferAttribs[] = { 
		EGL_WIDTH, 256,
		EGL_HEIGHT, 256,
		EGL_TEXTURE_TARGET, EGL_TEXTURE_2D,
		EGL_TEXTURE_FORMAT, EGL_TEXTURE_RGBA,
		EGL_NONE
	};  

	eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
	if (eglDisplay == EGL_NO_DISPLAY) {
		printf("eglGetDisplay returned EGL_NO_DISPLAY\n");
		exit(1);
	}
	EGLint iMajorVersion, iMinorVersion;
	if (!eglInitialize(eglDisplay, &iMajorVersion, &iMinorVersion)) {
		printf("Error %d\n", __LINE__);
		exit(1);
	}
	EGLint iConfigs = 0;
	if (!eglChooseConfig(eglDisplay, attribListPbuffer, &eglConfigPbuffer, 1, &iConfigs)) {
		printf("Error %d\n", __LINE__);
		exit(1);
	}
	printf("\nconfig: %d\n", iConfigs);

	eglSurfacePbuffer = eglCreatePbufferSurface(eglDisplay, eglConfigPbuffer, pBufferAttribs);
	if (!eglSurfacePbuffer) {
		printf("Error %d\n", __LINE__);
		exit(1);
	}

	EGLint pi32ContextAttribs[3];
	pi32ContextAttribs[0] = EGL_CONTEXT_CLIENT_VERSION;
	pi32ContextAttribs[1] = 2;
	pi32ContextAttribs[2] = EGL_NONE;
	eglContextPbuffer = eglCreateContext(eglDisplay,
			eglConfigPbuffer, EGL_NO_CONTEXT, pi32ContextAttribs);
	if (eglContextPbuffer == EGL_NO_CONTEXT) {
		printf("eglCreateContext failed.\n");
		return 0;
	}

	EGLBoolean return_value;
	return_value = eglMakeCurrent(eglDisplay, eglSurfacePbuffer, eglSurfacePbuffer, eglContextPbuffer);
	if (return_value != EGL_TRUE) {
		printf("eglMakeCurrent failed.\n");
		return 0;
	}

	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT);
	/* Setting up successful, start drawing */
	{
#if 0
		eglSwapBuffers(eglDisplay, eglSurfacePbuffer);
#endif
		/* Using shader code */
		GLuint uiVertShader;
		GLuint uiFragShader;
		GLuint uiProgramObject;

		/* Create shader objects */
		uiVertShader = glCreateShader(GL_VERTEX_SHADER);
		uiFragShader = glCreateShader(GL_FRAGMENT_SHADER);
		/* Load shader code into shader objects*/
		glShaderSource(uiVertShader, 1, (const char**)&pszVertShader, NULL);
		glShaderSource(uiFragShader, 1, (const char**)&pszFragShader, NULL);
		/* Compile the shaders */
		glCompileShader(uiVertShader);
		glCompileShader(uiFragShader);
		/* Create shader program, attach shader object to it */
		uiProgramObject = glCreateProgram();
		glAttachShader(uiProgramObject, uiVertShader);
		glAttachShader(uiProgramObject, uiFragShader);

		glLinkProgram(uiProgramObject);
		glUseProgram(uiProgramObject);

		clock_gettime(CLOCK_MONOTONIC, &start[1]);
		for (i=0; i<n; i++) {
			glClear(GL_COLOR_BUFFER_BIT);
			for (j=0; j<n; j++) {
				for (k=0; k<n; k++) {

					int ma, mb, mt;
					ma = 4*4*n*i+4*k;
					mb = 4*4*n*k+4*j;
					mt = 4*4*n*i+4*j;
					for (int m=0; m<16; m++) {
						pfMatrix1[m] = A[ma+(m/4)*N+m%4];
						pfMatrix2[m] = B[mb+(m/4)*N+m%4];
					}
					/* Set up matrix */
					i32Location = glGetUniformLocation(uiProgramObject, "myPMVMatrix1");
					glUniformMatrix4fv(i32Location, 1, GL_FALSE, pfMatrix1);
					i32Location = glGetUniformLocation(uiProgramObject, "myPMVMatrix2");
					glUniformMatrix4fv(i32Location, 1, GL_FALSE, pfMatrix2);
					
					glEnableVertexAttribArray(VERTEX_ARRAY);
					glVertexAttribPointer(VERTEX_ARRAY, 3, GL_FLOAT, GL_FALSE, 0, 0);
					glDrawArrays(GL_TRIANGLES, 0, 3);
					glDisableVertexAttribArray(VERTEX_ARRAY);

					/* Read back the result */
					glGetUniformfv(uiProgramObject, i32Location, buff);
					for (int m=0; m<16; m++)
						T[mt+(m/4)*N+m%4] += buff[m];
				}
			}
		}
		clock_gettime(CLOCK_MONOTONIC, &stop[1]);

		/* Clean up */
		glDeleteProgram(uiProgramObject);
		glDeleteShader(uiFragShader);
		glDeleteShader(uiVertShader);
	}
	eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
	eglTerminate(eglDisplay);
	clock_gettime(CLOCK_MONOTONIC, &stop[0]);

	for (int m=0; m<16; m++)
		printf("%f ", buff[0]);
	printf("\n");

	float diff_time;
	diff_time = calc_diff_time(&start[1], &stop[1])/1000;
	printf("exec/total: %f ", diff_time);
	diff_time = calc_diff_time(&start[0], &stop[0])/1000;
	printf(" %f us\n", diff_time);

//	fs->U_cop = (fs->N_comp*fs->U_cpu)/30.518;

	return diff_time;
}
