/*************************************************************
** Image space refraction example.                          **
** ------------------------------                           **
**                                                          **
** An IGLU example showing how to implement a more complex  **
**     effect, refraction.  The work here is from the       **
**     paper "An approximate image-space approach for       **
**     interactive refraction," though we used the deferred **
**     rendering approach proposed by the paper "Adaptive   **
**     caustic maps using deferred shading."                **
**                                                          **
** Chris Wyman (1/31/2012)                                  **
*************************************************************/


#include <GL/glew.h>
#include <stdio.h>

#include "iglu.h"
using namespace iglu;

// Out model reader
IGLUOBJReader::Ptr      objReader = 0;

// Key matriices variable for this program
IGLUMatrix4x4           modelview = IGLUMatrix4x4::LookAt(vec3(1.0f, 0.0f, 5.0f), vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 1.0f, 0.0f));
IGLUMatrix4x4           project   = IGLUMatrix4x4::Perspective(60, 1024.0 / 768.0, 0.1, 10.0);

// Shaders used in this sample program
IGLUShaderProgram::Ptr  geomShader      = 0;
IGLUShaderProgram::Ptr  drawLightProbe  = 0;
IGLUShaderProgram::Ptr  refractShader   = 0;
IGLUShaderProgram::Ptr  resolveShader   = 0;

// A texture containing the light probe
IGLUTextureLightprobeCubemap::Ptr   lightProbe = 0;
IGLUTexture::Ptr                    randTex2D = 0;

// An intermediate framebuffer object we'll use to store a texture array with surface normals
IGLUFramebuffer::Ptr	refrNorms = 0;
IGLUFramebuffer::Ptr	msFbo     = 0;

// A simple trackball to rotate our geometry
IGLUTrackball::Ptr		objBall = 0;
IGLUTrackball::Ptr		eyeBall = 0;

// Our index of refraction
float                   idxRefr    = 1.2;
float                   shiny      = 64.0;
int                     numSamples = 32;

// A vertex array to store our geometry
IGLUVertexArray::Ptr    vertArray = 0;

// A frame timer we'll use to display a counter on screen
IGLUFrameRate::Ptr      frameTime = 0;

// Data for a simple square
float vertData[32] = { 
	0, 0,  0, 0, 1,  -1, -1, 0, 
	1, 0,  0, 0, 1,   1, -1, 0, 
	0, 1,  0, 0, 1,  -1, 1, 0,
	1, 1,  0, 0, 1,   1, 1, 0, 
};



void display ( void )
{
	// Start timing this frame
	frameTime->StartFrame();

	// Clear the screen to start the frame fresh
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

	// Create the gometry buffers (storing normals & depths)
	refrNorms->Bind();
	refrNorms->Clear();
		geomShader->Enable();
		geomShader["modelview"] = modelview * objBall->GetMatrix() * IGLUMatrix4x4::Scale( 0.5 );
			objReader->Draw( geomShader ); 
		geomShader->Disable();
	refrNorms->Unbind();

	// Draw the unrefracted background
	drawLightProbe->Enable();
	drawLightProbe["rotation"] = eyeBall->GetMatrix();
		vertArray->DrawArrays( GL_TRIANGLE_STRIP, 0, 4 );
	drawLightProbe->Disable();

	// Draw the refractive into our multisample buffer
	msFbo->Bind();
	msFbo->Clear();
		refractShader->Enable();
		refractShader["rotation"] = eyeBall->GetMatrix();
			vertArray->DrawArrays( GL_TRIANGLE_STRIP, 0, 4 );
		refractShader->Disable();
	msFbo->Unbind();

	// Resolve our multisample refraction buffer into the framebuffer
	resolveShader->Enable();
		vertArray->DrawArrays( GL_TRIANGLE_STRIP, 0, 4 );
	resolveShader->Disable();

	// Draw information on teh screen
	char buf[64];
	sprintf( buf, "%.1f fps", frameTime->EndFrame(), 0.5 );
	IGLUDraw::DrawText( IGLU_FONT_FIXED, 0, 0, buf );

	sprintf( buf, "Index of refraction: %.2f    Change: + / -", idxRefr );
	IGLUDraw::DrawText( IGLU_FONT_VARIABLE, 10, glutGet( GLUT_WINDOW_HEIGHT )-30, buf );

	sprintf( buf, "Phong Shininess: %.1f    Change: < / >", shiny );
	IGLUDraw::DrawText( IGLU_FONT_VARIABLE, 10, glutGet( GLUT_WINDOW_HEIGHT )-55, buf );

	sprintf( buf, "Random Samples: %d    Change: ( / )", numSamples );
	IGLUDraw::DrawText( IGLU_FONT_VARIABLE, 10, glutGet( GLUT_WINDOW_HEIGHT )-80, buf );

	// Swap the GLUT buffers so we're actually show what we drew.
	glutSwapBuffers();
}

