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

// All headers are automatically included from "iglu.h"
#include "iglu.h"

// IGLU classes and constants are all inside the iglu namespace.
using namespace iglu;

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

// The image that will store the reflection so we can paste it on our reflective plane.
IGLUFramebuffer::Ptr	reflection     = 0;

// The framebuffer that will be used to compute our summed area table
IGLUFramebuffer::Ptr    sat[2];

// Load our cow
IGLUOBJReader::Ptr      objReader      = 0;

// A trackball to control motion in our scene
IGLUTrackball::Ptr		ball           = 0;

// Information needed for transformations
IGLUCPUTimer::Ptr       cowTimer       = 0;   // The cow is bouncing.  How much?
IGLUMatrix4x4           cowTranslate   = IGLUMatrix4x4::Translate( 0, 2, 0 );

// Standard view/projection settings
IGLUMatrix4x4           proj           = IGLUMatrix4x4::Perspective( 60, 1, 3, 20 );
IGLUMatrix4x4           view           = IGLUMatrix4x4::LookAt( vec3(0, 6, 6), vec3(0, 2, 0), vec3(0, 1, 0) );

// How big is the cow and plane?  These depend on the size of the original geometry.
IGLUMatrix4x4           cowScale       = IGLUMatrix4x4::Scale( 0.4 );
IGLUMatrix4x4           planeScale     = IGLUMatrix4x4::Scale( 4 );

// Shaders two draw our two objects
IGLUShaderProgram::Ptr  cowShader      = 0;
IGLUShaderProgram::Ptr  planeShader    = 0;

// A shader used to compute our summed area table
IGLUShaderProgram::Ptr  satShader      = 0;

// Since we have a mirror, how do we reflect geometry around it?  Since the mirror
//     is a trivial mirror (e.g., the x-z plane), we can use a simple scale matrix
//     to do this.  For arbitrary mirrors we need a much more complex matrix here.
IGLUMatrix4x4           reflect        = IGLUMatrix4x4::Scale( 1, -1, 1 );

// When drawing, we'd like to clip our cow so it isn't visible below the mirror and
//     the reflection doesn't appear to come out of the mirror.  We pass in the 
//     equation of the reflection plane to the shader to enable it to discard fragments
//     on the wrong side.  Of course, the "wrong" side depends on if we're currently
//     drawing the reflected or unreflected scene.
vec4                    mirror( 0, 1, 0, 0 );
vec4                    reflmirror( 0, -1, 0, 0 );

// Geometry for the reflective mirror plane
IGLUVertexArray::Ptr    planeArray     = 0;
float                   planeData[]    = { 0, 1, 0,   -1, 0, -1,   
                                           0, 1, 0,   -1, 0, 1,   
								           0, 1, 0,    1, 0, -1,   
								           0, 1, 0,    1, 0, 1  };

// We use full-screen quads for our recursive doubling pass
IGLUVertexArray::Ptr    quadArray      = 0;
float                   screenQuad[]   = { -1, -1,   1, -1,   -1, 1,   1, 1 };

// What size are we filtering on the mirror?  (This is actually 1/2 filter diameter)
float                   filterWidth    = 4.0;

// What is the current size of the viewport?  Used for positioning text @ top of screen
int                     viewWidth, viewHeight;

