/******************************************************************/
/* A pretty darn good program for drawing the Cornell Box, which  */
/*    uses an indexed vertex array to represent the scene.  This  */
/*    allows for a minimum number of draw calls.  In this case,   */
/*    the whole scene can be drawn with a single call.  However,  */
/*    in order to give the user the option to turn on/off drawing */
/*    of the large & small boxes, we actually use 3 draw calls.   */
/*                                                                */
/* -> Notice that we can draw multiple different triangle strips  */
/*    with a single render call, however we need to insert a      */
/*    special character into the index array, and then enable     */
/*    'primitive restart' to recognize that character (which then */
/*    starts a new triangle strip with the next vertex).          */
/* -> Of course, there are still ways this program could be       */
/*    be cleaned up.  In particular, there are duplicate vertices */
/*    in the scene that could be combined to save space (and      */
/*    perhaps render time).  However, this significantly          */
/*    complicates reading/manipulating data, so I don't typically */
/*    do this unless my model has it built in.                    */
/* -> Of course, the code would also be easier to read if the box */
/*    data was stored elsewhere (e.g., a file).  But that's left  */
/*    as an exercise to the reader.                               */
/*                                                                */
/* Chris Wyman (02/23/2012)                                       */
/******************************************************************/

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

// Include IGLU and set it up to avoid the iglu:: prefix on types
#include "iglu.h"
using namespace iglu;

// IGLU shaders this program uses
IGLUShaderProgram::Ptr  shaders[2] = { 0, 0 };  

// Vertex array to store our geometry!
IGLUVertexArray::Ptr    cboxArray     = 0;  

// Setup 3D projection and default viewing matrix
IGLUMatrix4x4           proj = IGLUMatrix4x4::Perspective( 38.5, 1.0, 5, 20 );
IGLUMatrix4x4           view = IGLUMatrix4x4::LookAt( vec3(2.78, 2.73, -8.00), 
													  vec3(2.78, 2.73, 2.795), 
													  vec3(0.0, 1.0, 0.0) );
IGLUMatrix4x4           smallRotation = IGLUMatrix4x4::Identity();
IGLUMatrix4x4           largeRotation = IGLUMatrix4x4::Identity();

// IGLU variables that can be changed via a FLTK-window user-interface.
IGLUBool                displayFPS ( true,                                  "Display framerate" );
IGLUBool                simpleShade( true,                                  "Use simple shader" );
IGLUBool                enableCull ( false,                                 "Enable back-face culling?" );
IGLUBool                drawSmall  ( true,                                  "Draw small box?" );
IGLUBool                drawLarge  ( true,                                  "Draw large box?" );
IGLUBool                rotateSmall( false,                                 "Rotate small box?" );
IGLUBool                rotateLarge( false,                                 "Rotate large box?" );

// Stores the current rotation angles for our boxes
float angleSmall = 0;
float angleLarge = 0;

// These are handles to the two windows, should you need to modify their
//   behavior after window creation.
IGLUWindow::Ptr         myWin = 0;
IGLUWidgetWindow::Ptr   uiWin = 0;

