/*************************************************************

GLSL Demo
(c) 2005 Antoine Bouthors, EVASION

Needs
 - DevIL : http://openil.sourceforge.net/
 - GLEW : http://glew.sourceforge.net/
 - GLUT : http://www.opengl.org/resources/libraries/glut.html

*************************************************************/

//Devil for loading images
#include <IL/il.h>
#include <IL/ilu.h>

#ifdef WIN32
#include <windows.h>
#endif

//GLEW for OpenGL extentions
#include <GL/glew.h>

//OpenGL
#include <GL/gl.h>
#include <GL/glut.h>
#include <IL/ilut.h>

//Standard suff
#include <iostream>
#include <string>
#include <assert.h>
#include <math.h>

using namespace std;


/* Events (mouse + modifiers) */
#define MOUSE_SHIFT  0x0001
#define MOUSE_CTRL   0x0002
#define MOUSE_ALT    0x0004
#define MOUSE_LEFT   0x0010
#define MOUSE_MIDDLE 0x0020
#define MOUSE_RIGHT  0x0040

struct CMouse
{
	double	X ,Y,             /* Normalized mouse position     */
			X0,Y0,			 /* Previous position            */
			Dx,Dy;			 /* Displacement                      */
	int	Left, Middle, Right;  /* Mouse buttions state  */
	int	Shift, Ctrl, Alt;     /* Modifiers state */
	int	State;              /* Global state (see following flags) */
};
// Flags for mouse state
#define SHIFT  0x0001
#define CTRL   0x0002
#define ALT    0x0004
#define LEFT   0x0010
#define MIDDLE 0x0020
#define RIGHT  0x0040


struct CView
{
	double	Distance;
	double	AngleY;
	double	AngleX;

	bool	Wireframe;
};


CView		MyView;
CView		Light;
CMouse		MyMouse;
int WindowW = 512, WindowH = 512;


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

//Shader handle
GLhandleARB MyProgram;

//Texture handles
GLuint TexCubeId;
GLuint Tex2DId;

//Texture file names
char* TextureCubeName = "textures/uffizi";
char* Texture2DName = "textures/crate.jpg";

//Shader file names
char* Shader = "toon";

void InitShader();
void ReloadShader();
float Time=0;
int DrawWhat = 0;
char* ProgramName;

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

