/*======================================================================================

	Program:		Range Imaging Toolkit (RITK)
	Module:			ritkOpenGLFloatVolumeCudaRayCastEntity.cxx
	Version:		$Revision: 2882 $
	Date:			$Date: 2012-04-02 17:00:07 +0200 (Mo, 02 Apr 2012) $

	Copyright (c) 2010-2011
	Pattern Recognition Lab, 
    University Erlangen-Nuremberg, Germany
    All rights reserved.

	This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
	See RITKCopyright.txt for more information.

	http://www5.informatik.uni-erlangen.de/research/software/ritk

========================================================================================*/

// Required for windows
#pragma comment(lib, "opengl32.lib")
#pragma comment(lib, "glu32.lib")

#include <QDataStream>
#include <QVariant>
#include <QDockWidget>
#include <QApplication>
#include <QComboBox>

#include "ritkDebugManager.h"
#include "ThirdParty/GLee.h"

#include "ritkOpenGLFloatVolumeCudaRayCastEntity.h"
#include "ritkOpenGLVolumeRayCastEntityShaders.h"
#include "ritkLUTs.h"



#include <itkRescaleIntensityImageFilter.h>


// Degree to radian conversion
#define DEG2RAD(x) (3.14159265*x)/180.0

// Convenience macro to check for OpenGL errors
#define CHECK_GL_ERROR()												\
{																		\
	GLenum errCode = glGetError();										\
	if ( errCode != GL_NO_ERROR )										\
	{																	\
	std::cout << "An OpenGL error occured: " << errCode << std::endl;	\
	std::cout << gluErrorString(errCode) << std::endl;					\
	std::cout << __FILE__ << " Line " << __LINE__  << std::endl;		\
	}																	\
}