void mouse( int button, int state, int x, int y )
{
	// Handle trackball clicks and releases
	if ( state == GLUT_DOWN && button == GLUT_RIGHT_BUTTON )
		eyeBall->SetOnClick( x, y );
	else if ( state == GLUT_DOWN && button == GLUT_LEFT_BUTTON )
		objBall->SetOnClick( x, y );
	else if (state == GLUT_UP )
	{
		eyeBall->Release();
		objBall->Release();
	}
}

void motion( int x, int y )
{
	// Update the trackball matrix when we detect motion.
	objBall->UpdateOnMotion( x, y ); 
	eyeBall->UpdateOnMotion( x, y ); 
	glutPostRedisplay();
}


void keys(unsigned char key, int x, int y)
{
	switch(key)
	{
	case 27:
	case'q':
	case'Q':
		exit(0);
		break;
	case'+':
	case'=':
		idxRefr = iglu::clamp( idxRefr+0.02, 1.0, 1.5 );
		refractShader["idxRefr"]  = idxRefr;
		break;
	case'-':
	case'_':
		idxRefr = iglu::clamp( idxRefr-0.02, 1.0, 1.5 );
		refractShader["idxRefr"]  = idxRefr;
		break;
	case'>':
	case'.':
		shiny = iglu::clamp( shiny*1.414213562f, 1.0f, 262144.0f);
		refractShader["shininess"] = shiny;
		break;
	case'<':
	case',':
		shiny = iglu::clamp( shiny/1.414213562f, 1.0f, 262144.0f );
		refractShader["shininess"] = shiny;
		break;
	case')':
	case'0':
		numSamples = iglu::clamp( numSamples+1, 1, 32 );
		resolveShader["numSamples"] = numSamples;
		break;
	case '(':
	case '9':
		numSamples = iglu::clamp( numSamples-1, 1, 32 );
		resolveShader["numSamples"] = numSamples;
		break;
	}
}

void idle ( void )
{
	glutPostRedisplay();
}

