/*********************************************************/
/* A program that displays using a fairly simplistic     */
/*     'stochastic' transparency technique.  In quotes   */
/*     since a positional hash is used for randomization */
/* This demonstrates the use of IGLU render-to-texture,  */
/*     multisample framebuffers, shaders, trackball,     */
/*     vertex arrays, matrices, and use of multiple      */
/*     shaders, changing shader variables during display */
/*     and perhaps a few other things.                   */
/* The original non-IGLU demo by Ethan Kerzner.          */
/*********************************************************/

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

#include "iglu.h"
using namespace iglu;

// How many samples are in our multisample buffer?
#define NUM_SAMPLES_PER_PIXEL  8

// What types of drawing techniques will we use?
enum MODES
{
	DRAW_TO_TEXTURE = 0,
	DRAW_TO_SCREEN,
	NUM_DRAW_MODES
};

// A selector to allow us to swap between drawing to a 32-sample
//    framebuffer object or the (1-sample) screen
int                     drawMode       = DRAW_TO_TEXTURE;
int                     shaderSelected = 0;
int                     numSamples     = NUM_SAMPLES_PER_PIXEL;

int                     screenWidth, screenHeight;

// Texture storing random seeds for stochastic rasterization
IGLUTexture::Ptr        randTex2D = 0;
IGLURandom::Ptr         rng       = 0;

// 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  resolve            = 0;
IGLUShaderProgram::Ptr  samplerShaders[2];

// An intermediate framebuffer object we'll use to store a multisample buffer
IGLUFramebuffer::Ptr    msFbo     = 0;

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

// 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, 1,  0, 0, 1,  -1, -1, 0, 
	1, 1,  0, 0, 1,   1, -1, 0, 
	0, 0,  0, 0, 1,  -1, 1, 0,
	1, 0,  0, 0, 1,   1, 1, 0, 
};


void DrawGeometry( void )
{
	samplerShaders[shaderSelected]->Enable();

		// Draw the first square.  White, opaque.
		samplerShaders[shaderSelected]["model"] = modelview * ball->GetMatrix();
		samplerShaders[shaderSelected]["color"] = vec4( 1.0f, 1.0f, 1.0f, 1.0f );
		samplerShaders[shaderSelected]["randOffset"] = vec2( rng->fRandom(), rng->fRandom() );
		vertArray->DrawArrays( GL_TRIANGLE_STRIP, 0, 4 );
		
		// Draw the second square.  Red, 50% opaque.
		samplerShaders[shaderSelected]["model"] = modelview * ball->GetMatrix() * IGLUMatrix4x4::Translate(1.0f, 0.0f, 1.0f);
		samplerShaders[shaderSelected]["color"] = vec4( 1.0f, 0.0f, 0.0f, 0.3f );
		samplerShaders[shaderSelected]["randOffset"] = vec2( rng->fRandom(), rng->fRandom() );
		objReader->Draw( samplerShaders[shaderSelected] ); 

		// Draw the third square.  Blue, 25% opaque.
		samplerShaders[shaderSelected]["model"] = modelview * ball->GetMatrix() * IGLUMatrix4x4::Translate(-0.5f, 1.0f, 0.1f);
		samplerShaders[shaderSelected]["color"] = vec4( 0.0f, 0.0f, 1.0f, 0.25 );
		samplerShaders[shaderSelected]["randOffset"] = vec2( rng->fRandom(), rng->fRandom() );
		vertArray->DrawArrays( GL_TRIANGLE_STRIP, 0, 4 );
	
	samplerShaders[shaderSelected]->Disable();
}

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

	// Pick one of our two drawing modes to use
	if (drawMode == DRAW_TO_TEXTURE)
	{
		// Draw the squares into our 32-sample buffer
		msFbo->Bind();
		msFbo->Clear();
			DrawGeometry( );
		msFbo->Unbind();

		// Do a resolve phase to figure out the color based on all 32 samples
		resolve->Enable();
			vertArray->DrawArrays( GL_TRIANGLE_STRIP, 0, 4 );
		resolve->Disable();
	}
	else if (drawMode == DRAW_TO_SCREEN)
		// Draw the squares into out 1-sample screen buffer
		DrawGeometry( );	

	// Draw a framerate counter on screen
	char buf[32];
	sprintf( buf, "%.1f fps", frameTime->EndFrame() );
	IGLUDraw::DrawText( IGLU_FONT_FIXED, 0, 0, buf );
	IGLUDraw::DrawText( IGLU_FONT_VARIABLE, 10, screenHeight-35, 
		                !shaderSelected ? "Stratified random samples" : "Independent random samples" );
	IGLUDraw::DrawColorText( IGLU_FONT_VARIABLE, 310, screenHeight-35, vec4(0.5, 0.5, 0.5, 1.0), "('M' to change)" );
	sprintf( buf, "%d sample(s) per pixel", (drawMode == DRAW_TO_TEXTURE) ? numSamples : 1 );
	IGLUDraw::DrawColorText( IGLU_FONT_VARIABLE, 10, screenHeight-55, vec4(1.0, 1.0, 0.0, 1.0), buf );
	IGLUDraw::DrawColorText( IGLU_FONT_VARIABLE, 310, screenHeight-55, vec4(0.5, 0.5, 0.5, 1.0), "(+/- to change)" );
	IGLUDraw::DrawColorText( IGLU_FONT_VARIABLE, 10, screenHeight-75, vec4(0.5, 0.5, 1.0, 1.0), 
		                     (drawMode == DRAW_TO_TEXTURE) ? "Drawing to multisample buffer" : "Drawing to single sample buffer" );
	IGLUDraw::DrawColorText( IGLU_FONT_VARIABLE, 310, screenHeight-75, vec4(0.5, 0.5, 0.5, 1.0), "('A' to change)" );

	// 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 )
		ball->SetOnClick( x, y );
	else if (state == GLUT_UP )
		ball->Release();
}

