/*********************************************************/
/* 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;

#define SCREEN_WIDTH   1464	
#define SCREEN_HEIGHT  1098

// 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_WITH_Z_ORACLE,
	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;
IGLUInt                 numSamples( NUM_SAMPLES_PER_PIXEL, 1, NUM_SAMPLES_PER_PIXEL, "samples per pixel" );
IGLUFloat				scaleFactor( 2.0f, 0.2, 3.0, "model scale" );
IGLUFloat               modelAlpha( 0.3f, 0.02, 1.0, "model alpha" );

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, SCREEN_WIDTH / float(SCREEN_HEIGHT), 2.0, 10.0);

// Shaders used in this sample program
IGLUShaderProgram::Ptr  resolve            = 0;
IGLUShaderProgram::Ptr  alphaStencilPass   = 0;
IGLUShaderProgram::Ptr  samplerShader      = 0;

IGLUShaderProgram::Ptr  zOnlyResolve       = 0;
IGLUShaderProgram::Ptr  zOnlySample        = 0;
IGLUShaderProgram::Ptr  zOnlyNormalize     = 0;

// An intermediate framebuffer object we'll use to store a multisample buffer
IGLUFramebuffer::Ptr    msFbo      = 0;
IGLUFramebuffer::Ptr    zOnlyMSFbo = 0;
IGLUFramebuffer::Ptr    prepassFbo = 0;
IGLUFramebuffer::Ptr    zTransResolve = 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 PrepassGeometry( void )
{
	alphaStencilPass->Enable();
		glBlendFunc( GL_DST_COLOR, GL_ZERO );
		alphaStencilPass["model"] = modelview * ball->GetMatrix() * IGLUMatrix4x4::Scale( scaleFactor );
		alphaStencilPass["color"] = vec4( 1.0f, 1.0f, 1.0f, modelAlpha );
		objReader->Draw( alphaStencilPass ); 
	alphaStencilPass->Disable();
}

void DrawGeometryDepth( void )
{
	zOnlySample->Enable();
		glBlendEquation( GL_MIN );
		glBlendFunc( GL_ONE, GL_ONE );
		zOnlySample["model"] = modelview * ball->GetMatrix() * IGLUMatrix4x4::Scale( scaleFactor );
		zOnlySample["color"] = vec4( 1.0f, 1.0f, 1.0f, modelAlpha );
		zOnlySample["randOffset"] = vec2( rng->fRandom(), rng->fRandom() );
		zOnlySample["numSamples"] = int(numSamples);
		objReader->Draw( zOnlySample ); 
		glBlendEquation( GL_FUNC_ADD );
	zOnlySample->Disable();
}

void DrawGeometry( void )
{
	samplerShader->Enable();
		
		samplerShader["model"] = modelview * ball->GetMatrix() * IGLUMatrix4x4::Scale( scaleFactor );
		samplerShader["color"] = vec4( 1.0f, 1.0f, 1.0f, modelAlpha );
		samplerShader["randOffset"] = vec2( rng->fRandom(), rng->fRandom() );
		samplerShader["numSamples"] = int(numSamples);
		objReader->Draw( samplerShader ); 

	samplerShader->Disable();
}

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

	// Clear the screen to start the frame fresh
	glClearColor(0,0,0,1);
	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
		glClearColor(0,0,0,0);
		msFbo->Bind();
		msFbo->Clear();
			DrawGeometry( );
		msFbo->Unbind();

		// Compute an alpha correction factor & "stencil" for fragment gathers
		glClearColor(1,1,1,1);
		prepassFbo->Bind();
		prepassFbo->Clear();
			PrepassGeometry();
		prepassFbo->Unbind();

		// Do a resolve phase to figure out the color based on all 32 samples
		resolve->Enable();
		resolve["numSamples"]       = int(numSamples);
			vertArray->DrawArrays( GL_TRIANGLE_STRIP, 0, 4 );
		resolve->Disable();
	}
	else if (drawMode == DRAW_WITH_Z_ORACLE)
	{
		// Draw the squares into our 32-sample buffer
		glClearColor(1,1,1,1);
		zOnlyMSFbo->Bind();
		zOnlyMSFbo->Clear();
			DrawGeometryDepth( );
		zOnlyMSFbo->Unbind();

		// Do the z-compare for each surface
		zTransResolve->Bind();
		GLenum drawBufs[2] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 };
		GLfloat clearColor0[4] = {0, 0, 0, 0};
		GLfloat clearColor1[4] = {1, 1, 1, 1};
		glDrawBuffers( 2, drawBufs );
		glClearBufferfv( GL_COLOR, 0, clearColor0 );
		glClearBufferfv( GL_COLOR, 1, clearColor1 );
			zOnlyResolve->Enable();
				glBlendFunci( 0, GL_ONE, GL_ONE );
				glBlendFunci( 1, GL_DST_COLOR, GL_ZERO );
				zOnlyResolve["model"] = modelview * ball->GetMatrix() * IGLUMatrix4x4::Scale( scaleFactor );
				zOnlyResolve["color"] = vec4( 1.0f, 1.0f, 1.0f, modelAlpha );
				zOnlyResolve["numSamples"] = int(numSamples);
				objReader->Draw( zOnlyResolve ); 
			zOnlyResolve->Disable();	
		glDrawBuffers( 1, drawBufs );
		zTransResolve->Unbind();

		zOnlyNormalize->Enable();
			vertArray->DrawArrays( GL_TRIANGLE_STRIP, 0, 4 );
		zOnlyNormalize->Disable();
	}

	// 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, (drawMode == DRAW_TO_TEXTURE) ? 
		"Alpha-corrected Stochastic Transparency" : "Depth-based Stochastic Transparency" );
	IGLUDraw::DrawColorText( IGLU_FONT_VARIABLE, 410, screenHeight-35, color::Gray50, "('M' to change)" );
	sprintf( buf, "%d sample(s) per pixel", int(numSamples) );
	IGLUDraw::DrawColorText( IGLU_FONT_VARIABLE, 10, screenHeight-55, color::Yellow, buf );
	IGLUDraw::DrawColorText( IGLU_FONT_VARIABLE, 220, screenHeight-55, color::Gray50, "(+/- to change)" );
	
	sprintf( buf, "Model alpha: %.2f", float(modelAlpha) );
	IGLUDraw::DrawColorText( IGLU_FONT_VARIABLE, 10, screenHeight-75, color::Sienna, buf );
	IGLUDraw::DrawColorText( IGLU_FONT_VARIABLE, 220, screenHeight-75, color::Gray50, "('<' / '>' to change)" );
	sprintf( buf, "Model scale: %.1fx", float(scaleFactor) );
	IGLUDraw::DrawColorText( IGLU_FONT_VARIABLE, 10, screenHeight-95, color::Sienna, buf );
	IGLUDraw::DrawColorText( IGLU_FONT_VARIABLE, 220, screenHeight-95, color::Gray50, "('S' / 's' 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)
{
	numSamples.UpdateFromInput( key );
	scaleFactor.UpdateFromInput( key );
	modelAlpha.UpdateFromInput( key );

	switch(key)
	{
	case 27:
	case'q':
	case'Q':
		exit(0);
		break;
	case 'M':
	case'm':
		drawMode = (drawMode+1) % NUM_DRAW_MODES; 
		break;
	}
}

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


void InitializeInterface( void )
{
	modelAlpha.AddKeyResponse( '>', +0.02 );
	modelAlpha.AddKeyResponse( '.', +0.02 );
	modelAlpha.AddKeyResponse( '<', -0.02 );
	modelAlpha.AddKeyResponse( ',', -0.02 );
	scaleFactor.AddKeyResponse( 's', +0.2 );
	scaleFactor.AddKeyResponse( 'S', -0.2 );
	numSamples.AddKeyResponse( '+',  +1 );
	numSamples.AddKeyResponse( '=',  +1 );
	numSamples.AddKeyResponse( '-', -1 );
	numSamples.AddKeyResponse( '_', -1 );
}

int main(int argc, char** argv)
{
	glutInit(&argc, argv);
	glutInitDisplayMode( GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_MULTISAMPLE);
	glutInitWindowSize( SCREEN_WIDTH, SCREEN_HEIGHT );
	glutInitWindowPosition( 20, 20 );
	glutCreateWindow( "Advanced IGLU Example:  Depth-Based and Alpha-Corrected 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( SCREEN_WIDTH, SCREEN_WIDTH, GL_FLOAT );
	rng       = new IGLURandom();

	// Load the texture with IGLU
	printf("(+) Loading object...\n" );
	objReader  = new IGLUOBJReader( "O:/GraphicsData/models/centimeters.obj", IGLU_OBJ_UNITIZE );
	//objReader  = new IGLUOBJReader( "O:/GraphicsData/models/hairball.obj", IGLU_OBJ_UNITIZE | IGLU_OBJ_COMPACT_STORAGE );
	//objReader  = new IGLUOBJReader( "../../CommonSampleFiles/models/cow.obj", IGLU_OBJ_UNITIZE | IGLU_OBJ_COMPACT_STORAGE );
	//objReader  = new IGLUOBJReader( "O:/GraphicsData/models/sanMiguel/san-miguel.obj", IGLU_OBJ_UNITIZE );

	// Create a virtual trackball
	printf("(+) Creating a virtual trackball...\n");
	ball = new IGLUTrackball( SCREEN_WIDTH, SCREEN_HEIGHT );
	
	printf("(+) Creating framebuffers...\n");
	msFbo         = IGLUFramebuffer::CreateMultisample( GL_RGBA16F, SCREEN_WIDTH, SCREEN_HEIGHT, NUM_SAMPLES_PER_PIXEL, true );
	zOnlyMSFbo    = IGLUFramebuffer::CreateMultisample( GL_R32F, SCREEN_WIDTH, SCREEN_HEIGHT, NUM_SAMPLES_PER_PIXEL );

	prepassFbo    = IGLUFramebuffer::Create( GL_R16F, SCREEN_WIDTH, SCREEN_HEIGHT );

	//zTransResolve = IGLUFramebuffer::Create( GL_RGBA16F, SCREEN_WIDTH, SCREEN_HEIGHT );
	zTransResolve = new IGLUFramebuffer();
	zTransResolve->AttachTexture( IGLU_COLOR0, new IGLURenderTexture2D( SCREEN_WIDTH, SCREEN_HEIGHT, GL_RGBA16F ) );
	zTransResolve->AttachTexture( IGLU_COLOR1, new IGLURenderTexture2D( SCREEN_WIDTH, SCREEN_HEIGHT, GL_R16F ) );
	
	

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

	samplerShader = new IGLUShaderProgram( "shaders/drawModel.vert.glsl", "shaders/sampleStratified.frag.glsl");
	samplerShader->SetProgramEnables( IGLU_GLSL_DEPTH_TEST | IGLU_GLSL_MULTISAMPLE );
	samplerShader->SetProgramDisables( IGLU_GLSL_BLEND );
	samplerShader["proj"]    = project;
	samplerShader["randTex"] = randTex2D;
	samplerShader["numSamples"] = int(numSamples);

	alphaStencilPass = new IGLUShaderProgram( "shaders/drawModel.vert.glsl", "shaders/alphaStencilPass.frag.glsl");
	alphaStencilPass->SetProgramEnables( IGLU_GLSL_BLEND );
	alphaStencilPass->SetProgramDisables( IGLU_GLSL_DEPTH_TEST );
	alphaStencilPass["proj"]    = project;

	zOnlySample = new IGLUShaderProgram( "shaders/drawModel.vert.glsl", "shaders/zOnlyStratified.frag.glsl");
	zOnlySample->SetProgramEnables( IGLU_GLSL_BLEND | IGLU_GLSL_MULTISAMPLE );
	zOnlySample->SetProgramDisables( IGLU_GLSL_DEPTH_TEST );
	zOnlySample["proj"]    = project;
	zOnlySample["randTex"] = randTex2D;
	zOnlySample["numSamples"] = int(numSamples);

	// 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->SetProgramDisables( IGLU_GLSL_BLEND );
	resolve["theRenderTex"]     = msFbo[ IGLU_COLOR0 ];
	resolve["alphaTex"]         = prepassFbo[ IGLU_COLOR0 ];
	resolve["proj"]             = IGLUMatrix4x4::Identity();   
	resolve["model"]            = IGLUMatrix4x4::Identity();
	resolve["numSamples"]       = int(numSamples);

	zOnlyNormalize = new IGLUShaderProgram( "shaders/textureDisplay.vert.glsl", "shaders/zOnlyNormalize.frag.glsl");
	zOnlyNormalize->SetProgramDisables( IGLU_GLSL_DEPTH_TEST | IGLU_GLSL_BLEND ); 
	zOnlyNormalize["sumTex"]           = zTransResolve[ IGLU_COLOR0 ];
	zOnlyNormalize["alphaTex"]         = zTransResolve[ IGLU_COLOR1 ]; //prepassFbo[ IGLU_COLOR0 ];
	zOnlyNormalize["proj"]             = IGLUMatrix4x4::Identity();   
	zOnlyNormalize["model"]            = IGLUMatrix4x4::Identity();

	zOnlyResolve                = new IGLUShaderProgram( "shaders/drawModel.vert.glsl", "shaders/zOnlyResolve.frag.glsl");
	zOnlyResolve->SetProgramEnables( IGLU_GLSL_BLEND ); 
	zOnlyResolve->SetProgramDisables( IGLU_GLSL_DEPTH_TEST );
	zOnlyResolve["theRenderTex"] = zOnlyMSFbo[ IGLU_COLOR0 ];
	zOnlyResolve["proj"]         = project;   
	zOnlyResolve["numSamples"]   = int(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) );

	InitializeInterface();

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