/******************************************************************/
/* This version of the Cornell Box program looks quite messy (and */
/*    it is -- it's a test of how internal IGLU stuff should work */
/*    shortly).  The idea is that it creates numerous "material"  */
/*    structures (verrrry similar to OBJ .mtl files) and then     */
/*    converts this into a texture buffer which can be used to    */
/*    select a material based only on an integer ID during shader */
/*    computation.                                                */
/*                                                                */
/* Additionally, this code uses ides from the sample              */
/*    SimpleTextureArrayCreation to create a sampler2DArray that  */
/*    stores all the textures specified in the "material file."   */
/*    This allows the material structure created to store integer */
/*    indices into the sampler2DArray to find surface texture.    */
/*                                                                */
/* The end result is that shaders using this approach can draw    */
/*    arbitrary .mtl-specified materials by only taking in a      */
/*    per-vertex material ID (part of the vertex array) and two   */
/*    textures, (one storing all non-texture material properties, */
/*    the other storing all textures used as part of the matl.)   */
/*                                                                */
/* Chris Wyman (02/28/2012)                                       */
/******************************************************************/

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

#define NUM_TEXTURES 9
#define NUM_MATLS    6

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

// Textures that will hold our material information
IGLUTextureBuffer::Ptr	texBuffer       = 0;    // Holds the "material texture" we'll create for our shaders
IGLUTexture::Ptr        matlTexArray    = 0;    // Stores all the actual textures our materials need (in an array)
IGLUFramebuffer::Ptr    matlArrFBO      = 0;    // A temp FBO we use to create 'matlTexArray'
IGLUShaderProgram::Ptr  createTexArray  = 0;    // The shader used to create 'matlTexArray'
IGLUTexture2D::Ptr      textures[NUM_TEXTURES]; // Temporaries to store the textures we load from file (sed by 'createTexArray')
IGLUBuffer::Ptr         mtlBuf          = 0;    // An IGLU buffer used as the storage for 'texBuffer'

// IGLU shaders this program uses
IGLUShaderProgram::Ptr  shader        = 0;      // The shader we use to draw the Cornell Box geometry
IGLUShaderProgram::Ptr  toneMap       = 0;      // The shader used to tone & gamma correct the scene.

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

// Stores intermediate framebuffer.
IGLUFramebuffer::Ptr    tempFBO       = 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) );

// IGLU variables that can be changed via a FLTK-window user-interface.
IGLUBool                displayFPS ( true,                                  "Display framerate" );
IGLUFloat               lightIntensity( 1.0, IGLURange<float>(0.1,10), 0.1, "Light intensity:" );
IGLUBool                useToneMap ( false,                                 "Apply tone mapping?" );
IGLUFloat               gammaValue ( 2.2, IGLURange<float>(1.0,3.0), 0.05,  "Image gamma:" );
IGLUBool                useGamma   ( false,                                 "Apply gamma correction?" );

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

// Textures we'll use in our Cornell Box materials...
char fNames[NUM_TEXTURES][128] = {
	"../../CommonSampleFiles/images/dragonCaustic.ppm",
	"../../CommonSampleFiles/images/iowaLogo.ppm",
	"../../CommonSampleFiles/images/lake.ppm",
	"../../CommonSampleFiles/images/newZealand_512.ppm",
	"../../CommonSampleFiles/images/glassDragon.ppm",
	"../../CommonSampleFiles/images/niceSunset.ppm",
	"../../CommonSampleFiles/images/ruinedRomanBath.ppm",
	"../../CommonSampleFiles/images/lena512x512.ppm",
	"../../CommonSampleFiles/images/voxelShadowsFairy.jpg"
};