// A prototype for our SAT creator.
void CreateSummedAreaTable( const IGLUTexture::Ptr &tex );

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

	// Clear the screen
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

	// -----------------------------------------------------------
	// First, draw the reflection to an off-screen buffer/texture
	// -----------------------------------------------------------

	// Create a reflection of the cow in the 'reflection' buffer
	reflection->Bind();
	reflection->Clear();
		cowShader->Enable(); 
			cowShader["project"]     = proj;
			cowShader["model"]       = reflect * cowTranslate * cowScale;
			cowShader["view"]        = view * ball->GetMatrix();
			cowShader["mirrorPlane"] = reflmirror;
			objReader->Draw( cowShader ); 
		cowShader->Disable(); 
	reflection->Unbind();

	// -----------------------------------------------------------
	// Now, create our summed area table, based on prior results
	// -----------------------------------------------------------
	CreateSummedAreaTable( &reflection[0] );

	// -----------------------------------------------------------
	// Now, the unreflected part of the scene.
	// -----------------------------------------------------------

	// Draw the unreflected cow above the mirror.
	cowShader->Enable(); 
		cowShader["project"]     = proj;
		cowShader["model"]       = cowTranslate * cowScale;
		cowShader["view"]        = view * ball->GetMatrix();
		cowShader["mirrorPlane"] = mirror;
		objReader->Draw( cowShader ); 
	cowShader->Disable(); 

	// Draw a 'mirror' plane textured with the reflected cow image.
	planeShader->Enable(); 
	    planeShader["mirrorTex"]   = sat[0][0]; 
		planeShader["project"]     = proj;
		planeShader["modelview"]   = view * ball->GetMatrix() * planeScale;
		planeShader["filterWidth"] = filterWidth;
		planeArray->DrawArrays( GL_TRIANGLE_STRIP, 0, 4 ); 
	planeShader->Disable(); 

	// Draw the framerate on the screen
	char buf[32];
	sprintf( buf, "%.1f fps", frameTime->EndFrame() );
	IGLUDraw::DrawColorText( IGLU_FONT_FIXED, 0, 0, vec4(1.0f, 1.0f, 0.0f, 1.0f), buf );

	IGLUDraw::DrawColorText( IGLU_FONT_VARIABLE, 0, viewHeight-25, vec4(1.0f, 1.0f, 0.0f, 1.0f), 
		                      "+/- adjusts blur diameter" );

	glutSwapBuffers();
}


void keys(unsigned char key, int x, int y)
{
	if (key == '=' || key == '+')
		filterWidth = iglu::clamp( filterWidth+1.0f, 1.0f, 40.0f );
	else if (key == '-' || key =='_')
		filterWidth = iglu::clamp( filterWidth-1.0f, 1.0f, 40.0f );
	else
		exit(0);
}

void idle ( void )
{
	// Recompute the height of the cow!  Use the current time in a sinusoidal translation.
	double msec  = cowTimer->GetTime();
	float sinVal = 0.5 * sinf( IGLU_PI * msec / 2000.0 ) + 0.5;
	cowTranslate = IGLUMatrix4x4::Translate(0, 0.5+3*sinVal, 0 );

	// Redraw with the new translation.
	glutPostRedisplay();
}

void mouse( int button, int state, int x, int y )
{
	// Handle the trackball motion.
	if ( state == GLUT_DOWN )
		ball->SetOnClick( x, y );
	else if (state == GLUT_UP )
		ball->Release();
}

void motion( int x, int y )
{
	// Handle the trackball motion.
	ball->UpdateOnMotion( x, y ); 
	glutPostRedisplay();
}


void reshape( int w, int h )
{
	// The trackball needs to be told when the screen resizes.
	glViewport( 0, 0, viewWidth=w, viewHeight=h );
	ball->ResizeInteractorWindow( w, h );
	glutPostRedisplay();
}