// Data for the Cornell Box scene, all rolled into a single array.
//    Each vertex is on a single line.  For each vertex, the:
//       1st number        = a material ID (used in the shaders)
//       2nd - 3rd numbers = vertex texture coordinate
//       4th - 6th numbers = vertex surface normal
//       7th - 9th numbers = vertex position
float cboxData[540] = { 
	0,   0, 1,  0, 1, 0,  0.000, 0.000, 0.000,             // Floor
	0,   0, 0,  0, 1, 0,  0.000, 0.000, 5.592,
	0,   1, 1,  0, 1, 0,  5.560, 0.000, 0.000,
	0,   1, 0,  0, 1, 0,  5.560, 0.000, 5.592, 
	0,   0, 1,  0, -1, 0,  0.000, 5.488, 5.592,            // Ceiling
	0,   0, 0,  0, -1, 0,  0.000, 5.488, 0.000,
	0,   1, 1,  0, -1, 0,  5.560, 5.488, 5.592, 
	0,   1, 0,  0, -1, 0,  5.560, 5.488, 0.000,
	0,   0, 1,  0, 0, -1,  0.000, 0.000, 5.592,            // Back wall
	0,   0, 0,  0, 0, -1,  0.000, 5.488, 5.592,
	0,   1, 1,  0, 0, -1,  5.560, 0.000, 5.592, 
	0,   1, 0,  0, 0, -1,  5.560, 5.488, 5.592,
	1,   0, 1,  -1, 0, 0,  5.560, 0.000, 0.000,            // Left wall
	1,   1, 1,  -1, 0, 0,  5.560, 0.000, 5.592, 
	1,   0, 0,  -1, 0, 0,  5.560, 5.488, 0.000,
	1,   1, 0,  -1, 0, 0,  5.560, 5.488, 5.592,
	2,   0, 1,  1, 0, 0,  0.000, 0.000, 5.592,             // Right wall
	2,   1, 1,  1, 0, 0,  0.000, 0.000, 0.000,
	2,   0, 0,  1, 0, 0,  0.000, 5.488, 5.592,
	2,   1, 0,  1, 0, 0,  0.000, 5.488, 0.000,
	0,   0, 1,  0, 1, 0,  1.30, 1.65, 0.65,                // Small box
	0,   1, 1,  0, 1, 0,  0.82, 1.65, 2.25,
	0,   0, 0,  0, 1, 0,  2.90, 1.65, 1.14,
	0,   1, 0,  0, 1, 0,  2.40, 1.65, 2.72,
	0,   0, 1,  0.953, 0.000, 0.302,    2.90, 0, 1.14,
	0,   1, 1,  0.953, 0.000, 0.302,    2.90, 1.65, 1.14,
	0,   0, 0,  0.953, 0.000, 0.302,    2.40, 0, 2.72,
	0,   1, 0,  0.953, 0.000, 0.302,    2.40, 1.65, 2.72,
	0,   0, 1,  0.293, 0.000, -0.956,    1.30, 0, 0.65,
	0,   1, 1,  0.293, 0.000, -0.956,    1.30, 1.65, 0.65,
	0,   0, 0,  0.293, 0.000, -0.956,    2.90, 0, 1.14,
	0,   1, 0,  0.293, 0.000, -0.956,    2.90, 1.65, 1.14,
	0,   0, 1,  -0.958, 0.000, -0.287,    0.82, 0, 2.25,
	0,   1, 1,  -0.958, 0.000, -0.287,    0.82, 1.65, 2.25,
	0,   0, 0,  -0.958, 0.000, -0.287,    1.30, 0, 0.65,
	0,   1, 0,  -0.958, 0.000, -0.287,    1.30, 1.65, 0.65,
	0,   0, 1,  -0.285, 0.000, 0.958,    2.40, 0, 2.72,
	0,   1, 1,  -0.285, 0.000, 0.958,    2.40, 1.65, 2.72,
	0,   0, 0,  -0.285, 0.000, 0.958,    0.82, 0, 2.25,
	0,   1, 0,  -0.285, 0.000, 0.958,    0.82, 1.65, 2.25,
	0,   0, 1,  0, 1, 0,  4.23, 3.30, 2.47,                // Large box
	0,   1, 1,  0, 1, 0,  2.65, 3.30, 2.96,
	0,   0, 0,  0, 1, 0,  4.72, 3.30, 4.06,
	0,   1, 0,  0, 1, 0,  3.14, 3.30, 4.56,
	0,   0, 1,  0.956, 0.000, -0.295,    4.23, 0, 2.47,
	0,   1, 1,  0.956, 0.000, -0.295,    4.23, 3.30, 2.47,
	0,   0, 0,  0.956, 0.000, -0.295,    4.72, 0, 4.06,
	0,   1, 0,  0.956, 0.000, -0.295,    4.72, 3.30, 4.06,
	0,   0, 1,  0.302, 0.000, 0.953,    4.72, 0, 4.06,
	0,   1, 1,  0.302, 0.000, 0.953,    4.72, 3.30, 4.06,
	0,   0, 0,  0.302, 0.000, 0.953,    3.14, 0, 4.56,
	0,   1, 0,  0.302, 0.000, 0.953,    3.14, 3.30, 4.56,
	0,   0, 1,  -0.956, 0.000, 0.293,    3.14, 0, 4.56,
	0,   1, 1,  -0.956, 0.000, 0.293,    3.14, 3.30, 4.56,
	0,   0, 0,  -0.956, 0.000, 0.293,    2.65, 0, 2.96,
	0,   1, 0,  -0.956, 0.000, 0.293,    2.65, 3.30, 2.96,
	0,   0, 1,  -0.296, 0.000, -0.955,    2.65, 0, 2.96,
	0,   1, 1,  -0.296, 0.000, -0.955,    2.65, 3.30, 2.96,
	0,   0, 0,  -0.296, 0.000, -0.955,    4.23, 0, 2.47,
	0,   1, 0,  -0.296, 0.000, -0.955,    4.23, 3.30, 2.47,
};

