
#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 simple texture we'll load from a file
IGLUTexture::Ptr       startTex         = 0;
IGLUTexture::Ptr       randomTex        = 0;
bool                   useStartTex      = true;
bool                   useRandomTex     = false;

// A simple framebuffer object (e.g., render-to-texture)
IGLUFramebuffer::Ptr   pingPongBufs[2]  = { 0, 0 };
int                    currentBuf       = 0;

// The shader that updates 
IGLUShaderProgram::Ptr gameOfLife       = 0;

// Use a timer to make sure the game doesn't progress too quickly.
IGLUCPUTimer::Ptr      cpuTimer         = 0;
float                  targetFPS        = 20;
char                   fpsBuf[32];

// Our geometry for a full-screen quad
IGLUVertexArray::Ptr   fullScreenQuad   = 0;
static float triStripData[] = { -1, -1,   1, -1,   -1, 1,   1, 1  };

// Colors for our fonts
vec4                   instrColor       = vec4( 1.0f, 0.0f, 0.0f, 0.8f );
vec4                   fpsColor         = vec4( 0.0f, 1.0f, 0.0f, 0.8f );


void display ( void )
{
	// Draw into our temp buffer that stores state for next frame
	//     When assigning the textures to the GLSL shader, the *startTex is used
	//     because both conditions of the ?: operator need to return the same type.
	//     pingPongBufs[][] returns a texture reference, startTex is a texture pointer.
	//     Either one is valid on the RHS of the = operator, but you cannot use both 
	//     inside a ?: operator.  So we simply dereference startTex.
	pingPongBufs[currentBuf]->Bind();
		gameOfLife->Enable();
		gameOfLife["lastStep"] = !useStartTex ? pingPongBufs[1-currentBuf][0]              // Use last frame 
		                                      : (useRandomTex ? *randomTex : *startTex );  // Use one-time start/random texture
		fullScreenQuad->DrawArrays( GL_TRIANGLE_STRIP, 0, 4 );
		gameOfLife->Disable();
	pingPongBufs[currentBuf]->Unbind();

	// Draw the temp buf on screen so viewers can see the current state
	IGLUDraw::Fullscreen( pingPongBufs[currentBuf][0] ); 

	// Update for next frame's computation
	useStartTex = false;          // Once we use the start texture, we're done with it!
	currentBuf  = 1-currentBuf;   // Swap temporary buffers

	// Draw some text/help info
	IGLUDraw::DrawColorText( IGLU_FONT_VARIABLE, 10, 990, instrColor, "Press ' S ' to restart game!" );
	IGLUDraw::DrawColorText( IGLU_FONT_VARIABLE, 10, 965, instrColor, "Press ' R ' to start a random game!" );
	IGLUDraw::DrawColorText( IGLU_FONT_VARIABLE, 10, 940, instrColor, "Press ' + ' to speed up game!" );
	IGLUDraw::DrawColorText( IGLU_FONT_VARIABLE, 10, 915, instrColor, "Press ' - ' to slow down game!" );

	// Get the time since our last frame completed
	sprintf( fpsBuf, "%.1f fps", 1000.0/cpuTimer->Tick() );
	IGLUDraw::DrawColorText( IGLU_FONT_VARIABLE, 10, 5, fpsColor, fpsBuf );

	// Swap OpenGL front/back buffers to display what we just drew.
	glutSwapBuffers();
}


void keys(unsigned char key, int x, int y)
{
	switch ( key )
	{
	default:      // All other keys... Quit.
		exit(0);

	case 's':     // Restart the game.
	case 'S':
		useStartTex = true;
		useRandomTex = false;
		break;
	case 'r':     // Randomize the game.
	case 'R':
		useStartTex = true;
		useRandomTex = true;
		if (randomTex) delete randomTex;
		randomTex = new IGLURandomTexture2D( 256, 256, GL_FLOAT );
		break;
	case '+':     // Compute new frames at a faster refresh rate
	case '=':
		targetFPS *= 1.2;
		break;
	case '-':     // Compute new frames at a slower refresh rate
	case '_':
		targetFPS /= 1.2;
		break;
	}

	glutPostRedisplay();
}

void idle ( void )
{
	// Don't go too fast!
	if ( (1000.0/cpuTimer->GetTime()) > targetFPS ) 
		return;

	// Redraw the screen
	glutPostRedisplay();
}

int main(int argc, char** argv)
{
	// Standard OpenGL/GLUT setup
	glutInit(&argc, argv);
	glutInitDisplayMode( GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH );
	glutInitWindowSize( 1024, 1024 );
	glutInitWindowPosition( 20, 20 );
	glutCreateWindow( "Intermediate IGLU Example: 'Game of Life' using multiple IGLUFramebuffer objects" );
	glewInit();
	glutDisplayFunc( display );
	glutIdleFunc( idle );
	glutKeyboardFunc( keys );

	// Load a simple texture
	printf("(+) Loading starting board for 'Game of Life'...\n" );
	startTex        = new IGLUTexture2D( "lifeStart.ppm", IGLU_MAG_NEAREST | IGLU_MIN_NEAREST );

	// Create our ping-pong buffers we'll use to store the last state and the next state
	pingPongBufs[0] = IGLUFramebuffer::Create( GL_RGBA8, 256, 256 );
	pingPongBufs[1] = IGLUFramebuffer::Create( GL_RGBA8, 256, 256 );

	// Make sure the internal renderable textures in these frame buffers use nearest-neighbor interpolation.
	pingPongBufs[0][0].SetTextureParameters( IGLU_MAG_NEAREST | IGLU_MIN_NEAREST );
	pingPongBufs[1][0].SetTextureParameters( IGLU_MAG_NEAREST | IGLU_MIN_NEAREST );

	// Load the "Game of Life" shader
	gameOfLife      = new IGLUShaderProgram( "gameOfLife.vert.glsl", "gameOfLife.frag.glsl" );
	gameOfLife->SetProgramDisables( IGLU_GLSL_DEPTH_TEST );

	// Create our full-screen quad geometry for drawing
	fullScreenQuad = new IGLUVertexArray();
	fullScreenQuad->SetVertexArray( sizeof( triStripData ), triStripData );
	fullScreenQuad->EnableAttribute( gameOfLife["vertex"], 2, GL_FLOAT, 0, BUFFER_OFFSET(0) );

	// Create a timer to ensure things don't go too fast!
	cpuTimer       = new IGLUCPUTimer();
	cpuTimer->Start();

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