/* 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>

int windowWidth = 500;
int windowHeight = 500;

float mouseWindowX = 0.0;
float mouseWindowY = 0.0;

GLuint program;

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

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

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) {
	if(h <= 0) // you cant make a window of zero width
		h = 1;

	float ratio = w / h;

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	
	glViewport(0, 0, w, h);
    
	windowWidth = w;
	windowHeight = h;

	gluPerspective(45, ratio, 1, 1000);
	glMatrixMode(GL_MODELVIEW);
	}

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

void processNormalKeys(unsigned char key, int x, int y) {
	if (key == 27)  // escape key to exit the program
		exit(0);
	}

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

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 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);
		}
	}

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

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

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

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

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

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

	free(vs); free(fs); free(gs);

	glCompileShader(v);
	glCompileShader(f);
	glCompileShader(g);

	//fprintf(stderr, "vertex\n");
	printShaderLog(v);

	//fprintf(stderr, "fragment\n");
	printShaderLog(f);

	//fprintf(stderr, "geometry\n");
	printShaderLog(g);

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

	// geometry shader details
	// input: GL_POINTS, GL_LINES, GL_LINES_ADJACENCY_EXT, GL_TRIANGLES, GL_TRIANGLES_ADJACENCY_EXT
	// output: GL_POINTS, GL_LINE_STRIP, GL_TRIANGLE_STRIP 
	
	glProgramParameteriEXT(pro,GL_GEOMETRY_INPUT_TYPE_EXT,GL_LINES);
	glProgramParameteriEXT(pro,GL_GEOMETRY_OUTPUT_TYPE_EXT,GL_LINE_STRIP);
	int temp;
	glGetIntegerv(GL_MAX_GEOMETRY_OUTPUT_VERTICES_EXT,&temp);
	glProgramParameteriEXT(pro,GL_GEOMETRY_VERTICES_OUT_EXT,temp);
	
	glLinkProgram(pro);
	printProgramLog(pro);

	return(pro);
	}
	
////////////////////////////////////////////////////////////////

void renderScene(void) {
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	glLoadIdentity();
	gluLookAt(	0.0f, 0.0f,  10.0f, 
				0.0f, 0.0f, -1.0f,
				0.0f, 1.0f,  0.0f);



	glLightfv(GL_LIGHT0, GL_POSITION, lpos);

	glUseProgram(program);

	glRotatef(mouseWindowX*90+90,0,1,0); // rotate the teapot
	glRotatef(mouseWindowY*90,0,0,1);

		
	glutWireTeapot(2.0);	
    
	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.0f, 0.0f ,0.0f ,1.0f);

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

	program = setShaders( (char *) "./toon.vert", (char *) "./toon.frag", (char *) "./toon.geom");
	
	glutMainLoop();
	return 0;
	}

// printed with:	
// enscript --pretty-print --line-numbers --no-header -M Wall --landscape --color --margins=1:40:1:1 -3jr -T 3 -h -f Courier30 -o foo.ps ogl.cpp
// Media:  Wall 2100   4200     24      24      2050     4150
// convert foo.ps -negate -rotate 90 foo_neg.pdf