void motion( int x, int y )
{
	// Update the trackball matrix when we detect motion.
	ball->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 '=':
		numSamples = iglu::clamp( numSamples+1, 1, NUM_SAMPLES_PER_PIXEL );
		break;
	case '-':
	case '_':
		numSamples = iglu::clamp( numSamples-1, 1, NUM_SAMPLES_PER_PIXEL );
		break;
	case 'M':
	case 'm':
		shaderSelected = ( shaderSelected + 1 ) % 2;
		break;
	case 'A':
	case'a':
		drawMode = (drawMode+1) % NUM_DRAW_MODES; 
		break;
	}

	samplerShaders[0]["numSamples"] = (drawMode == DRAW_TO_TEXTURE) ? numSamples : 1;
	samplerShaders[1]["numSamples"] = (drawMode == DRAW_TO_TEXTURE) ? numSamples : 1;
	resolve["numSamples"]           = (drawMode == DRAW_TO_TEXTURE) ? numSamples : 1;
}

void idle ( void )
{
	glutPostRedisplay();
}

// Callback invoked when the window is resized
void reshape( int w, int h )
{
  glViewport( 0, 0, screenWidth = w, screenHeight = h );
  ball->ResizeInteractorWindow( w, h ); 
  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( "More Complex IGLU Example:  Stratified Stochastic Transparency" );
	glewInit();
	glutDisplayFunc( display );
	glutKeyboardFunc( keys );
	glutIdleFunc( idle );
	glutMouseFunc( mouse );
	glutMotionFunc( motion );
	glutReshapeFunc( reshape );

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

	// Load the texture with IGLU
	printf("(+) Creating a randomly populated texture...\n" );
	randTex2D = new IGLURandomTexture2D( 1024, 1024, GL_FLOAT );
	rng       = new IGLURandom();

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

	// Create a virtual trackball
	printf("(+) Creating a virtual trackball...\n");
	ball = new IGLUTrackball( 1024, 768 );
	
	printf("(+) Creating multisample framebuffer...\n");
	msFbo = IGLUFramebuffer::CreateMultisample( GL_RGBA32F, 1024, 768, 8, true );

	printf("(+) Loading vertex and fragment shaders...\n" );
	
	// First, load the shader that takes geometry and draws it into a multisample buffer

	samplerShaders[0] = new IGLUShaderProgram( "shaders/drawModel.vert.glsl", "shaders/sampleStratified.frag.glsl");
	samplerShaders[0]->SetProgramEnables( IGLU_GLSL_DEPTH_TEST | IGLU_GLSL_MULTISAMPLE );
	samplerShaders[0]["proj"]    = project;
	samplerShaders[0]["randTex"] = randTex2D;
	samplerShaders[0]["numSamples"] = (drawMode == DRAW_TO_TEXTURE) ? numSamples : 1;

	samplerShaders[1] = new IGLUShaderProgram( "shaders/drawModel.vert.glsl", "shaders/sampleIndependentRandom.frag.glsl");
	samplerShaders[1]->SetProgramEnables( IGLU_GLSL_DEPTH_TEST | IGLU_GLSL_MULTISAMPLE );
	samplerShaders[1]["proj"]    = project;
	samplerShaders[1]["randTex"] = randTex2D;
	samplerShaders[1]["numSamples"] = (drawMode == DRAW_TO_TEXTURE) ? numSamples : 1;

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

	printf("(+) Initializing geometry...\n");
	vertArray = new IGLUVertexArray();
	vertArray->SetVertexArray( sizeof( vertData ), vertData, IGLU_STATIC|IGLU_DRAW );

	// This is a bit odd.  Our quad always needs vertex positions, and sometimes needs normals
	//    (for shading) and sometimes needs texcoords (when copying multisample buffer to
	//    display it averaged on screen).  While 'vertex', 'normal' and 'texcoord' are all 
	//    located at the same link spot in the shaders (thanks to our use of GLSL's layout
	//    location property), the GLSL compiler does dead code & variable removal, so 'normal'
	//    is not in the 'resolve' shader and 'texcoord' is not in the alphaWeightedCoin shader.
	//    This leaves our C code looking a bit weird, below, since we have to enable all 3 attribues
	//    but can't do them all with the same shader.
	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) );

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