int main(int argc, char** argv)
{
	// Standard OpenGL/GLUT setup
	glutInit(&argc, argv);
	glutInitDisplayMode( GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH );
	glutInitWindowSize( 512, 512 );
	glutInitWindowPosition( 20, 20 );
	glutCreateWindow( "Intermediate IGLU Example:  Blurry Reflections via Summed Area Tables" );
	glewInit();
	glutDisplayFunc( display );
	glutReshapeFunc( reshape );
	glutIdleFunc( idle );
	glutKeyboardFunc( keys );
	glutMouseFunc( mouse );
	glutMotionFunc( motion );

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

	// Load the cow object
	objReader  = new IGLUOBJReader( "../../CommonSampleFiles/models/cow.obj" );

	// Create a virtual trackball
	ball = new IGLUTrackball( 512, 512 );

	// Create a buffer to store our reflection!
	reflection = IGLUFramebuffer::Create( GL_RGBA8, 512, 512, true );

	// Create a pair of ping-pong buffers for creating our summed-area table
	sat[0]     = IGLUFramebuffer::Create( GL_RGBA32F, 512, 512 );
	sat[0][0].SetTextureParameters( IGLU_CLAMP_TO_EDGE_S | IGLU_CLAMP_TO_EDGE_T );
	sat[1]     = IGLUFramebuffer::Create( GL_RGBA32F, 512, 512 );
	sat[1][0].SetTextureParameters( IGLU_CLAMP_TO_EDGE_S | IGLU_CLAMP_TO_EDGE_T );

	// Load a shader for the objects
	cowShader = new IGLUShaderProgram( "cowShader.vert.glsl", "cowShader.frag.glsl" );
	cowShader->SetProgramEnables( IGLU_GLSL_DEPTH_TEST ); 
	planeShader = new IGLUShaderProgram( "planeShader.vert.glsl", "planeShader.frag.glsl" );
	planeShader->SetProgramEnables( IGLU_GLSL_DEPTH_TEST ); 
	satShader = new IGLUShaderProgram( "satShader.vert.glsl", "satShader.frag.glsl" );
	satShader->SetProgramDisables( IGLU_GLSL_DEPTH_TEST ); 

	// Create the geometry for the mirrored plane
	planeArray = new IGLUVertexArray();
	planeArray->SetVertexArray( sizeof( planeData ), planeData );
	planeArray->EnableAttribute( planeShader["vertex"],   3, GL_FLOAT, 6*sizeof(float), BUFFER_OFFSET(3*sizeof(float)) );
	planeArray->EnableAttribute( planeShader["normal"],   3, GL_FLOAT, 6*sizeof(float), BUFFER_OFFSET(0) );

	// We need a timer, so our cow can jump!
	cowTimer       = new IGLUCPUTimer();
	cowTimer->Start();

	// Set the background color
	glClearColor( 0, 0.6, 0, 1 );

	// Create our geometry for a full-screen quad
	quadArray = new IGLUVertexArray();
	quadArray->SetVertexArray( sizeof( screenQuad ), screenQuad );
	quadArray->EnableAttribute( satShader["vertex"], 2, GL_FLOAT, 0, BUFFER_OFFSET(0) );

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

// Requires a texture with dimensions that are a power of 2!
void CreateSummedAreaTable( const IGLUTexture::Ptr &tex )
{
	// How many passes will we have to create our SAT?  (Using recursive doubling)
	int xPasses     = (int)( log( (float)reflection[0].GetWidth()  )/ log( 2.0f ) + 0.001 );
	int yPasses     = (int)( log( (float)reflection[0].GetHeight() )/ log( 2.0f ) + 0.001 );
	int totalPasses = xPasses + yPasses;

	// Based on that, choose which FBO to draw into first.  We choose
	//     this so that sat[0] will contain the final reault.
	int currBuf = ( totalPasses % 2 ? 0 : 1 );

	// How many pixels are we offset this pass?
	float passTexOffset=1.0;
	for (int i=0; i < xPasses; i++)
	{
		sat[currBuf]->Bind();
		satShader->Enable();
		satShader["inputTex"]    = i==0 ? *tex : sat[1-currBuf][0];
		satShader["texelOffset"] = vec2( passTexOffset/tex->GetWidth(), 0 );
			quadArray->DrawArrays( GL_TRIANGLE_STRIP, 0, 4 );
		satShader->Disable();
		sat[currBuf]->Unbind();

		currBuf = 1-currBuf;
		passTexOffset *= 2;
	}

	passTexOffset=1.0;
	for (int i=0; i < yPasses; i++)
	{
		sat[currBuf]->Bind();
		satShader->Enable();
		satShader["inputTex"]    = sat[1-currBuf][0];
		satShader["texelOffset"] = vec2( 0, passTexOffset/tex->GetHeight() );
			quadArray->DrawArrays( GL_TRIANGLE_STRIP, 0, 4 );
		satShader->Disable();
		sat[currBuf]->Unbind();

		currBuf = 1-currBuf;
		passTexOffset *= 2;
	}
}