namespace ritk
{

//----------------------------------------------------------------------------
OpenGLFloatVolumeCudaRayCastEntity::OpenGLFloatVolumeCudaRayCastEntity()
{
	// Create the raycaster
	Superclass::m_RayCaster = CudaRayCasterType::New();

	// Not initialized by default
	m_InitFlag = false;

	// Create our control unit
	m_ControlUnit = new OpenGLFloatVolumeRayCastControlUnit();

	// Connect our control unit's signals
	connect(m_ControlUnit->m_RenderBBCheckBox, SIGNAL(toggled(bool)), this, SLOT(RenderBoundingBox(bool)));
	connect(m_ControlUnit->m_LUTComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(SetLUT(int)));

	// No OpenGL
	m_Texture = 0;
	m_LUTTexture = 0;
	m_ShaderProgram = 0;
}


//----------------------------------------------------------------------------
OpenGLFloatVolumeCudaRayCastEntity::~OpenGLFloatVolumeCudaRayCastEntity()
{
}


//----------------------------------------------------------------------------
QDockWidget*
OpenGLFloatVolumeCudaRayCastEntity::GetControlUnit() const
{
	return m_ControlUnit;	
}


//----------------------------------------------------------------------------
void
OpenGLFloatVolumeCudaRayCastEntity::RenderBoundingBox(bool b)
{
	Superclass::SetRenderBoundingBox(b);
	emit StatusChanged();
}


//----------------------------------------------------------------------------
void
OpenGLFloatVolumeCudaRayCastEntity::InitGL()
{
	if ( m_InitFlag )
		return;

	// Create our textures
	glGenTextures(1, &m_Texture);
	glGenTextures(1, &m_LUTTexture);

	// Shaders
	LoadShader(OpenGLVolumeRayCasetEntity_VertexShader, true);
	LoadShader(OpenGLVolumeRayCasetEntity_FragmentShader, false);
	glLinkProgram(m_ShaderProgram);
	CHECK_GL_ERROR();

	m_InitFlag = true;

	// Default LUT
	SetLUT(0);
}


//----------------------------------------------------------------------------
void
OpenGLFloatVolumeCudaRayCastEntity::CloseGL()
{
	// Delete our texture
	glDeleteTextures(1, &m_Texture);
	glDeleteTextures(1, &m_LUTTexture);

	// Shader
	for ( unsigned int i = 0; i < m_ShaderHandles.size(); i++ )
	{
		glDetachShader(m_ShaderProgram, m_ShaderHandles[i]);
		glDeleteShader(m_ShaderHandles[i]);
	}
	m_ShaderHandles.clear();

	if ( m_ShaderProgram )
	{
		glDeleteProgram(m_ShaderProgram); 
		m_ShaderProgram = 0;
	}

	m_InitFlag = false;
}


//----------------------------------------------------------------------------
GLuint
OpenGLFloatVolumeCudaRayCastEntity::LoadShader(const char *Program, bool VertexFlag)
{
	// No valid program
	if ( !Program )
	{
		std::cerr << "No valid program provided!" << std::endl;
		return 0;
	}

	// Create shader. The type is set by the user
	GLuint ShaderHandle = 0;
	if ( VertexFlag )
		ShaderHandle = glCreateShader(GL_VERTEX_SHADER);
	else
		ShaderHandle = glCreateShader(GL_FRAGMENT_SHADER);

	// Set source
	glShaderSource(ShaderHandle, 1, &Program, NULL);

	// Compile shader
	glCompileShader(ShaderHandle);

	// If the shader program does not exist, create it
	if ( !m_ShaderProgram )
		m_ShaderProgram = glCreateProgram();

	// Finally attach the shader to the shader program and save the handle
	glAttachShader(m_ShaderProgram,ShaderHandle);

	m_ShaderHandles.push_back(ShaderHandle);

	// Some debug info
	int infologLength = 0;
	int charsWritten  = 0;
	char *infoLog;
	glGetShaderiv(ShaderHandle, GL_INFO_LOG_LENGTH, &infologLength);

	if (infologLength > 0)
	{
		infoLog = (char *)malloc(infologLength);
		glGetShaderInfoLog(ShaderHandle, infologLength, &charsWritten, infoLog);

		//printf("ShaderInfo: %s\n",infoLog);
		free(infoLog);
	}
	return ShaderHandle;
}


//----------------------------------------------------------------------------
void
OpenGLFloatVolumeCudaRayCastEntity::SetLUT(int id)
{
	if ( !m_InitFlag )
	{
		InitGL();
		return;
	}

	// Bind the LUT
	unsigned int LUTID = id;
	unsigned int NumLUTElems = 0;
	const GLfloat *LUT = 0;
	if ( LUTID == 0 )
		LUT = ritk::GetLUT("GrayScale", &NumLUTElems);
	else if ( LUTID == 1 )
		LUT = ritk::GetLUT("FireStorm1",&NumLUTElems);
	else if ( LUTID == 2 )
		LUT = ritk::GetLUT("FireStorm2",&NumLUTElems);
	else if ( LUTID == 3 )
		LUT = ritk::GetLUT("ColdFusion",&NumLUTElems);
	else if ( LUTID == 4 )
		LUT = ritk::GetLUT("HylaArborea",&NumLUTElems);
	else if ( LUTID == 5 )
		LUT = ritk::GetLUT("Jet", &NumLUTElems);
	else
	{
		LOG_DEB("Invalid LUTID: " << LUTID << " Reverting to default grayscale...");
		LUT = ritk::GetLUT("GrayScale", &NumLUTElems);
	}
	glBindTexture(GL_TEXTURE_1D, m_LUTTexture);
	glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameterf(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameterf(GL_TEXTURE_1D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, NumLUTElems, 0, GL_RGBA, GL_FLOAT, LUT);
	glBindTexture(GL_TEXTURE_1D, 0);

	//emit StatusChanged();
}


//----------------------------------------------------------------------------
void
OpenGLFloatVolumeCudaRayCastEntity::PostProcessAndRender()
{
	// Should return immediately once we're initialized
	InitGL();

	// Cast the projector's output to UC
#if 1
	typedef itk::Image<float,2> OutputImageType;
	typedef itk::RescaleIntensityImageFilter<CudaRayCasterType::OutputImageType, OutputImageType> RescalerType;
	RescalerType::Pointer Rescaler = RescalerType::New();
	Rescaler->SetOutputMinimum(0);
	Rescaler->SetOutputMaximum(1);
	Rescaler->SetInput(Superclass::m_RayCaster->GetOutput());
	Rescaler->Update();
	std::cout << "Min=" << Rescaler->GetInputMinimum() << " Max=" << Rescaler->GetInputMaximum() << std::endl;
	OutputImageType::Pointer UCImage = Rescaler->GetOutput();
#else
	CudaRayCasterType::OutputImageType::Pointer UCImage = Superclass::m_RayCaster->GetOutput();
#endif


	// Enable shader program
	glUseProgram(m_ShaderProgram);

	// Bind the UC image to our texture
	GLint MainTextureLocation = glGetUniformLocation(m_ShaderProgram, "m_MainTexture");
	glActiveTexture(GL_TEXTURE0); 
	glBindTexture(GL_TEXTURE_2D, m_Texture);
	//glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, UCImage->GetRequestedRegion().GetSize()[0], UCImage->GetRequestedRegion().GetSize()[1], 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, UCImage->GetBufferPointer());
	glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, UCImage->GetRequestedRegion().GetSize()[0], UCImage->GetRequestedRegion().GetSize()[1], 0, GL_LUMINANCE, GL_FLOAT, UCImage->GetBufferPointer());
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	glEnable(GL_TEXTURE_2D);
	glUniform1i(MainTextureLocation, 0);
	glDisable(GL_TEXTURE_2D);

	// LUT texture
	GLint LUTTextureLocation = glGetUniformLocation(m_ShaderProgram, "m_LUTTexture");
	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_1D, m_LUTTexture);
	glEnable(GL_TEXTURE_1D);
	glUniform1i(LUTTextureLocation, 1);
	glDisable(GL_TEXTURE_1D);

