/******************************************************************/
/* A better program for drawing the Cornell Box, which combines   */
/*    all the scene data into a single vertex array, saving quite */
/*    a significant amount of initialization space (making the    */
/*    code much more legible).  However, the rendering code is    */
/*    still inefficient, essentially performing a draw call for   */
/*    each quad (or for every pair of triangles).                 */
/*                                                                */
/* -> Also demonstrates the use of a material ID in the vertex    */
/*    array, to get information about the material to the shaders */
/*    on a per-vertex basis.  Currently, this is used to manually */
/*    set colors in the vertex shader, but could be used to look  */
/*    up material data in a texture, uniform buffer, etc.         */
/*                                                                */
/* 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,
};

// 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->DrawArrays( GL_TRIANGLE_STRIP, 0, 4 );
		cboxArray->DrawArrays( GL_TRIANGLE_STRIP, 4, 4 );
		cboxArray->DrawArrays( GL_TRIANGLE_STRIP, 8, 4 );
		cboxArray->DrawArrays( GL_TRIANGLE_STRIP, 12, 4 );
		cboxArray->DrawArrays( GL_TRIANGLE_STRIP, 16, 4 );

		if (drawSmall)
		{
			shaders[curShader]["model"] = smallRotation;
			cboxArray->DrawArrays( GL_TRIANGLE_STRIP, 20, 4 );
			cboxArray->DrawArrays( GL_TRIANGLE_STRIP, 24, 4 );
			cboxArray->DrawArrays( GL_TRIANGLE_STRIP, 28, 4 );
			cboxArray->DrawArrays( GL_TRIANGLE_STRIP, 32, 4 );
			cboxArray->DrawArrays( GL_TRIANGLE_STRIP, 36, 4 );
		}
		if (drawLarge)
		{
			shaders[curShader]["model"] = largeRotation;
			cboxArray->DrawArrays( GL_TRIANGLE_STRIP, 40, 4 );
			cboxArray->DrawArrays( GL_TRIANGLE_STRIP, 44, 4 );
			cboxArray->DrawArrays( GL_TRIANGLE_STRIP, 48, 4 );
			cboxArray->DrawArrays( GL_TRIANGLE_STRIP, 52, 4 );
			cboxArray->DrawArrays( GL_TRIANGLE_STRIP, 56, 4 );
		}
	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->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;
}