void Display() 
{
	//Initialize at first frame
	static int init=0;
	if( !init )
	{
		InitShader();
		init = 1;
	}

	//Standard stuff
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

	if( MyView.Wireframe ) glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
	else glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );

	glMatrixMode( GL_PROJECTION );
	glLoadIdentity();
	gluPerspective( 45.0, double( WindowW )/double( WindowH ), .1, 10000 );
	glMatrixMode( GL_MODELVIEW );

	glMatrixMode( GL_MODELVIEW );
	glLoadIdentity();
	glTranslatef( 0, 0, -MyView.Distance );
	glRotatef( MyView.AngleX, 1, 0, 0 );
	glRotatef( MyView.AngleY, 0, 1, 0 );

	//Don't use the shader
	glUseProgramObjectARB( 0 );

	//Set the light
	glPushMatrix();
	float LightColor[4] = { .7, .3, .3, 1 };
	float LightPos[4] = { 0, 0, 1, 1 };
	glRotatef( Light.AngleY, 0, 1, 0 );
	glRotatef( Light.AngleX, 1, 0, 0 );
	glTranslatef( 0, 0, -Light.Distance );

	//Display the light
	glDisable( GL_LIGHTING );
	glActiveTexture( GL_TEXTURE0 );
	glDisable( GL_TEXTURE_CUBE_MAP_ARB );
	glActiveTexture( GL_TEXTURE1 );
	glDisable( GL_TEXTURE_2D );
	glPointSize( 10 );
	glBegin( GL_POINTS );
	glColor3f( 1,1,1 );
	glVertex4fv( LightPos );
	glEnd();

	//Set the light parameters
	glLightfv( GL_LIGHT0, GL_DIFFUSE, LightColor );
	glLightfv( GL_LIGHT0, GL_POSITION, LightPos );
	glPopMatrix();


	//Set the cubemap texture
	glActiveTexture( GL_TEXTURE0 );
	glEnable( GL_TEXTURE_CUBE_MAP_ARB );
	glBindTexture( GL_TEXTURE_CUBE_MAP_ARB, TexCubeId );

	//Set the cubemap texture matrix to follow the light position
	glMatrixMode( GL_TEXTURE );
	glLoadIdentity();
	glRotatef( MyView.AngleY+Light.AngleY, 0, 1, 0 );
	glRotatef( MyView.AngleX+Light.AngleX, 1, 0, 0 );
	glBindTexture( GL_TEXTURE_CUBE_MAP_ARB, TexCubeId );

	//Set the 2D texture
	glActiveTexture( GL_TEXTURE1 );
	glEnable( GL_TEXTURE_2D );
	glBindTexture( GL_TEXTURE_2D, Tex2DId );
	glMatrixMode( GL_MODELVIEW );
	
	//Activates the shader
	glUseProgramObjectARB( MyProgram );
	GLint TimeUniform = glGetUniformLocationARB( MyProgram, "Time" );
	glUniform1fARB( TimeUniform, Time );
	GLint TexCubeUniform = glGetUniformLocationARB( MyProgram, "TexCube" );
	glUniform1iARB( TexCubeUniform, 0 );
	GLint Tex2DUniform = glGetUniformLocationARB( MyProgram, "Tex2D" );
	glUniform1iARB( Tex2DUniform, 1 );

	glEnable( GL_LIGHTING );

	//Draw objects
	switch( DrawWhat )
	{
		case 0: //Teapot
			glFrontFace(GL_CW);
			glutSolidTeapot(7);
			glFrontFace(GL_CCW);
		break;

		case 1: //Cube
			glBegin(GL_QUADS);  	
			glNormal3f( 0, 0, -1 );
			glTexCoord2i(0,1);glVertex3i(+5,-5,-5);
			glTexCoord2i(1,1);glVertex3i(-5,-5,-5);
			glTexCoord2i(1,0);glVertex3i(-5,+5,-5);
			glTexCoord2i(0,0);glVertex3i(+5,+5,-5);
			glNormal3f( 0, 0, +1 );
			glTexCoord2i(0,0);glVertex3i(-5,-5,+5);
			glTexCoord2i(1,0);glVertex3i(+5,-5,+5);
			glTexCoord2i(1,1);glVertex3i(+5,+5,+5);
			glTexCoord2i(0,1);glVertex3i(-5,+5,+5);
			glNormal3f( 1, 0, 0 );
			glTexCoord2i(0,1);glVertex3i(+5,+5,-5);
			glTexCoord2i(1,1);glVertex3i(+5,+5,+5);
			glTexCoord2i(1,0);glVertex3i(+5,-5,+5);
			glTexCoord2i(0,0);glVertex3i(+5,-5,-5);
			glNormal3f( -1, 0, 0 );
			glTexCoord2i(0,0);glVertex3i(-5,-5,-5);
			glTexCoord2i(1,0);glVertex3i(-5,-5,+5);
			glTexCoord2i(1,1);glVertex3i(-5,+5,+5);
			glTexCoord2i(0,1);glVertex3i(-5,+5,-5);
			glNormal3f( 0, 1, 0 );
			glTexCoord2i(0,0);glVertex3i(-5,+5,+5);
			glTexCoord2i(0,1);glVertex3i(+5,+5,+5);
			glTexCoord2i(1,1);glVertex3i(+5,+5,-5);
			glTexCoord2i(1,0);glVertex3i(-5,+5,-5);
			glNormal3f( 0, -1, 0 );
			glTexCoord2i(0,0);glVertex3i(-5,-5,-5);
			glTexCoord2i(0,1);glVertex3i(+5,-5,-5);
			glTexCoord2i(1,1);glVertex3i(+5,-5,+5);
			glTexCoord2i(1,0);glVertex3i(-5,-5,+5);
			glEnd();
		break;
		case 2: //Sphere
			glutSolidSphere( 4, 20, 20 );
		break;
	}

    glFlush();
    glutSwapBuffers();
}

void Idle()
{
	Time += .01;
	glutPostRedisplay();
}


