#include <stdio.h>
#include <stdlib.h>
//#include <GL/glew.h>
#include <GLUT/glut.h>
#include <sys/times.h>

// minimal flyttalsberäkning med shader
// Baserat på tutorial av  [hittar inte min ref just nu, skriver in det senare]

void CHECK_FRAMEBUFFER_STATUS()
{
	GLenum status;
	status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
	printf("Framebuffer status %d\n", status);
	switch(status)
	{
		case GL_FRAMEBUFFER_COMPLETE_EXT:
			printf("Framebuffer complete\n");
			break;
		case GL_FRAMEBUFFER_UNSUPPORTED_EXT:
			printf("Framebuffer unsuported\n");
			break;
		case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
			printf("GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT\n");
			break;
		case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
			printf("GL_FRAMEBUFFER_MISSING_ATTACHMENT_EXT\n");
			break;
		case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
			printf("GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT\n");
			break;
		case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
			printf("GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT\n");
			break;
		case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
			printf("GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT\n");
			break;
		case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
			printf("GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT\n");
			break;
		default:
			printf("Framebuffer error\n");
	}
}

// Givet pekare till källkoden (array av strängar), returnerar shaderprogramobjektet
// Hanterar inte argument till shaders, måste göras separat
GLhandleARB setupShader(const GLcharARB **vertSrc, const GLcharARB **fragSrc)
{
	GLhandleARB	programObject;	// the program used to update
	GLhandleARB	fragmentShader, vertexShader;
	
	programObject = glCreateProgramObjectARB();

	if (fragSrc != NULL)
	{	
		fragmentShader = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);
		glShaderSourceARB(fragmentShader, 1, fragSrc, NULL);
		glCompileShaderARB(fragmentShader);
		glAttachObjectARB(programObject, fragmentShader);
	}

	if (vertSrc != NULL)
	{	
		vertexShader = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB);
		glShaderSourceARB(vertexShader, 1, vertSrc, NULL);
		glCompileShaderARB(vertexShader);
		glAttachObjectARB(programObject, vertexShader);
	}

// Link the shader into a complete GLSL program.
	glLinkProgramARB(programObject);
	GLint progLinkSuccess;
	glGetObjectParameterivARB(programObject, GL_OBJECT_LINK_STATUS_ARB,
		&progLinkSuccess);
	if (!progLinkSuccess)
	{
		fprintf(stderr, "Shader could not be linked\n");
		exit(1);
		// Borde skicka ut hela infolog-felmeddelandet.
	}
	return programObject;
}

long GetTickCount()
{
	struct tms tm;
	return times(&tm); // 100 tick per sekund
}

// En enkel shader - drar bara roten ur
static const char *fragSource =
{
"uniform sampler2D texUnit;"
"void main(void)"
"{"
"   vec4 texVal  = texture2D(texUnit, gl_TexCoord[0].xy);"
"   gl_FragColor = sqrt(texVal);"
"}"
};

int main(int argc, char **argv)
{
    // declare texture size, the actual data will be a vector 
    // of size texSize*texSize*4
	// Obs att texSize måste vara 2-potens på många (äldre) grafikkort!
    int texSize = 256;
#define loopCount 1000
	int i;
    // create test data
    float* data = (float*)malloc(4*texSize*texSize*sizeof(float));
    float* result = (float*)malloc(4*texSize*texSize*sizeof(float));
    for (i=0; i<texSize*texSize*4; i++)
        data[i] = i+1.0;
    // set up glut to get valid GL context and 
    // get extension entry points
    glutInit (&argc, argv);
    glutCreateWindow("TEST1");
//    glewInit();
    // viewport transform for 1:1 pixel=texel=data mapping
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0.0,texSize,0.0,texSize);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glViewport(0,0,texSize,texSize);
    // create FBO and bind it (that is, use offscreen render target)
    GLuint fb;
    glGenFramebuffersEXT(1,&fb); 
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,fb);
    // create texture
    GLuint tex;
    glGenTextures (1, &tex);
    glBindTexture(GL_TEXTURE_2D,tex);
    // set texture parameters
    glTexParameteri(GL_TEXTURE_2D, 
                    GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, 
                    GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    // define texture with floating point format
    glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA32F_ARB,
                 texSize,texSize,0,GL_RGBA,GL_FLOAT, data); // Var tom textur i originalet

    // create destination texture
    GLuint desttex;
    glGenTextures (1, &desttex);
    glBindTexture(GL_TEXTURE_2D,desttex);
    // set texture parameters
    glTexParameteri(GL_TEXTURE_2D, 
                    GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, 
                    GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    // define texture with floating point format
    glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA32F_ARB,
                 texSize,texSize,0,GL_RGBA,GL_FLOAT, NULL);

CHECK_FRAMEBUFFER_STATUS();

    // attach texture
    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, 
                              GL_COLOR_ATTACHMENT0_EXT, 
                              GL_TEXTURE_2D,desttex,0);

CHECK_FRAMEBUFFER_STATUS();

	GLhandleARB shader;
// Skapa shader
	shader = setupShader(NULL, &fragSource);
// Hantera shadervariabler
	glUseProgramObjectARB(shader);
	GLint loc = glGetUniformLocation(shader, "texUnit");
	glUniform1f(loc, 0); // texture unit 0
	glUseProgramObjectARB(0);

	int startTime = GetTickCount();

// draw

	int loop;
	for (loop = 0; loop < loopCount; loop++)
	{
		glUseProgramObjectARB(shader);
		glEnable(GL_TEXTURE_2D);
	    glBindTexture(GL_TEXTURE_2D,tex);
		glBegin(GL_QUADS);
		glTexCoord2f(0, 0);
		glVertex2f(0, 0);
		glTexCoord2f(1, 0);
		glVertex2f(texSize, 0);
		glTexCoord2f(1, 1);
		glVertex2f(texSize, texSize);
		glTexCoord2f(0, 1);
		glVertex2f(0, texSize);
		glEnd();
		glFlush();
	}

	int endTime = GetTickCount();


    // and read back
    glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
    glReadPixels(0, 0, texSize, texSize,GL_RGBA,GL_FLOAT,result);
    // print out results
	if (texSize < 32)
	{
	    printf("Data före beräkning:\n");
	    for (i=0; i<texSize*texSize*4; i++)
	        printf("%f\n",data[i]);
	    printf("Data efter beräkning:\n");
	    for (i=0; i<texSize*texSize*4; i++)
	        printf("%f\n",result[i]);
	}
	else
	{
	    printf("Data före beräkning:\n");
	    printf("%f\n",data[texSize*texSize*4-1]);
	    printf("Data efter beräkning:\n");
	    printf("%f\n",result[texSize*texSize*4-1]);
	}

	printf("Total ticks (GPU): %d\n", endTime - startTime);

// Samma sak på CPU! Bör ta längre tid om allt är väl.
	startTime = GetTickCount();
	for (loop = 0; loop < loopCount; loop++)
 	   for (i=0; i<texSize*texSize*4; i++)
 	       result[i] = sqrt(data[i]);
	endTime = GetTickCount();

// clean up
    free(data);
    free(result);

	printf("Total ticks (CPU): %d\n", endTime - startTime);

    glDeleteFramebuffersEXT (1,&fb);
    glDeleteTextures (1,&tex);
    return 0;
}

// gcc process-array.c -o process-array -lGLEW -lglut -lGL