// This has a list of the indices needed to lookup into
//    the data buffer above for our geometry.  Each line
//    contains the vertex indices for one quad in the box.
// This is a pretty silly buffer, since the vertices in
//    the array above are presented in order with vertex
//    sharing.  In real geometry, this index list could be
//    quite a bit more complex.
unsigned short cboxIndicies[75] = { 
	0, 1, 2, 3,     0xFFFF,
	4, 5, 6, 7,     0xFFFF,
	8, 9, 10, 11,   0xFFFF,
	12, 13, 14, 15, 0xFFFF,
	16, 17, 18, 19, 0xFFFF,
	20, 21, 22, 23, 0xFFFF,
	24, 25, 26, 27, 0xFFFF,
	28, 29, 30, 31, 0xFFFF,
	32, 33, 34, 35, 0xFFFF,
	36, 37, 38, 39, 0xFFFF,
	40, 41, 42, 43, 0xFFFF,
	44, 45, 46, 47, 0xFFFF,
	48, 49, 50, 51, 0xFFFF,
	52, 53, 54, 55, 0xFFFF,
	56, 57, 58, 59, 0xFFFF,
};


// The display callback that is invoked by our IGLUWindow with the screen is redrawn
void display ( void )
{
	// Select which shader to use this pass
	int curShader = simpleShade ? 0 : 1;

	// Does the user want back-face culling enabled?
	if (enableCull) glEnable( GL_CULL_FACE );
	else            glDisable( GL_CULL_FACE );
	
	// Clear the screen
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

	// Draw our geometry with an appropriate shader
	shaders[curShader]->Enable();
	shaders[curShader]["model"] = IGLUMatrix4x4::Identity();
		cboxArray->DrawElements( GL_TRIANGLE_STRIP, 25, 0*sizeof(short) );
		if (drawSmall) {
			shaders[curShader]["model"] = smallRotation;
			cboxArray->DrawElements( GL_TRIANGLE_STRIP, 25, 25*sizeof(short) );
		}
		if (drawLarge) {
			shaders[curShader]["model"] = largeRotation;
			cboxArray->DrawElements( GL_TRIANGLE_STRIP, 25, 50*sizeof(short) );
		}
	shaders[curShader]->Disable();

	// Draw text with some instructions
	IGLUDraw::DrawText( IGLU_FONT_VARIABLE, 5, 738, 
		"A simple Cornell Box display program....  Click the (+) button for options." );
}

void idle( void )
{
	// Does the user want to rotate the blocks
	if (rotateSmall) angleSmall += 0.02;
	if (rotateLarge) angleLarge += 0.02;

	// Update the rotational transforms.
	smallRotation = IGLUMatrix4x4::Translate(1.855,0.0,1.69) * 
		            IGLUMatrix4x4::Rotate( angleSmall, vec3(0,1,0) ) *
					IGLUMatrix4x4::Translate(-1.855,0.0,-1.69);
	largeRotation = IGLUMatrix4x4::Translate(3.685,0.0,3.5125) *
		            IGLUMatrix4x4::Rotate( angleLarge, vec3(0,1,0) ) *
					IGLUMatrix4x4::Translate(-3.685,0.0,-3.5125);
}