void InitShader()
{
	glewInit();
	ilutInit();
	ilInit();
	ilutRenderer(ILUT_OPENGL); 

	ILuint ImgId;

	ilGenImages( 1, &ImgId );
	ilBindImage( ImgId );

	// Load 2D texture
	glEnable( GL_TEXTURE_2D );
	glGenTextures( 1, &Tex2DId );
	glBindTexture( GL_TEXTURE_2D, Tex2DId );
	ILboolean Ok = ilLoadImage( Texture2DName );
	assert( Ok );		
	gluBuild2DMipmaps( GL_TEXTURE_2D, 3, ilGetInteger( IL_IMAGE_WIDTH ), ilGetInteger( IL_IMAGE_HEIGHT ), GL_RGB, GL_UNSIGNED_BYTE, ilGetData() );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE );
	glDisable( GL_TEXTURE_2D );

	// Load cube map
	glGenTextures( 1, &TexCubeId );
	glEnable( GL_TEXTURE_CUBE_MAP_ARB );
	glBindTexture( GL_TEXTURE_CUBE_MAP_ARB, TexCubeId );
	int i;
	for (i=0; i<6; i++) 
	{
		char* Name;
		int Side;
		switch( i )
		{
			case 0: Name="_front";	Side = GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB;	break;
			case 1: Name="_back";	Side = GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB;	break;
			case 2: Name="_up";		Side = GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB;	break;
			case 3: Name="_down";	Side = GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB;	break;
			case 4: Name="_left";	Side = GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB;	break;
			case 5: Name="_right";	Side = GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB;	break;
		}
		
		std::string TexName( TextureCubeName );
		TexName.append( Name );
		TexName.append( ".png" );
		
		ILboolean Ok = ilLoadImage( (const ILstring)TexName.c_str() );
		assert( Ok );		
		gluBuild2DMipmaps( Side, 3, ilGetInteger( IL_IMAGE_WIDTH ), ilGetInteger( IL_IMAGE_HEIGHT ), GL_RGB, GL_UNSIGNED_BYTE, ilGetData() );

	}
	glTexParameteri( GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
	glTexParameteri( GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
	glTexParameteri( GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE );
	glTexParameteri( GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE );
	glDisable( GL_TEXTURE_CUBE_MAP_ARB );

	// Load shaders
	ReloadShader();

	glEnable( GL_CULL_FACE );
	glEnable( GL_DEPTH_TEST );
	glEnable( GL_COLOR_MATERIAL );
	glColorMaterial( GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE );
}


void ReloadShader()
{
	cout << "-Compiling-------------------------------------" << endl;

	if(	GLEW_ARB_shading_language_100
	&&	GLEW_ARB_fragment_shader
	&&	GLEW_ARB_shader_objects
	&&	GLEW_ARB_vertex_shader
	&&	GLEW_ARB_multitexture )
	{
		char FragmentShaderFile[1000];
		char VertexShaderFile[1000];

		sprintf( FragmentShaderFile, "shaders/%s.fs", Shader );
		sprintf( VertexShaderFile, "shaders/%s.vs", Shader );

		GLcharARB* SourceCode = new GLcharARB[10000];
		FILE* SourceFile;
		GLint SourceCodeLen;
		GLhandleARB MyFragmentShader;
		GLhandleARB MyVertexShader;
		GLcharARB Log[10000];
		int L;
		
		//Read fragment shader source
		SourceFile = fopen( FragmentShaderFile, "r" );
		if( !SourceFile )
		{
			cout << "Could not open " << FragmentShaderFile << " for reading" << endl;
			return;
		}
		SourceCodeLen = (GLint)fread( SourceCode, 1, 10000, SourceFile );
		fclose( SourceFile );
	
		MyFragmentShader = glCreateShaderObjectARB( GL_FRAGMENT_SHADER_ARB );
		const char *FragmentLines = SourceCode;
		glShaderSourceARB( MyFragmentShader, 1, &FragmentLines, &SourceCodeLen );
		//Compile fragment shader
		glCompileShaderARB( MyFragmentShader );
		glGetInfoLogARB( MyFragmentShader, 10000, &L, Log );
		if( L ) cout << "Compile log for fragment shader : " << endl << Log << endl;
		else cout << "Fragment shader Ok" << endl;
	

		//Read vertex shader source
		SourceFile = fopen( VertexShaderFile, "r" );
		if( !SourceFile )
		{
			cout << "Could not open " << VertexShaderFile << " for reading" << endl;
			return;
		}
		SourceCodeLen = (GLint)fread( SourceCode, 1, 10000, SourceFile );
		fclose( SourceFile );
		
		MyVertexShader = glCreateShaderObjectARB( GL_VERTEX_SHADER_ARB );
		const char *VertexLines = SourceCode;
		glShaderSourceARB( MyVertexShader, 1, &VertexLines, &SourceCodeLen );
		//Compile vertex shader
		glCompileShaderARB( MyVertexShader );
		glGetInfoLogARB( MyVertexShader, 10000, &L, Log );
		if( L ) cout << "Compile log for vertex shader : " << endl << Log << endl;
		else cout << "Vertex shader Ok" << endl;

		
		//Link fragment and vertex shaders
		MyProgram = glCreateProgramObjectARB();
		glAttachObjectARB( MyProgram, MyFragmentShader );
		glAttachObjectARB( MyProgram, MyVertexShader );
		glLinkProgramARB( MyProgram );
		glGetInfoLogARB( MyProgram, 10000, &L, Log );
		if( L ) cout << "Compile log for link : " << endl << Log << endl;
		else cout << "Link Ok" << endl;
	}
	else
	{
		MyProgram = 0;
		cout << "GLSL or multitexture extensions not available" << endl;
	}
}

////////////////////////////////////////////////////////////////////
//Basic GLUT stuff
////////////////////////////////////////////////////////////////////

void Usage()
{
	cout << "Usage :" << endl;
	cout << "\t" << ProgramName << " [shader] [2d_texture] [cube_map]" << endl;
	cout << "Current values:" << endl;
	cout << "\tshader = " << Shader << endl;
	cout << "\t2d_texture = " << Texture2DName << endl;
	cout << "\tcube_map = " << TextureCubeName << endl;
	cout << endl;
	cout << "Controls:" << endl;
	cout << "\tLeftMouseDrag = rotate camera" << endl;
	cout << "\tMiddleMouseDrag = zoom camera" << endl;
	cout << "\tRightMouseDrag = rotate light/cube map" << endl;
	cout << "\tCtrl+MiddleMouseDrag = move light" << endl;
	cout << "\tF5 = reload shaders" << endl;
	cout << "\tW = toggle wireframe" << endl;
	cout << "\tC = toggle object" << endl;
	cout << "\tH = this help" << endl;
	cout << "\tQ = quit" << endl;
}

void Keypressed( unsigned char Key,int x, int y ) 
{
	switch( Key ) 
	{
		case 27:
		case 'q': 
		case 'Q': 
			exit(0);
		break;

		case 'h':
		case 'H':
			Usage();
		break;

		case 'c':
		case 'C':
			DrawWhat = (DrawWhat+1)%3;
		break;

		case 'w':
		case 'W':
			MyView.Wireframe = !MyView.Wireframe;
		break;
	}
}

void SpecialKeypressed( int Key,int x,int y ) 
{
	switch( Key ) 
	{
		case GLUT_KEY_F1:
			Usage();
		break;

		case GLUT_KEY_F5:
			ReloadShader();
		break;
	}
}

void MouseMoved(int x,int y) 
{
	MyMouse.X0 = MyMouse.X;
	MyMouse.Y0 = MyMouse.Y;
	MyMouse.X =          x /(double)WindowW; 
	MyMouse.Y = (WindowH-y)/(double)WindowH;
	MyMouse.Dx = MyMouse.X-MyMouse.X0;
	MyMouse.Dy = MyMouse.Y-MyMouse.Y0;
}

void MouseDragged( int x,int y)  
{
	MyMouse.X0 = MyMouse.X;
	MyMouse.Y0 = MyMouse.Y;
	MyMouse.X =          x /(double)WindowW; 
	MyMouse.Y = (WindowH-y)/(double)WindowH;
	MyMouse.Dx = MyMouse.X-MyMouse.X0;
	MyMouse.Dy = MyMouse.Y-MyMouse.Y0;

	switch( MyMouse.State )
	{
		case 0: 
		break;
		case MOUSE_LEFT: 
			MyView.AngleY += MyMouse.Dx*50;
			MyView.AngleX -= MyMouse.Dy*50;
		break;
		case MOUSE_MIDDLE:
			MyView.Distance -= MyMouse.Dy * MyView.Distance;
		break;
		case MOUSE_RIGHT:
			Light.AngleY += MyMouse.Dx*50;
			Light.AngleX += MyMouse.Dy*50;
			break;
		case MOUSE_SHIFT|MOUSE_LEFT:
			break;
		case MOUSE_SHIFT|MOUSE_MIDDLE:
			break;
		case MOUSE_SHIFT|MOUSE_RIGHT:
			break;
		case MOUSE_CTRL|MOUSE_LEFT:
			break;
		case MOUSE_CTRL|MOUSE_MIDDLE:
			Light.Distance -= MyMouse.Dy * Light.Distance;
			break;
		case MOUSE_CTRL|MOUSE_RIGHT:
			break;
	}

	glutPostRedisplay();
}


void MouseClicked( int Button, int State, int x, int y )
{
	int Modifiers;
	switch( Button ) 
	{
		case GLUT_LEFT_BUTTON:
			MyMouse.Left = ( State == GLUT_DOWN ); break;
		case GLUT_MIDDLE_BUTTON:
			MyMouse.Middle = ( State == GLUT_DOWN ); break;
		case GLUT_RIGHT_BUTTON:
			MyMouse.Right = ( State == GLUT_DOWN ); 
			break;
	}

	Modifiers = glutGetModifiers();
	MyMouse.Shift = ( Modifiers & GLUT_ACTIVE_SHIFT ) ?1:0;
	MyMouse.Ctrl  = ( Modifiers & GLUT_ACTIVE_CTRL )  ?1:0;
	MyMouse.Alt   = ( Modifiers & GLUT_ACTIVE_ALT )   ?1:0;

	MyMouse.State = MOUSE_SHIFT*MyMouse.Shift | MOUSE_CTRL*MyMouse.Ctrl | MOUSE_ALT*MyMouse.Alt |
			MOUSE_LEFT*MyMouse.Left | MOUSE_MIDDLE*MyMouse.Middle | MOUSE_RIGHT*MyMouse.Right;


	MyMouse.X0 = MyMouse.X; MyMouse.X = x /(double)WindowW; 		MyMouse.Dx = MyMouse.X0 - MyMouse.X;
	MyMouse.Y0 = MyMouse.Y; MyMouse.Y = (WindowH-y)/(double)WindowH; MyMouse.Dy = MyMouse.Y0 - MyMouse.Y;

	switch( MyMouse.State ) 
	{
		case 0: 
			break;
		case MOUSE_LEFT:   
			break;
		case MOUSE_MIDDLE:
			break;
		case MOUSE_RIGHT:
			break;
		case MOUSE_SHIFT|MOUSE_LEFT:
			break;
		case MOUSE_SHIFT|MOUSE_MIDDLE:
			break;
		case MOUSE_SHIFT|MOUSE_RIGHT:
			break;
		case MOUSE_CTRL|MOUSE_LEFT:
			break;
		case MOUSE_CTRL|MOUSE_MIDDLE:
			break;
		case MOUSE_CTRL|MOUSE_RIGHT:
			break;
	}
	glutPostRedisplay();
}


void ReshapeWindow( int Width,int Height ) 
{
	WindowW = Width;
	WindowH = Height;
    glViewport(0,0,WindowW,WindowH);
	glutPostRedisplay();
}


int main(int argc,char **argv)
{
	glutInit(&argc, argv);
	glutInitWindowSize(640, 480);
	glutInitDisplayMode( GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH );
	glutCreateWindow("GLSL Demo");

	ProgramName = argv[0];
	if( argc>=2 ) Shader = argv[1];
	if( argc>=3 ) Texture2DName = argv[2];
	if( argc>=4 ) TextureCubeName = argv[3];

	Usage();

	MyView.Distance = 20;
	MyView.AngleY = 0;
	MyView.AngleX = 20;
	Light.Distance = 10;
	Light.AngleY = 0;
	Light.AngleX = 180;

	glutDisplayFunc( Display );
	glutReshapeFunc( ReshapeWindow );
	glutMouseFunc( MouseClicked );
	glutMotionFunc( MouseDragged );
	glutPassiveMotionFunc( MouseMoved );
	glutKeyboardFunc( Keypressed );
	glutSpecialFunc( SpecialKeypressed );
	glutIdleFunc( Idle );

	glutMainLoop();

	cerr << endl << "Done." << endl;
	return 0;
}