int main(int argc, char** argv)
{
	glutInit(&argc, argv);
	glutInitDisplayMode( GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_MULTISAMPLE);
	glutInitWindowSize( 1024, 768 );
	glutInitWindowPosition( 20, 20 );
	glutCreateWindow( "Advanced IGLU Example: Image-space Refraction with Frosted Surface (Using Phong Distribution)" );
	glewInit();
	glutDisplayFunc( display );
	glutKeyboardFunc( keys );
	glutIdleFunc( idle );
	glutMouseFunc( mouse );
	glutMotionFunc( motion );

	// Create our frame timer
	frameTime = new IGLUFrameRate( 20 );

	printf("(+) Initializing random numbers...\n");
	randTex2D = new IGLURandomTexture2D( 1024, 1024, GL_FLOAT );

	// Load the texture with IGLU
	printf("(+) Loading object...\n" );
	objReader  = new IGLUOBJReader( "../../CommonSampleFiles/models/cow.obj" );

	printf("(+) Loading lightprobe...\n" );
	lightProbe = new IGLUTextureLightprobeCubemap( "../../CommonSampleFiles/images/grace_cross.ppm" );

	// Create a virtual trackball
	printf("(+) Creating virtual trackballa...\n");
	objBall = new IGLUTrackball( 1024, 768 );
	eyeBall = new IGLUTrackball( 1024, 768 );
	
	// Create a framebuffer with our texuture array for the normal buffers
	printf("(+) Creating intermediate step framebuffers...\n");
	refrNorms = new IGLUFramebuffer();
	refrNorms->AttachTexture( IGLU_COLOR, new IGLURenderTexture2DArray( 1024, 768, 2, GL_RGBA16F ) );
	refrNorms->AttachTexture( IGLU_DEPTH, new IGLURenderTexture2DArray( 1024, 768, 2, GL_DEPTH_COMPONENT24 ) );

	// Create a fbo for our stochastic gather
	msFbo = IGLUFramebuffer::CreateMultisample( GL_RGBA8, 1024, 768, 32 );


	printf("(+) Loading shaders...\n" );
	geomShader = new IGLUShaderProgram( "shaders/renderFrontAndBackNormals.vert.glsl", 
		                                "shaders/renderFrontAndBackNormals.Geom.glsl",
										"shaders/renderFrontAndBackNormals.Frag.glsl");
	geomShader->SetProgramEnables( IGLU_GLSL_DEPTH_TEST );
	geomShader["project"] = project;

	refractShader = new IGLUShaderProgram( "shaders/refraction.vert.glsl", "shaders/refraction.frag.glsl");
	refractShader->SetProgramDisables( IGLU_GLSL_DEPTH_TEST );
	refractShader->SetProgramEnables( IGLU_GLSL_MULTISAMPLE | IGLU_GLSL_SAMPLE_SHADING );
	refractShader["normalTex"] = refrNorms[0];
	refractShader["bgTex"]     = lightProbe;
	refractShader["fovy"]      = float( 60.0 );
	refractShader["aspect"]    = float( 1024.0 / 768.0 );
	refractShader["idxRefr"]   = idxRefr;
	refractShader["shininess"] = shiny;
	refractShader["project"]   = project;
	refractShader["randTex"]   = randTex2D;

	drawLightProbe = new IGLUShaderProgram( "shaders/displayCubemap.vert.glsl", "shaders/displayCubemap.frag.glsl");
	drawLightProbe->SetProgramDisables( IGLU_GLSL_DEPTH_TEST );
	drawLightProbe["fovy"]    = float( 60.0 );
	drawLightProbe["aspect"]  = float( 1024.0 / 768.0 );
	drawLightProbe["cubeTex"] = lightProbe;

	// Now load the shader that takes the multisample buffer and figures out a color to display on screen
	resolveShader = new IGLUShaderProgram( "shaders/resolveMSbuffer.vert.glsl", "shaders/resolveMSbuffer.frag.glsl");
	resolveShader->SetProgramEnables( IGLU_GLSL_MULTISAMPLE ); 
	resolveShader["theRenderTex"]     = msFbo[0];
	resolveShader["proj"]             = IGLUMatrix4x4::Identity();   
	resolveShader["model"]            = IGLUMatrix4x4::Identity();
	resolveShader["numSamples"]       = numSamples;

	printf("(+) Initializing geometry...\n");
	vertArray = new IGLUVertexArray();
	vertArray->SetVertexArray( sizeof( vertData ), vertData, IGLU_STATIC|IGLU_DRAW );
	vertArray->EnableAttribute( IGLU_ATTRIB_VERTEX,   3, GL_FLOAT, 8 * sizeof(float), BUFFER_OFFSET(5*sizeof(float)) );
	vertArray->EnableAttribute( IGLU_ATTRIB_NORMAL,   3, GL_FLOAT, 8 * sizeof(float), BUFFER_OFFSET(2*sizeof(float)) );
	vertArray->EnableAttribute( IGLU_ATTRIB_TEXCOORD, 2, GL_FLOAT, 8 * sizeof(float), BUFFER_OFFSET(0) );

	// Set the clear color
	glClearColor( 0, 0, 0, 1 );

	// Start the GLUT interaction loop
	glutMainLoop();
	return 0;
}
