//
// Simple example of using Cg in OpenGL programs
//
// Author: Alex V. Boreskoff <steps3d@narod.ru>,<alexboreskoff@mtu-net.ru>
//

#include    "glut.h"
#include    <stdio.h>
#include    <stdlib.h>
#include	"Cg/cgGL.h"

#include    "libTexture.h"
#include    "TypeDefs.h"
#include    "Vector3D.h"
#include    "Vector2D.h"
#include	"Vector4D.h"

/////////////////// here goes data for communication with Cg ///////////////////////

CGcontext	context;
CGprofile	vertexProfile;			// profile for vertex shader
CGprofile	fragmentProfile;		// profile for fragment shader
CGprogram	vertexProgram;			// handles for programs
CGprogram	fragmentProgram;
CGparameter	modelViewProjMatrix;	// handles for parameters
CGparameter modelViewMatrix;
CGparameter modelViewItMatrix;
CGparameter	lightPos, eyePos;
CGparameter	tex0;

//////////////////////// here goes main program data ///////////////////////////////

Vector3D	eye   ( 7, 5, 7 );			// camera position
Vector3D	light ( 5, 0, 4 );			// light position
Vector3D	rot ( 0, 0, 0 );
float	 	angle     = 0;
int			mouseOldX = 0;
int			mouseOldY = 0;

unsigned	decalMap;

//Torus		torus ( 1, 3, 30, 30 );

/////////////////////////// here goes main glut part ///////////////////////////////

void checkCgError ()
{
	CGerror error = cgGetError();

	if ( error != CG_NO_ERROR )
 	{
    	fprintf ( stderr, "CG error: %s\n", cgGetErrorString ( error ) );

     	exit(1);
   }
}

void	cgErrorCallback ()
{
	CGerror	lastError = cgGetError ();

	if ( lastError )
	{
		fprintf ( stderr, "CG error: %s\n", cgGetErrorString ( lastError ) );
		exit    ( 1 );
	}
}

void init ()
{
    glClearColor ( 0.0, 0.0, 0.0, 1.0 );
    glEnable     ( GL_DEPTH_TEST );
    glEnable     ( GL_TEXTURE_2D );
    glDepthFunc  ( GL_LEQUAL     );

    glHint       ( GL_POLYGON_SMOOTH_HINT,         GL_NICEST );
    glHint       ( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );

    								// now setup Cg
    								// create context
	context = cgCreateContext ();

									// check for success
	if ( context == NULL )
	{
		fprintf ( stderr, "Cannot create Cg context\n" );

		exit ( 1 );
	}

									// create vertex profile
	vertexProfile = cgGLGetLatestProfile ( CG_GL_VERTEX );

	if ( vertexProfile == CG_PROFILE_UNKNOWN )
	{
		fprintf ( stderr, "Invalid vertex profile type" );

		exit ( 1 );
	}

	cgGLSetOptimalOptions ( vertexProfile );

									// create fragment profile
	fragmentProfile = cgGLGetLatestProfile ( CG_GL_FRAGMENT );

	if ( fragmentProfile == CG_PROFILE_UNKNOWN )
	{
		fprintf ( stderr, "Invalid fragment profile type" );

		exit ( 1 );
	}

	cgGLSetOptimalOptions ( fragmentProfile );

									// now load cg programs
	vertexProgram = cgCreateProgramFromFile ( context, CG_SOURCE, "vertex.cg", vertexProfile, "main", 0 );

	checkCgError ();

	if ( vertexProgram == NULL )
	{
		CGerror	error = cgGetError ();

		fprintf ( stderr, "Error loading vertex program:\n%s", cgGetErrorString ( error ) );

		exit ( 1 );
	}

	fprintf ( stdout, "Vertex Listing:\n%s", cgGetProgramString ( vertexProgram, CG_COMPILED_PROGRAM ) );

	cgGLLoadProgram ( vertexProgram );

	modelViewProjMatrix = cgGetNamedParameter ( vertexProgram, "ModelViewProj" );
	modelViewMatrix     = cgGetNamedParameter ( vertexProgram, "ModelView" );
	modelViewItMatrix   = cgGetNamedParameter ( vertexProgram, "ModelViewIT"   );
	lightPos            = cgGetNamedParameter ( vertexProgram, "lightPos"   );
	eyePos              = cgGetNamedParameter ( vertexProgram, "eyePos"     );

	fragmentProgram = cgCreateProgramFromFile ( context, CG_SOURCE, "fragment.cg", fragmentProfile, "main", 0 );

	checkCgError ();

	if ( fragmentProgram == NULL )
	{
		CGerror	error = cgGetError ();

		fprintf ( stderr, "Error loading fragment program:\n%s", cgGetErrorString ( error ) );

		exit ( 1 );
	}

	fprintf ( stdout, "Fragment Listing:\n%s", cgGetProgramString ( fragmentProgram, CG_COMPILED_PROGRAM ) );

	cgGLLoadProgram ( fragmentProgram );

	tex0 = cgGetNamedParameter ( fragmentProgram, "tex0" );
}