// 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] = { 
	3,   1, 0,  0, 1, 0,  0.000, 0.000, 0.000,             // Floor
	3,   1, 1,  0, 1, 0,  0.000, 0.000, 5.592,
	3,   0, 0,  0, 1, 0,  5.560, 0.000, 0.000,
	3,   0, 1,  0, 1, 0,  5.560, 0.000, 5.592, 
	4,   1, 0,  0, -1, 0,  0.000, 5.488, 5.592,            // Ceiling
	4,   1, 1,  0, -1, 0,  0.000, 5.488, 0.000,
	4,   0, 0,  0, -1, 0,  5.560, 5.488, 5.592, 
	4,   0, 1,  0, -1, 0,  5.560, 5.488, 0.000,
	5,   1, 0,  0, 0, -1,  0.000, 0.000, 5.592,            // Back wall
	5,   1, 1,  0, 0, -1,  0.000, 5.488, 5.592,
	5,   0, 0,  0, 0, -1,  5.560, 0.000, 5.592, 
	5,   0, 1,  0, 0, -1,  5.560, 5.488, 5.592,
	1,   0, 0,  -1, 0, 0,  5.560, 0.000, 0.000,            // Left wall
	1,   1, 0,  -1, 0, 0,  5.560, 0.000, 5.592, 
	1,   0, 1,  -1, 0, 0,  5.560, 5.488, 0.000,
	1,   1, 1,  -1, 0, 0,  5.560, 5.488, 5.592,
	2,   0, 0,  1, 0, 0,  0.000, 0.000, 5.592,             // Right wall
	2,   1, 0,  1, 0, 0,  0.000, 0.000, 0.000,
	2,   0, 1,  1, 0, 0,  0.000, 5.488, 5.592,
	2,   1, 1,  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,
};

// A structure describing a "material".  This structure is extraordinarily arbitrary.
//    It's only here becuase, OBJ's .mtl file format stores substantially similar 
//    information.  Ideally, this code will be used inside IGLU to hide material loading
//    from developers.
struct CBoxMaterial
{
	vec3 kAmb, kDif, kSpec;  // Ambient, diffuse, specular matl info.
	float kAlpha;            // Known as "dissolve" in OBJ file.
	float kShiny;            // Phong exponent
	float kIdxRefr;          // Index of refraction.  Probably not used 
	int kAmbTexId, kDifTexId, kSpecTexId; // Textures used to modify k-values;
};

// Create a few textures to use for our Cornell box...  Typically would be loaded from an .mtl file
CBoxMaterial  matls[NUM_MATLS] = {
	{ vec3(0.0f), vec3(0.76,0.75,0.50), vec3(0.0), 1.0, 1.0, 1.0, -1, -1, -1 }, // White
	{ vec3(0.0f), vec3(0.63,0.06,0.04), vec3(0.0), 1.0, 1.0, 1.0, -1, 5, -1 },  // Red w/a texture
	{ vec3(0.0f), vec3(0.15,0.48,0.09), vec3(0.0), 1.0, 1.0, 1.0, -1, 7, -1 },  // Green w/a texture
	{ vec3(0.0f), vec3(1.0), vec3(0.0), 1.0, 1.0, 1.0, -1, 6, -1 },  // White w/a texture
	{ vec3(0.0f), vec3(1.0), vec3(0.0), 1.0, 1.0, 1.0, -1, 1, -1 },  // White w/a texture
	{ vec3(0.0f), vec3(1.0), vec3(0.0), 1.0, 1.0, 1.0, -1, 3, -1 },  // White w/a texture
};

// The display callback that is invoked by our IGLUWindow with the screen is redrawn
void display ( void )
{
	// Clear the screen
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

	// Draw our geometry with an appropriate shader into the temporary buffer
	tempFBO->Bind();
	tempFBO->Clear();
		shader->Enable();
		shader["lightIntensity"] = float(lightIntensity);
		shader["model"]          = IGLUMatrix4x4::Identity();
			cboxArray->DrawElements( GL_TRIANGLE_STRIP, 75, 0*sizeof(short) );
		shader->Disable();
	tempFBO->Unbind();

	// Now draw our buffer onto the screen, with tonemapping & gamma correction
	toneMap["useToneMap"] = bool(useToneMap) ? 1.0f : 0.0f;
	toneMap["useGamma"]   = bool(useGamma) ? 1.0f : 0.0f;
	toneMap["gamma"]      = float(gammaValue);
	IGLUDraw::Fullscreen( toneMap, tempFBO[0] );

	// Draw text with some instructions
	IGLUDraw::DrawText( IGLU_FONT_VARIABLE, 5, 738, 
		"Adds material properties (think OBJ .mtl files) to the Cornell Box....  Click (+) for options.", 0.8 );
}