	CHECK_GL_ERROR();

	// Disable the current state
	glMatrixMode(GL_MODELVIEW); 
	glPushMatrix(); 
	glLoadIdentity();
	glMatrixMode(GL_PROJECTION);
	glPushMatrix(); 
	glLoadIdentity();
	glDisable(GL_LIGHTING);
	glDisable(GL_DEPTH_TEST);
	glEnable(GL_TEXTURE_2D);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA,GL_ONE); 

	CHECK_GL_ERROR();

	// Render the projection to the texture
	glColor3f(1,1,1);
	glBegin (GL_QUADS);
		glTexCoord2f(0.0f, 0.0f);
		//glColor3f(1,0,0);
		glVertex3f(-1.0f, -1.0f, -1.0f); 

		glTexCoord2f(1.0f, 0.0f);
		//glColor3f(0,1,0);
		glVertex3f(1.0f, -1.0f, -1.0f); 

		glTexCoord2f(1.0f, 1.0f);
		//glColor3f(0,0,1);
		glVertex3f(1.0f, 1.0f, -1.0f);

		glTexCoord2f(0.0f, 1.0f);
		//glColor3f(1,1,1);
		glVertex3f(-1.0f, 1.0f, -1.0f); 
	glEnd();

	CHECK_GL_ERROR();


	// Restore previous state
	glDisable(GL_TEXTURE_2D);
	glEnable(GL_LIGHTING);
	glEnable(GL_DEPTH_TEST);
	glDisable(GL_BLEND);
	glPopMatrix(); 
	glMatrixMode(GL_MODELVIEW); 
	glPopMatrix();

	// Unload shader program
	glUseProgram(0);
}




} // ritk
