#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 our main window
IGLUWindow::Ptr         myWin = 0;

// Variables used for interaction in the UI-widget window
IGLUInt                 smapRes ( 1024, IGLURange<int>(128,4096),  1, "Shadow map resolution" );
IGLUInt                 mrtLayer( 0,    IGLURange<int>(0,3),       1, "MRT layer to display" );

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

// Our framebuffers for the shadowmap and our multi render target 
IGLUFramebuffer::Ptr    shadowMapFBO;
IGLUFramebuffer::Ptr    mrtFBO;

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

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

// Create a shadow map in the input framebuffer object
void CreateShadowMap( IGLUFramebuffer::Ptr &smapFBO )
{
	smapFBO->Bind();
	smapFBO->Clear();
		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(); 
	smapFBO->Unbind();
}

// Use an existing shadow map to render the view from the light
void RenderEyeViewIntoMRTBuffer( IGLUFramebuffer::Ptr &mrtBuf )
{
	mrtBuf->Bind();
	mrtBuf->Clear();				
		shaders[QUERY]->Enable();
			shaders[QUERY]["color"] = vec4(.6f, .5f, .8f, 1.0f);   // Draw the cow
			shaders[QUERY]["model"] = cowPosition;
			objCow->Draw( shaders[QUERY] ); 
			shaders[QUERY]["color"] = vec4(1.0, .50f, .80f, 1.0f); // Draw the plane
			shaders[QUERY]["model"] = planePosition;
			squareGeom->DrawArrays( GL_TRIANGLE_STRIP, 0, 4 );
		shaders[QUERY]->Disable();
	mrtBuf->Unbind();
}


void display ( void )	
{
	CreateShadowMap( shadowMapFBO );
	RenderEyeViewIntoMRTBuffer( mrtFBO );

	IGLUDraw::Fullscreen( mrtFBO[ mrtLayer ] );
}


// 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 square to use as the ground plane
	squareGeom   = IGLUGeometry::CreateQuad( IGLU_GEOM_XY_PLANE | IGLU_GEOM_USE_NORMAL );

	// Create a virtual trackball
	ball         = new IGLUTrackball( myWin->w(), myWin->h() );
	
	// Create our shadow map & multi render target FBOs
	shadowMapFBO = IGLUFramebuffer::CreateShadowMap( smapRes, smapRes ); 
	mrtFBO       = IGLUFramebuffer::CreateMRT( GL_RGBA16F, 768, 768, 4, true );
	
	//Load a shader that creates a shadow map
	shaders[CREATE] = new IGLUShaderProgram( "Shaders/test.vert.glsl", "Shaders/test.frag.glsl" );
	shaders[CREATE]["proj"] = lightProj;
	shaders[CREATE]["view"] = lightView;
	
	// Load a shader that queries the shadow map
	shaders[QUERY] = new IGLUShaderProgram("Shaders/test.vert.glsl", "Shaders/query-shadowmap.frag.glsl");
	shaders[QUERY]["shadowTex"]     = shadowMapFBO[ IGLU_DEPTH ];
	shaders[QUERY]["proj"]          = IGLUMatrix4x4::Perspective( 60, 1, 1.0, 4.0 );
	shaders[QUERY]["view"]          = eyeView * ball->GetMatrix();
	shaders[QUERY]["esLightPos"]    = (eyeView * ball->GetMatrix()) * vec4( lightPos.X(), lightPos.Y(), lightPos.Z(), 1.0f );
	shaders[QUERY]["shadMapMatrix"] = IGLUMatrix4x4::ShadowMapAccessMatrix( lightProj, lightView, eyeView * ball->GetMatrix(), -0.005 );

}

// Track any updates to the trackball matrix when the mouse moves 
void Motion(int x, int y)
{
	ball->UpdateOnMotion(x, y);
	shaders[QUERY]["esLightPos"]    = (eyeView * ball->GetMatrix()) * vec4( lightPos.X(), lightPos.Y(), lightPos.Z(), 1.0f );
	shaders[QUERY]["view"]          = eyeView * ball->GetMatrix();
	shaders[QUERY]["shadMapMatrix"] = IGLUMatrix4x4::ShadowMapAccessMatrix( lightProj, lightView, eyeView * ball->GetMatrix(), -0.005 );

}

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

// 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:  Using a framebuffer with multiple render targets" );
	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
	IGLUWidgetWindow::Ptr uiWin = new IGLUWidgetWindow( 300, 125, "UI Widget Window" );
	uiWin->AddWidget( &smapRes );
	uiWin->AddWidget( &mrtLayer );
	myWin->SetWidgetWindow( uiWin );
	uiWin->CreateWindow();
	
	// Some of our widget/variables need callbacks to affect OpenGL state
	//    when they change, so setup the callback functions here.
	smapRes.SetCallback( UpdateShadowMapResolution );

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