// Code that initializes our OpenGL state.  This is guaranteed (by IGLUWindow) to be 
//    called after the OpenGL context & all extensions have been initialized
void OpenGLInitialization( void )
{            
	// Set up our Cornell Box geometry.
	cboxArray = new IGLUVertexArray();
	cboxArray->SetVertexArray( sizeof( cboxData ), cboxData, IGLU_STATIC|IGLU_DRAW );
	cboxArray->SetElementArray( GL_UNSIGNED_SHORT, sizeof( cboxIndicies ), cboxIndicies, IGLU_STATIC|IGLU_DRAW );
	cboxArray->EnablePrimitiveRestart( 0xFFFF );
	cboxArray->EnableAttribute( IGLU_ATTRIB_VERTEX,   3, GL_FLOAT, 9 * sizeof(float), BUFFER_OFFSET(6*sizeof(float)) );
	cboxArray->EnableAttribute( IGLU_ATTRIB_NORMAL,   3, GL_FLOAT, 9 * sizeof(float), BUFFER_OFFSET(3*sizeof(float)) );
	cboxArray->EnableAttribute( IGLU_ATTRIB_TEXCOORD, 2, GL_FLOAT, 9 * sizeof(float), BUFFER_OFFSET(1*sizeof(float)) );
	cboxArray->EnableAttribute( IGLU_ATTRIB_MATL_ID,  1, GL_FLOAT, 9 * sizeof(float), BUFFER_OFFSET(0) );
	
	// Load the shader that performs the wavelet transform
	shaders[0] = new IGLUShaderProgram( "test.vert.glsl", "test.frag.glsl" );
	shaders[0]->SetProgramEnables( IGLU_GLSL_DEPTH_TEST ); 
	shaders[0]->SetProgramDisables( IGLU_GLSL_BLEND ); 
	shaders[0]["project"]   = proj;
	shaders[0]["model"]     = IGLUMatrix4x4::Identity();
	shaders[0]["view"]      = view;

	shaders[1] = new IGLUShaderProgram( "test.vert.glsl", "test2.frag.glsl" );
	shaders[1]->SetProgramEnables( IGLU_GLSL_DEPTH_TEST ); 
	shaders[1]->SetProgramDisables( IGLU_GLSL_BLEND ); 
	shaders[1]["project"]   = proj;
	shaders[1]["model"]     = IGLUMatrix4x4::Identity();
	shaders[1]["view"]      = view;
}

void ToggleFramerate( void *uiBool )
{
	myWin->DisplayFramerate( ((IGLUBool *)uiBool)->GetValue() );
}

// Our main entry point
int main(int argc, char** argv)
{
	// Put together our main window
	myWin = new IGLUWindow( 768, 768, "Simple IGLU Example:  Draw a Cornell Box" );
	myWin->SetWindowProperties( IGLU_WINDOW_NO_RESIZE |	
								IGLU_WINDOW_DOUBLE |
								IGLU_WINDOW_REDRAW_ON_IDLE |
								IGLU_WINDOW_W_FRAMERATE ); 
	myWin->SetDisplayCallback( display );  
	myWin->SetIdleCallback( idle );
	myWin->SetPreprocessOnGLInit( OpenGLInitialization );
	myWin->CreateWindow( argc, argv );     

	// Create a widget window to allow us to interact/change our IGLUVariables
	uiWin = new IGLUWidgetWindow( 300, 300, "UI Widget Window" );
	uiWin->AddWidget( &simpleShade );
	uiWin->AddWidget( &enableCull );
	uiWin->AddWidgetSpacer();
	uiWin->AddWidget( &drawSmall );
	uiWin->AddWidget( &rotateSmall );
	uiWin->AddWidgetSpacer();
	uiWin->AddWidget( &drawLarge );
	uiWin->AddWidget( &rotateLarge );
	uiWin->AddWidgetSpacer();
	uiWin->AddWidget( &displayFPS );

	// Tell our main window about the widget window... 
	myWin->SetWidgetWindow( uiWin );

	// Turning off the framerate display is more complex and actually
	//    needs a callback, so we know when the variable changes
	displayFPS.SetCallback( ToggleFramerate );

	// Start the FLTK interaction loop
	IGLUWindow::Run();
	return 0;
}
