/* Simple Demo for GLSL www.lighthouse3d.com - tweaked by andy  */

#include "GL/glew.h"

#ifdef __linux__
#include <GL/glut.h>
#endif

#ifdef __APPLE__
#include <glut.h>
#endif

#ifdef _WIN32
#include "glut.h"
#endif

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

char normalData[800000];
GLuint normaltex;
GLuint texNumNormal;

float mouseWindowX = 0.0;
float mouseWindowY = 0.0;

int windowWidth = 320;
int windowHeight = 320;

float bSize    = 0.15;
float bDensity = 16.0;

GLuint p;

float lpos[4] = {1.0, 0.5, 1.0, 0.0};

////////////////////////////////////////////////////////////////

char *textFileRead(char *fn)
{
	FILE *fp;
	char *content = NULL;

	int count=0;

	if (fn != NULL) {
        fp = fopen(fn,"rt");

        if (fp != NULL) {
      
            fseek(fp, 0, SEEK_END);
            count = ftell(fp);
            rewind(fp);

			if (count > 0) {
				content = (char *)malloc(sizeof(char) * (count+1));
				count = fread(content,sizeof(char),count,fp);
				content[count] = '\0';
			}
			fclose(fp);
		}
	}
	
	if (content == NULL)
        {
        fprintf(stderr, "ERROR: could not load in file %s\n", fn);
        exit(1);
        }
    return content;
}           

////////////////////////////////////////////////////////////////

void changeSize(int w, int h) {

    // Prevent a divide by zero, when window is too short
    // (you cant make a window of zero width).
    if(h == 0)
        h = 1;

    float ratio = 1.0* w / h;

    // Reset the coordinate system before modifying
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
	
    // Set the viewport to be the entire window
    glViewport(0, 0, w, h);

    windowWidth = w;
    windowHeight = h;
    
	// Set the correct perspective.
	gluPerspective(45, ratio, 1, 1000);
	glMatrixMode(GL_MODELVIEW);
}

////////////////////////////////////////////////////////////////

void printShaderLog(GLuint obj) {
	GLint infoLogLength = 0;
	GLsizei charsWritten  = 0;
	GLchar *infoLog;

	glGetShaderiv(obj, GL_INFO_LOG_LENGTH, &infoLogLength);

	if (infoLogLength > 0){
		infoLog = (char *) malloc(infoLogLength);
		glGetShaderInfoLog(obj, infoLogLength, &charsWritten, infoLog);
		printf("%s\n",infoLog);
		free(infoLog);
		}
	}

void printProgramLog(GLuint obj) {
	GLint infoLogLength = 0;
	GLsizei charsWritten  = 0;
	GLchar *infoLog;

	glGetProgramiv(obj, GL_INFO_LOG_LENGTH, &infoLogLength);

	if (infoLogLength > 0){
		infoLog = (char *) malloc(infoLogLength);
		glGetProgramInfoLog(obj, infoLogLength, &charsWritten, infoLog);
		printf("%s\n",infoLog);
		free(infoLog);
		}
	}

////////////////////////////////////////////////////////////////

void PassiveMouseMotion(int x, int y)
{
    if (x<0)
        mouseWindowX = -1;
    else if (x>windowWidth)
        mouseWindowX = 1;
    else
        mouseWindowX = 2* (x / (float) windowWidth) - 1.0;
   
    if (y<0)
        mouseWindowY = -1;
    else if (y>windowHeight)
        mouseWindowY = 1;
    else
        mouseWindowY = 2* (y / (float) windowHeight) - 1.0;
    }

////////////////////////////////////////////////////////////////

void processNormalKeys(unsigned char key, int x, int y) {
    if (key == 27)     
	   exit(0);
		
    // size	
    if (key == '1')
        bSize += 0.02;
    if (key == '2')
        bSize -= 0.02;
        
    // density    
    if (key == '3')
        bDensity += 1.0;
    if (key == '4')
        bDensity -= 1.0;
}

////////////////////////////////////////////////////////////////

GLuint setShaders(char * vert, char * frag) {
    GLuint v,f, pro;
	char *vs,*fs;

	v = glCreateShader(GL_VERTEX_SHADER);
	f = glCreateShader(GL_FRAGMENT_SHADER);

	vs = textFileRead(vert);
	fs = textFileRead(frag);

	const char * vv = vs;
	const char * ff = fs;

	glShaderSource(v, 1, &vv,NULL);
	glShaderSource(f, 1, &ff,NULL);

	free(vs);free(fs);

	glCompileShader(v);
	glCompileShader(f);

	printShaderLog(v);
	printShaderLog(f);

	pro = glCreateProgram();
	glAttachShader(pro,v);
	glAttachShader(pro,f);

	glLinkProgram(pro);
	printProgramLog(pro);
	
	return(pro);
}

////////////////////////////////////////////////////////////////

void renderScene(void) {

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	glLoadIdentity();
	gluLookAt(  0.0f, 3.0f, 6.0f, 
                0.0f, 0.0f,-1.0f,
                0.0f, 1.0f, 0.0f);

    glLightfv(GL_LIGHT0, GL_POSITION, lpos);

    glRotatef(mouseWindowX*140+180,0,1,0);
    glRotatef(mouseWindowY*90+22,1,0,0);

    glUseProgram(p);

    GLint texLoc, lightLoc, bumpSizeLoc, bumpDensityLoc;
    texLoc = glGetAttribLocation(p, "Tangent");
    glVertexAttrib3f(texLoc, 1.0, 0.0, 0.0);

    lightLoc = glGetUniformLocation(p, "LightPosition");
    glUniform3f(lightLoc, 1.0, 1.0, 4.0);

    bumpSizeLoc = glGetUniformLocation(p, "BumpSize");
    glUniform1f(bumpSizeLoc, bSize);

    bumpDensityLoc = glGetUniformLocation(p, "BumpDensity");
    glUniform1f(bumpDensityLoc, bDensity);
    
    glNormal3f(0.0f, 0.0f, -1.0f );

    glBegin( GL_POLYGON );
        glTexCoord2f( 0.0f, 1.0f );  
        glVertex3f( -2.0f, -2.0f, 0.0f );
        
        glTexCoord2f( 1.0f, 1.0f );
        glVertex3f(  2.0f, -2.0f, 0.0f );
        
        glTexCoord2f( 1.0f, 0.0f ); 
        glVertex3f(  2.0f,  2.0f, 0.0f );
        
        glTexCoord2f( 0.0f, 0.0f );        
        glVertex3f( -2.0f,  2.0f, 0.0f );
    glEnd();	

    glutSwapBuffers();
}

////////////////////////////////////////////////////////////////

int main(int argc, char **argv) {
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
	glutInitWindowPosition(100,100);
	glutInitWindowSize(windowWidth, windowHeight);
	glutCreateWindow("GLSL");

	glutDisplayFunc(renderScene);
	glutIdleFunc(renderScene);
	glutReshapeFunc(changeSize);
	glutKeyboardFunc(processNormalKeys);
	glutPassiveMotionFunc(PassiveMouseMotion);

	glEnable(GL_DEPTH_TEST);
	glClearColor(0.0,0.0,0.0,1.0);


	glewInit();
	if (GLEW_ARB_vertex_shader && GLEW_ARB_fragment_shader)
        printf("Ready for GLSL\n");
	else {
        printf("No GLSL support\n");
        exit(1);
    }

    p = setShaders("./bump2.vert", "./bump2.frag");
	
	glutMainLoop();
	return 0;
}