void display ()
{
	glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

												// draw the light
	glMatrixMode ( GL_MODELVIEW );
	glPushMatrix ();

	glDisable       ( GL_TEXTURE_2D );
	glTranslatef    ( light.x, light.y, light.z );
	glColor4f       ( 1, 1, 1, 1 );
	glutSolidSphere ( 0.1f, 15, 15 );

	glPopMatrix  ();
	glEnable     ( GL_TEXTURE_2D );

	glMatrixMode ( GL_MODELVIEW );
	glPushMatrix ();

    glRotatef    ( rot.x, 1, 0, 0 );
    glRotatef    ( rot.y, 0, 1, 0 );
    glRotatef    ( rot.z, 0, 0, 1 );

	cgGLEnableProfile ( vertexProfile   );
	cgGLEnableProfile ( fragmentProfile );
	cgGLBindProgram   ( vertexProgram   );
	cgGLBindProgram   ( fragmentProgram );

    cgGLSetStateMatrixParameter ( modelViewItMatrix,
                                  CG_GL_MODELVIEW_MATRIX, CG_GL_MATRIX_INVERSE_TRANSPOSE );

    cgGLSetStateMatrixParameter ( modelViewProjMatrix,
                                  CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY );

    cgGLSetStateMatrixParameter ( modelViewMatrix,
                                  CG_GL_MODELVIEW_MATRIX, CG_GL_MATRIX_IDENTITY );

	cgGLSetParameter4f ( lightPos, light.x, light.y, light.z, 1 );
	cgGLSetParameter4f ( eyePos,   eye.x,   eye.y,   eye.z,   1 );

	glBindTexture  ( GL_TEXTURE_2D, decalMap );

//	glutSolidTorus ( 1, 3, 30, 30 );

	glutSolidTeapot ( 1.5 );

	cgGLDisableProfile ( vertexProfile   );
	cgGLDisableProfile ( fragmentProfile );

    glPopMatrix ();

	glutSwapBuffers ();
}

void reshape ( int w, int h )
{
   glViewport     ( 0, 0, (GLsizei)w, (GLsizei)h );
   glMatrixMode   ( GL_PROJECTION );
   											// factor all camera ops into projection matrix
   glLoadIdentity ();
   gluPerspective ( 60.0, (GLfloat)w/(GLfloat)h, 1.0, 60.0 );
   gluLookAt      ( eye.x, eye.y, eye.z,	// eye
					0, 0, 0,				// center
					0, 0, 1 );				// up

   glMatrixMode   ( GL_MODELVIEW );
   glLoadIdentity ();
}

void motion ( int x, int y )
{
    rot.y -= ((mouseOldY - y) * 180.0f) / 200.0f;
    rot.z -= ((mouseOldX - x) * 180.0f) / 200.0f;
    rot.x  = 0;

    if ( rot.z > 360 )
		rot.z -= 360;

	if ( rot.z < -360 )
		rot.z += 360;

    if ( rot.y > 360 )
		rot.y -= 360;

	if ( rot.y < -360 )
        rot.y += 360;

    mouseOldX = x;
    mouseOldY = y;

	glutPostRedisplay ();
}

void mouse ( int button, int state, int x, int y )
{
    if ( state == GLUT_DOWN )
    {
        mouseOldX = x;
        mouseOldY = y;
	}
}

void key ( unsigned char key, int x, int y )
{
	if ( key == 27 || key == 'q' || key == 'Q' )	//	quit requested
    	exit ( 0 );
}

void	animate ()
{
	angle  = 0.004f * glutGet ( GLUT_ELAPSED_TIME );

	light.x = 4*cos ( angle );
	light.y = 4*sin ( angle );
	light.z = 3 + 0.3 * sin ( angle / 3 );

	cgGLEnableProfile  ( vertexProfile );
	cgGLBindProgram    ( vertexProgram );
	cgGLSetParameter4f ( lightPos, light.x, light.y, light.z, 1 );
	cgGLSetParameter4f ( eyePos,   eye.x,   eye.y,   eye.z,   1 );
	cgGLDisableProfile ( vertexProfile );

	glutPostRedisplay ();
}

int main ( int argc, char * argv [] )
{
								// initialize glut
	glutInit            ( &argc, argv );
	glutInitDisplayMode ( GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH );
	glutInitWindowSize  ( 500, 500 );


								// create window
	glutCreateWindow ( "Example of Cg specular vertex shader" );

								// register handlers
	glutDisplayFunc  ( display );
	glutReshapeFunc  ( reshape );
	glutKeyboardFunc ( key     );
	glutMouseFunc    ( mouse   );
	glutMotionFunc   ( motion  );
	glutIdleFunc     ( animate );

	init ();

	decalMap  = createTexture2D ( true, "../../Textures/wood1.bmp" );

    									// install program object as part of
    									// current state
	cgGLEnableProfile       ( fragmentProfile );
	cgGLBindProgram         ( fragmentProgram );
	cgGLSetTextureParameter ( tex0, decalMap  );
	cgGLDisableProfile      ( fragmentProfile );

    glutMainLoop ();

	return 0;
}
