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

// All headers are automatically included from "iglu.h"
#include "iglu.h"
using namespace iglu;

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

IGLUBool                displayFPS         ( true,                                      "Display framerate" );
IGLUBool				visualizeShadowmap ( false,                                     "Visualize shadowmap" );
IGLUBool				useColorMask       ( false,                                     "Enable color masking for shadow map" );
IGLUBool				useLambertian      ( false,                                     "Use Lambertian shading" );
IGLUFloat               zBias              ( -0.005, IGLURange<float>(-0.02,0), 0.0002, "Shadow map bias" );
IGLUInt                 smapRes            ( 1024,   IGLURange<int>(128,4096),  1,      "Shadow map resolution" );

//The scene geometry consists of two objects: a wavefront obj file and a square 
IGLUOBJReader::Ptr      objCow     = 0;
IGLUVertexArray::Ptr    squareGeom = 0;

//We use two shaders--one to create the shadow map, another to query the shadow map
//These are accessed using the enum below
IGLUShaderProgram::Ptr  shaders[2] = {0, 0};
enum ShaderType{ CREATE = 0, QUERY = 1 };

//Trackball for interaction
IGLUTrackball::Ptr		ball = 0;

//The framebuffer holding our shadow map
IGLUFramebuffer::Ptr    shadowMapFBO;

// Location of the light and the eye
vec3            lightPos = vec3(2.0,2.5,0.0);
vec3            eyePos   = vec3(0.0,0.0,2.0);

// Matrices for setting up the view from the eye
IGLUMatrix4x4   eyeView = IGLUMatrix4x4::LookAt( eyePos, vec3::Zero(), vec3::YAxis() );
IGLUMatrix4x4   eyeProj = IGLUMatrix4x4::Perspective( 60, 1, 1.0, 4.0 );

// Matrices for setting up the view from the light (i.e., the shadow map
IGLUMatrix4x4   lightView = IGLUMatrix4x4::LookAt( lightPos, vec3::Zero(), vec3::YAxis() );
IGLUMatrix4x4   lightProj = IGLUMatrix4x4::Perspective( 60, 1, 2.5, 4.6 ); 

// Matrices for positioning our geometry relative to the world origin
IGLUMatrix4x4   cowPosition   = IGLUMatrix4x4::Scale( 0.75f ) * IGLUMatrix4x4::Rotate( -45, vec3::YAxis() );
IGLUMatrix4x4   planePosition = IGLUMatrix4x4::Translate( -0.45*vec3::YAxis() ) * IGLUMatrix4x4::Scale( 2.0f ) *
                                IGLUMatrix4x4::Rotate( 90, vec3::XAxis() );


void display ( void )	
{
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
	
	//Render into the shadow map.  This can be done with or without a color mask.
	//   When no color channels are enabled for rendering, creating the shadow map
	//   is significantly faster (usually 2-4x), so color masking is usually a good
	//   idea for when rendering only to a depth buffer.
	shadowMapFBO->Bind();
	if (useColorMask) 
		glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );
	shadowMapFBO->Clear();

		// Draw our objects in the correct position
		shaders[CREATE]->Enable(); 
			shaders[CREATE]["model"] = cowPosition;
			objCow->Draw( shaders[CREATE] ); 
			shaders[CREATE]["model"] = planePosition;
	        squareGeom->DrawArrays( GL_TRIANGLE_STRIP, 0, 4 );
		shaders[CREATE]->Disable(); 

	// Finish rendering our shadow map
	if (useColorMask) 
		glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );
	shadowMapFBO->Unbind();


	// Did the user ask to just draw the shadow map?
	if( visualizeShadowmap )
		IGLUDraw::Fullscreen( shadowMapFBO[IGLU_DEPTH] );
	
	else // Nope.  So draw a full scene with shadows!
	{	
		// Compute the eye-space position of the light this frame
		vec4 esLightPos = (eyeView * ball->GetMatrix()) * vec4( lightPos.X(), lightPos.Y(), lightPos.Z(), 1.0f );

		// Setup some values constant over all the geometry in the frame
		shaders[QUERY]["useLambertian"] = useLambertian ? 1.0f : 0.0f;
		shaders[QUERY]["esLightPos"]    = esLightPos;
		shaders[QUERY]["shadMapMatrix"] = IGLUMatrix4x4::ShadowMapAccessMatrix( lightProj, lightView, eyeView * ball->GetMatrix(), zBias );
		shaders[QUERY]["view"]          = eyeView * ball->GetMatrix();
		
		// Draw the geometry, modifying per-geometry shader variables as needed
		shaders[QUERY]->Enable();
			shaders[QUERY]["color"] = vec4(.6f, .5f, .8f, 1.0f);
			shaders[QUERY]["model"] = cowPosition;
			objCow->Draw( shaders[QUERY] ); 

			shaders[QUERY]["color"] = vec4(1.0, .50f, .80f, 1.0f);
			shaders[QUERY]["model"] = planePosition;
			squareGeom->DrawArrays( GL_TRIANGLE_STRIP, 0, 4 );
		shaders[QUERY]->Disable();
	}
}