// 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 a temporary float buffer to store our non-tone mapped rendering
	tempFBO = IGLUFramebuffer::Create( GL_RGBA16F, 768, 768, true );

	// 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) );
	
	// The shader that does tone mapping & gamma correction
	toneMap = new IGLUShaderProgram( "shaders/toneMap.vert.glsl", "shaders/toneMap.frag.glsl" );
	toneMap->SetProgramDisables( IGLU_GLSL_DEPTH_TEST | IGLU_GLSL_BLEND ); 

	// Load our textures for our cornell box "materials"
	for (int i=0; i<NUM_TEXTURES; i++)
		textures[i] = new IGLUTexture2D( fNames[i] );

	// Create a Texture2DArray to hold all of our material textures...  We'll create it by rendering into it.
	matlTexArray = new IGLURenderTexture2DArray( 512, 512, NUM_TEXTURES );
	matlArrFBO = new IGLUFramebuffer();
	matlArrFBO->AttachTexture( IGLU_COLOR0, (IGLURenderTexture *)matlTexArray );

	// Load the shader that will create our texture array out of the individual textures.
	createTexArray = new IGLUShaderProgram( "shaders/toArray.vert.glsl", "shaders/toArray.geom.glsl", "shaders/toArray.frag.glsl" );
	createTexArray->SetProgramDisables( IGLU_GLSL_DEPTH_TEST | IGLU_GLSL_BLEND ); 

	// Store our textures into the texture array
	matlArrFBO->Bind();
	for (int i=0; i<NUM_TEXTURES; i++)
	{
		createTexArray[ "outputLayer" ] = i;
		IGLUDraw::Fullscreen( createTexArray, textures[i] );
	}
	matlArrFBO->Unbind();

	// Now that we have our texture array, free the Texture2Ds
	for (int i=0; i<NUM_TEXTURES; i++)
		delete textures[i];

	// Now create our texture array that stores information about each material
	float *matlArrayData = (float *)malloc( NUM_MATLS * 4 * sizeof(float) * 4 );
	for (int i=0; i<NUM_MATLS; i++)
	{
		float *mtlPtr = &matlArrayData[i*16];
		*mtlPtr++ = matls[i].kAmb.X();
		*mtlPtr++ = matls[i].kAmb.Y();
		*mtlPtr++ = matls[i].kAmb.Z();
		*mtlPtr++ = matls[i].kIdxRefr;
		*mtlPtr++ = matls[i].kDif.X();
		*mtlPtr++ = matls[i].kDif.Y();
		*mtlPtr++ = matls[i].kDif.Z();
		*mtlPtr++ = matls[i].kAlpha;
		*mtlPtr++ = matls[i].kSpec.X();
		*mtlPtr++ = matls[i].kSpec.Y();
		*mtlPtr++ = matls[i].kSpec.Z();
		*mtlPtr++ = matls[i].kShiny;
		*mtlPtr++ = matls[i].kAmbTexId;
		*mtlPtr++ = matls[i].kDifTexId;
		*mtlPtr++ = matls[i].kSpecTexId;
		*mtlPtr++ = -1.0f;
	}
	mtlBuf = new IGLUBuffer();
	mtlBuf->SetBufferData( NUM_MATLS * 16 * sizeof(float), matlArrayData );
	texBuffer = new IGLUTextureBuffer();
	texBuffer->BindBuffer( GL_RGBA32F, mtlBuf );
	free( matlArrayData );


	// Shader that draws the Cornell Box
	shader = new IGLUShaderProgram( "shaders/test.vert.glsl", "shaders/test.frag.glsl" );
	shader->SetProgramEnables( IGLU_GLSL_DEPTH_TEST ); 
	shader->SetProgramDisables( IGLU_GLSL_BLEND ); 
	shader["project"]         = proj;
	shader["model"]           = IGLUMatrix4x4::Identity();
	shader["view"]            = view;
	shader["lightIntensity" ] = float(lightIntensity);
	shader["matlInfoTex"]     = texBuffer;
	shader["matlTextures"]    = matlTexArray;
}

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( IGLUWindow::NullIdle );
	myWin->SetPreprocessOnGLInit( OpenGLInitialization );
	myWin->CreateWindow( argc, argv );     

	// Create a widget window to allow us to interact/change our IGLUVariables
	uiWin = new IGLUWidgetWindow( 300, 220, "UI Widget Window" );
	uiWin->AddWidget( &lightIntensity );
	uiWin->AddWidget( &useToneMap );
	uiWin->AddWidgetSpacer();
	uiWin->AddWidget( &gammaValue );
	uiWin->AddWidget( &useGamma );
	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;
}