// 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 ){
	
	//Load an obj file
	objCow       = new IGLUOBJReader( "../../CommonSampleFiles/Models/cow.obj", IGLU_OBJ_UNITIZE );

	// Create a virtual trackball
	ball         = new IGLUTrackball( myWin->w(), myWin->h() );
	
	// Create a shadow map.  Make sure to use nearest neighbor on the z-buffer/shadow map.  It doesn't
	//    make sense to linearly interpolate (which is the default IGLU behavior for textures)
	shadowMapFBO = IGLUFramebuffer::Create(GL_R8, smapRes, smapRes, true, false);
	shadowMapFBO[IGLU_DEPTH].SetTextureParameters( IGLU_MAG_NEAREST | IGLU_MIN_NEAREST );
	
	//Load a shader that creates a shadow map
	shaders[CREATE] = new IGLUShaderProgram( "Shaders/test.vert.glsl", "Shaders/test.frag.glsl" );
	shaders[CREATE]->SetProgramEnables( IGLU_GLSL_DEPTH_TEST ); 
	shaders[CREATE]["proj"] = lightProj;
	shaders[CREATE]["view"] = lightView;
	
	// Load a shader that queries a shadow map
	shaders[QUERY] = new IGLUShaderProgram("Shaders/test.vert.glsl", "Shaders/query-shadowmap.frag.glsl");
	shaders[QUERY]->SetProgramEnables(IGLU_GLSL_DEPTH_TEST);
	shaders[QUERY]["shadowTex"] = shadowMapFBO[IGLU_DEPTH];
	shaders[QUERY]["proj"]      = eyeProj;

	// Create a square to use as the ground plane
	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};
	squareGeom = new IGLUVertexArray();
	squareGeom->SetVertexArray( sizeof( vertData ), vertData, IGLU_STATIC|IGLU_DRAW );
	squareGeom->EnableAttribute( IGLU_ATTRIB_VERTEX, 3, GL_FLOAT, 8 * sizeof(float), BUFFER_OFFSET(5*sizeof(float)) );
	squareGeom->EnableAttribute( IGLU_ATTRIB_NORMAL, 3, GL_FLOAT, 8 * sizeof(float), BUFFER_OFFSET(2*sizeof(float)) );
}

// Track any updates to the trackball matrix when the mouse moves 
void Motion(int x, int y)
{
	ball->UpdateOnMotion(x, y);
}

// When the user clicks/releases in the main window, start/stop tracking with our trackball
void Button(int button, int state, int x, int y  )
{
	if(IGLU_EVENT_DOWN == state)
		ball->SetOnClick( x, y );
	else
		ball->Release();
}

// When the user clicks the "Display Framerate" checkbox, we need to update some
//    window parameters, so this callback function needs to be called
void ToggleFramerate( void *uiBool )
{
	myWin->DisplayFramerate( ((IGLUBool *)uiBool)->GetValue() );
}

// When the user resizes the shadow map with the slider, we need to actually resize
//    the framebuffer object, so this callback function needs to be called
void UpdateShadowMapResolution( void *uiInt )
{
	int size = ((IGLUInt *)uiInt)->GetValue();
	shadowMapFBO->Resize( size, size );
}

int main(int argc, char** argv)
{
	// Create our main window
	myWin = new IGLUWindow( 768, 768, "Simple IGLU Example:  Create and Use a Shadow Map" );
	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->SetActiveMotionCallback(Motion);
	myWin->SetMouseButtonCallback(Button);
	myWin->CreateWindow( argc, argv );     
	
	// Create our widget window & add our widgets to it
	uiWin = new IGLUWidgetWindow( 300, 225, "UI Widget Window" );
	uiWin->AddWidget( &zBias );
	uiWin->AddWidget( &smapRes );
	uiWin->AddWidget( &useLambertian );
	uiWin->AddWidget( &useColorMask );
	uiWin->AddWidget( &visualizeShadowmap );
	uiWin->AddWidgetSpacer();
	uiWin->AddWidget( &displayFPS );
	myWin->SetWidgetWindow( uiWin );
	
	// Some of our widget/variables need callbacks to affect OpenGL state
	//    when they change, so setup the callback functions here.
	displayFPS.SetCallback( ToggleFramerate );
	smapRes.SetCallback( UpdateShadowMapResolution );

	// Start running our IGLU OpenGL program!
	IGLUWindow::Run();
	return 0;
}
