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

Program:		Range Imaging Toolkit (RITK)
Module:			ritkOpenGLVisualizationWidget.cxx
Version:		$Revision: 3114 $
Date:			$Date: 2012-04-19 09:54:08 +0200 (Do, 19 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

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

#include <ritkDebugManager.h>

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

#include "ThirdParty/GLee.h"

#include "ritkOpenGLVisualizationWidget.h"
#include "ritkManager.h"

#include <math.h>

namespace ritk
{

#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;		\
	}																		\
}

//----------------------------------------------------------------------------
QGLContext* OpenGLVisualizationWidget::s_GLContext = NULL;


//----------------------------------------------------------------------------
QGLWidget* OpenGLVisualizationWidget::s_QGLWidget = NULL;


//----------------------------------------------------------------------------
QGLContext*
OpenGLVisualizationWidget::GetGLContext()
{

	return const_cast<QGLContext*>(GetGlobalQGLWidget()->context());
}


//----------------------------------------------------------------------------
QGLWidget*
OpenGLVisualizationWidget::GetGlobalQGLWidget()
{
	
	/*QGLContext *Context = new QGLContext(QGLFormat());
	Context->create();
	m_OpenGLContext = Context;
	std::cout << "=== Global OpenGL context @" << m_OpenGLContext << " (Valid=" << Context->isValid() << ")" << std::endl;*/

	// Create the global widget
	if ( !s_QGLWidget )
	{
		s_QGLWidget = new QGLWidget(Manager::GetInstance().GetMainWindow(), NULL, Qt::Window);
		//s_QGLWidget = new QGLWidget();
		s_GLContext = const_cast<QGLContext*>(s_QGLWidget->context());
		std::cout << "OpenGLVisualizationWidget@" << QThread::currentThreadId() << ":: Creating global OpenGL context @" << s_GLContext << " (Valid=" << s_GLContext->isValid() << ")" << std::endl;
		s_QGLWidget->showMinimized();
		s_QGLWidget->hide();
		//s_QGLWidget->show();
		/*s_QGLWidget->setParent(Manager::GetInstance().GetMainWindow());*/
		s_QGLWidget->setWindowTitle(tr("RITK global GL window"));
	}
	return s_QGLWidget;
}


//----------------------------------------------------------------------------
OpenGLVisualizationWidget::OpenGLVisualizationWidget(QWidget *parent) :
	QGLWidget(parent, OpenGLVisualizationWidget::GetGlobalQGLWidget())
{
	// To accept key events
	setFocusPolicy(Qt::StrongFocus);

	m_DrawWire = false;
	m_Render3D = true;
	m_RenderModeFixed = false;

	m_PrimaryEntity = NULL;

	// Create our camera
	m_Camera = CameraType::New();
	m_Camera->SetRender3D(m_Render3D);
	m_Camera->setParentWidget(this);
	connect(m_Camera, SIGNAL(StatusChanged()), this, SLOT(Update()));
	
	// Internal communication
	connect(this, SIGNAL(ResetCameraSignal()), this, SLOT(ResetCamera()));

	this->setMouseTracking(true);

	/*std::cout << "OpenGLVisualizationWidget" << std::endl;
	std::cout << "	Context@" << this->context() << std::endl;
	std::cout << "	Valid=" << this->context()->isValid() << std::endl;
	std::cout << "	Sharing=" << this->context()->isSharing() << std::endl;*/

	m_BackGroundTexture = 0;
}


//----------------------------------------------------------------------------
OpenGLVisualizationWidget::~OpenGLVisualizationWidget()
{
	// Disconnect old camera
	if ( m_Camera )
		disconnect(m_Camera, SIGNAL(StatusChanged()), this, SLOT(Update()));
	m_Camera = NULL;

	if ( m_BackGroundTexture )
		glDeleteTextures(1, &m_BackGroundTexture);
}


//----------------------------------------------------------------------------
void
OpenGLVisualizationWidget::SetCamera(CameraPointer Camera)
{
	if ( m_Camera == Camera )
		return;

	// Disconnect old camera
	if ( m_Camera )
		disconnect(m_Camera, SIGNAL(StatusChanged()), this, SLOT(Update()));
		m_Camera->setParentWidget(NULL);

	// Set new camera and connect the signal
	m_Camera = Camera;
	m_Camera->setParentWidget(this);
	connect(m_Camera, SIGNAL(StatusChanged()), this, SLOT(Update()));

	m_Camera->SetRender3D(m_Render3D);
}


//----------------------------------------------------------------------------
OpenGLVisualizationWidget::CameraPointer 
OpenGLVisualizationWidget::GetCamera()
{
	return m_Camera;
}


//----------------------------------------------------------------------------
OpenGLVisualizationWidget::CameraConstPointer 
OpenGLVisualizationWidget::GetCamera() const
{
	return (CameraConstPointer)m_Camera;
}


//----------------------------------------------------------------------------
const OpenGLVisualizationWidget::EntityContainerType&
OpenGLVisualizationWidget::GetEntities() const
{
	return m_Entities;
}


//----------------------------------------------------------------------------
OpenGLVisualizationWidget::EntityContainerType&
OpenGLVisualizationWidget::GetEntities()
{
	return m_Entities;
}



//----------------------------------------------------------------------------
bool
OpenGLVisualizationWidget::AddEntity(OpenGLEntity *e, bool lock)
{
	e->SetRender3D(m_Render3D);

	if(lock)
		m_Mutex.lock();

	// Sanity check
	for ( EntityContainerType::iterator it = m_Entities.begin(); it != m_Entities.end(); ++it )
	{
		if ( *it == e )
		{
			if(lock)
				m_Mutex.unlock();
			return false;
		}
	}

	e->setParentWidget(this);

	// Add the widget and connect it properly
	m_Entities.push_back(e);

	if(lock)
		m_Mutex.unlock();

	connect(e,	SIGNAL(StatusChanged()), this, SLOT(Update()));

	return true;
}


//----------------------------------------------------------------------------
bool
OpenGLVisualizationWidget::RemoveEntity(OpenGLEntity *e, bool lock)
{
	if(lock)
		m_Mutex.lock();

	

	// Search the list for the entity and remove it
	for ( EntityContainerType::iterator it = m_Entities.begin(); it != m_Entities.end(); ++it )
	{
		if ( *it == e )
		{
			disconnect(e,	SIGNAL(StatusChanged()),	this,	SLOT(Update()));
			e->setParentWidget(NULL);
			m_Entities.erase(it);

			if ( m_PrimaryEntity == e )
			{
				m_PrimaryEntity = NULL;
			}

			if(lock)
				m_Mutex.unlock();
			return true;
		}
	}

	if(lock)
		m_Mutex.unlock();
	
	return false;
}


//----------------------------------------------------------------------------
void
OpenGLVisualizationWidget::Update()
{
	//std::cout << "update ... ";
	//wglSwapIntervalEXT(0);
	//glFinish();
	this->updateGL();
	//std::cout << "done." << std::endl;
}


//----------------------------------------------------------------------------
void 
OpenGLVisualizationWidget::ResetCamera()
{
	// Lock the mutex
	m_Mutex.lock();

	// Sanity checks
	if ( !m_Camera )
	{
		m_Mutex.unlock();
		return;
	}
	if ( m_Entities.size() == 0 )
	{
		m_Mutex.unlock();
		return;
	}

	if(m_Render3D)
	{
		// Initialize bounding box
		OpenGLEntity::BoundingBoxType JointBB;
		JointBB[0] = 1e16;
		JointBB[1] = 1e16;
		JointBB[2] = 1e16;
		JointBB[3] = -1e16;
		JointBB[4] = -1e16;
		JointBB[5] = -1e16;
		
		// Loop over our entities and compute the joint bounding box
		for ( EntityContainerType::iterator it = m_Entities.begin(); it != m_Entities.end(); ++it )
		{
			// Get the entity's BB
			OpenGLEntity::BoundingBoxType BB = (*it)->GetBoundingBox();
			
			// Sanity check
			if ( BB.GetNorm() < 1e-3 )
				continue;
	
			// Update the joint BB
			if ( BB[0] < JointBB[0]  )
				JointBB[0] = BB[0];
			if ( BB[1] < JointBB[1]  )
				JointBB[1] = BB[1];
			if ( BB[2] < JointBB[2]  )
				JointBB[2] = BB[2];
			if ( BB[3] > JointBB[3]  )
				JointBB[3] = BB[3];
			if ( BB[4] > JointBB[4]  )
				JointBB[4] = BB[4];
			if ( BB[5] > JointBB[5]  )
				JointBB[5] = BB[5];
		}

		// Unlock
		m_Mutex.unlock();
	
		// Comput the bounding sphere that encapsulates the whole scene
		OpenGLCamera::Vec4 BoundingSphere;
		BoundingSphere[0] = (JointBB[0] + JointBB[3])/2.f;
		BoundingSphere[1] = (JointBB[1] + JointBB[4])/2.f;
		BoundingSphere[2] = (JointBB[2] + JointBB[5])/2.f;
		float w1 = JointBB[3] - BoundingSphere[0];
		float w2 = JointBB[4] - BoundingSphere[1];
		float w3 = JointBB[5] - BoundingSphere[2];
		w1 *= w1;
		w2 *= w2;
		w3 *= w3;
		float Radius = w1 + w2 + w3;
		BoundingSphere[3] = ( Radius == 0 ) ? 1.f : sqrt(Radius);
	
		// Final check
		if ( abs(BoundingSphere[3]) < 1e-10 )
		{
			return;
		}
	
		// Reset the camera (this will also update the OpenGL part). Note that this is NOT thread-safe...
		m_Camera->ResetCamera3D(BoundingSphere);
	}
	else
	{		
		// Unlock
		m_Mutex.unlock();
		m_Camera->ResetCamera2D();
	}
}


//----------------------------------------------------------------------------
void
OpenGLVisualizationWidget::initializeGL()
{
	// Std OpenGL
	glClearColor(0.0, 0.0, 0.0, 0.0);
	glEnable(GL_DEPTH_TEST);

	CHECK_GL_ERROR();

	// We're initialized right now. Update the flag
	m_InitFlag = true;

	glEnable(GL_TEXTURE_2D);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	m_BackGroundTexture = bindTexture(QImage(":/ritk/Resources/ritk.png"));
	//glDisable(GL_TEXTURE_2D);
}


//----------------------------------------------------------------------------
void
OpenGLVisualizationWidget::closeGL()
{
}


//----------------------------------------------------------------------------
void
OpenGLVisualizationWidget::resizeGL(int width, int height)
{
	m_Mutex.lock();

	// Reset The Current Viewport
	glViewport(0, 0, (GLsizei)(width), (GLsizei)(height));

	// Update members
	m_Width = width;
	m_Height = height;

	OpenGLCamera::Vec2 ViewPort;
	ViewPort[0] = m_Width;
	ViewPort[1] = m_Height;

	m_Mutex.unlock();

	// This will also update the OpenGL part
	m_Camera->SetViewPort(ViewPort);
}


//----------------------------------------------------------------------------
void
OpenGLVisualizationWidget::paintGL()
{
	// Lock
	m_Mutex.lock();


	QVector2D payloadDimensions(1.0f, 1.0f);
	
	// Std OpenGL
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	
	if(m_Render3D)
	{
		/*GLfloat mat_specular[] = { 10.0, 1.0, 1.0, 1.0 };
		GLfloat mat_shininess[] = { 50.0 };*/
	
		GLfloat light_ambient[] = { 1.0, 1.0, 1.0, 1.0 };
		GLfloat light_diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
		GLfloat light_specular[] = { 1.0, 1.0, 1.0, 1.0 };
	
		GLfloat light_position[] = { 1.0, 1.0, 1.0, 1.0 };

		/*glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
		glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);*/

		glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
		glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
		glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);
		glLightfv(GL_LIGHT0, GL_POSITION, light_position);
		glLightfv(GL_LIGHT0, GL_POSITION, light_position);

		glClearColor (0.0, 0.0, 0.0, 0.0);
		glShadeModel (GL_SMOOTH);

		glEnable(GL_LIGHTING);
		glEnable(GL_LIGHT0);

		// Camera
		m_Camera->Render3D(this);
	
	}
	else
	{
		// Calculate the Dimension of the biggest Payload
		for ( EntityContainerType::iterator it = m_Entities.begin(); it != m_Entities.end(); ++it )
		{
			QVector2D newDims = (*it)->GetPayloadDimensions();
			if(newDims.x() > payloadDimensions.x()) payloadDimensions.setX(newDims.x());
			if(newDims.y() > payloadDimensions.y()) payloadDimensions.setY(newDims.y());
		}
		
		m_Camera->Render2D(this, payloadDimensions);
	
	}

	//==========================================
	//Gradient background
#if 0
	glMatrixMode(GL_MODELVIEW); 
	glPushMatrix(); 
	glLoadIdentity();
	glMatrixMode(GL_PROJECTION);
	glPushMatrix(); 
	glLoadIdentity();

	glDepthMask(GL_FALSE);
	glDisable(GL_LIGHTING);
	glDisable(GL_DEPTH_TEST);

	glBegin (GL_QUADS);
		//glColor3f(0.8,0.8,0.8);
		glColor3f(0.7,0.7,0.7);
		//glColor3f(1.0,1.0,1.0);
		glVertex3f(-1.0f, -1.0f, -1.0f); 
		glVertex3f(1.0f, -1.0f, -1.0f); 

		//glColor3f(0.4,0.4,0.4);
		glColor3f(0.3,0.3,0.3);
		//glColor3f(1.0,1.0,1.0);
		glVertex3f(1.0f, 1.0f, -1.0f); 
		glVertex3f(-1.0f, 1.0f, -1.0f); 
	glEnd ();
	
	glDepthMask(GL_TRUE);
	glEnable(GL_LIGHTING);
	glEnable(GL_DEPTH_TEST);
	glPopMatrix (); 
	glMatrixMode (GL_MODELVIEW); 
	glPopMatrix ();
#endif
	//================================

	//==========================================
#if 0
	// Texture background
	glMatrixMode(GL_MODELVIEW); 
	glPushMatrix(); 
	glLoadIdentity();
	glMatrixMode(GL_PROJECTION);
	glPushMatrix(); 
	glLoadIdentity();
	glMatrixMode(GL_TEXTURE);
	glPushMatrix(); 
	glLoadIdentity();

	glDepthMask(GL_FALSE);
	glDisable(GL_LIGHTING);
	glDisable(GL_DEPTH_TEST);

    
	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, m_BackGroundTexture);
	glBegin(GL_QUADS);
		glColor3f(1.0f,1.0f,1.0f);
		//glColor3f(1.0f,0.0f,0.0f);
		glTexCoord2f(0.0f, 0.0f); glVertex2f(-0.6f, -0.6f);  // Bottom Left Of The Texture and Quad
		//glColor3f(0.0f,1.0f,0.0f);
		glTexCoord2f(1.0f, 0.0f); glVertex2f(0.6f, -0.6f);  // Bottom Right Of The Texture and Quad
		//glColor3f(0.0f,0.0f,1.0f);
		glTexCoord2f(1.0f, 1.0f); glVertex2f(0.6f,  0.6f);  // Top Right Of The Texture and Quad
		//glColor3f(1.0f,1.0f,1.0f);
		glTexCoord2f(0.0f, 1.0f); glVertex2f(-0.6f,  0.6f);
		glEnd();
	//glDisable(GL_TEXTURE_2D);

	glDepthMask(GL_TRUE);
	glEnable(GL_LIGHTING);
	glEnable(GL_DEPTH_TEST);
	glPopMatrix();
	glMatrixMode(GL_PROJECTION); 
	glPopMatrix(); 
	glMatrixMode(GL_MODELVIEW); 
	glPopMatrix ();

#endif
	
	// Drawing mode
	if ( m_DrawWire && m_Render3D)
		glPolygonMode ( GL_FRONT_AND_BACK, GL_LINE ) ;
	else
		glPolygonMode ( GL_FRONT_AND_BACK, GL_FILL ) ;

//this->renderText(50,100,"test");

	// Render all entities
	for ( EntityContainerType::iterator it = m_Entities.begin(); it != m_Entities.end(); ++it )
	{
		if(m_Render3D)
			(*it)->Render3D(this);
		else
			(*it)->Render2D(this, payloadDimensions);
	}

	// Check for GL errors
	CHECK_GL_ERROR();

	// Don't forget to unlock
	m_Mutex.unlock();
}


//----------------------------------------------------------------------------
void 
OpenGLVisualizationWidget::mousePressEvent(QMouseEvent *e)
{
	if ( e->buttons() & Qt::RightButton )
	{
		bool reacted = false;
		// Search the list for the entity and remove it
		for ( EntityContainerType::iterator it = m_Entities.begin(); it != m_Entities.end(); ++it )
		{
			if((*it)->RightMousePressed(e))
				reacted = true;
		}
		if(reacted)
			this->Update();
	}
	
	m_LastMousePos = e->pos();
}

//----------------------------------------------------------------------------
void 
OpenGLVisualizationWidget::mouseReleaseEvent(QMouseEvent *e)
{
	if ( e->button() == Qt::RightButton )
	{
		bool reacted = false;
		
		for ( EntityContainerType::iterator it = m_Entities.begin(); it != m_Entities.end(); ++it )
		{
			if((*it)->RightMouseReleased(e))
				reacted = true;
		}
		if(reacted)
			this->Update();
	}
	
	m_LastMousePos = e->pos();
}

//----------------------------------------------------------------------------
void 
OpenGLVisualizationWidget::mouseMoveEvent(QMouseEvent *e)
{
	

	// Send MouseMoved to all Entities
	bool reacted = false;
	for ( EntityContainerType::iterator it = m_Entities.begin(); it != m_Entities.end(); ++it )
	{
		if((*it)->MouseMoved(e))
			reacted = true;
	}
	if(reacted)
		this->Update();

	

	int dx = e->x() - m_LastMousePos.x();
	int dy = e->y() - m_LastMousePos.y();

	if ( e->modifiers() & Qt::ShiftModifier )
	{
		if ( e->buttons() & Qt::LeftButton ) 
		{
			OpenGLCamera::Vec2 d;
			d[0] = dx;
			d[1] = dy;
			if(m_Render3D)
				m_Camera->Translate(d);
			else
				m_Camera->Rotate(d);
		}
	}
	else
	{
		if ( e->buttons() & Qt::LeftButton ) 
		{
			OpenGLCamera::Vec2 d;
			d[0] = dx;
			d[1] = dy;
			if(m_Render3D)
				m_Camera->Rotate(d);
			else
				m_Camera->Translate(d);
		} 
	}

	m_LastMousePos = e->pos();
	
}


//----------------------------------------------------------------------------
void 
OpenGLVisualizationWidget::wheelEvent(QWheelEvent *e)
{
	float Delta = e->delta()/120.0f;
	m_Camera->Zoom(Delta, m_LastMousePos);

	e->accept();
}


//----------------------------------------------------------------------------
void 
OpenGLVisualizationWidget::keyPressEvent(QKeyEvent *e)
{
	if ( e->key() == Qt::Key_R )
	{
		ResetCamera();
		e->accept();
	}
	else if ( e->key() == Qt::Key_W )
	{
		if(m_Render3D)
		{
			m_DrawWire = !m_DrawWire;
			e->accept();
		}
		else
		{
			e->ignore();
		}
	}
	else if ( e->key() == Qt::Key_V )
	{
		SetRender3D(!m_Render3D);
		e->accept();
	}
	else
		e->ignore();
}


//----------------------------------------------------------------------------
void 
OpenGLVisualizationWidget::SetRender3D(bool render3D)
{
	m_Mutex.lock();
	if (m_RenderModeFixed){
		m_Mutex.unlock();
		LOG_DEB("Unable to change render3D to '" << (render3D?"True":"False") <<"' - rendermode is fixed.");
		return;
	}
	m_Render3D = render3D;
	m_Camera->SetRender3D(m_Render3D);
	for ( EntityContainerType::iterator it = m_Entities.begin(); it != m_Entities.end(); ++it )
	{
		(*it)->SetRender3D(m_Render3D);
	}
	m_Mutex.unlock();
	ResetCamera();
}

void
OpenGLVisualizationWidget::SetRenderModeFixed(bool fixed)
{
	m_Mutex.lock();
	
	m_RenderModeFixed = fixed;

	m_Mutex.unlock();
}


//----------------------------------------------------------------------------
bool
OpenGLVisualizationWidget::SetPrimaryEntity(OpenGLEntity *e){

/*	e->SetRender3D(m_Render3D);

	m_Mutex.lock();

	if(m_PrimaryEntity != NULL){
		m_Mutex.unlock();
		return false;	
	}


	// Sanity check
	if ( m_PrimaryEntity == e )
	{
		m_Mutex.unlock();
		return false;
	}
	for ( EntityContainerType::iterator it = m_Entities.begin(); it != m_Entities.end(); ++it )
	{
		if ( *it == e )
		{
			m_Mutex.unlock();
			return false;
		}
	}

	e->setParentWidget(this);

	// Add the widget and connect it properly
	m_PrimaryEntity = e;

	connect(e,	SIGNAL(StatusChanged()), this, SLOT(Update()));

	m_Mutex.unlock();

	
*/
	m_Mutex.lock();

	if(m_PrimaryEntity != NULL){
		m_Mutex.unlock();
		return false;	
	}

	bool res = AddEntity(e, false);

	if ( res == true )
	{
		m_PrimaryEntity = e;
	}

	m_Mutex.unlock();

	return res;
}


//----------------------------------------------------------------------------
bool
OpenGLVisualizationWidget::RemovePrimaryEntity(){

	m_Mutex.lock();

	// Search the list for the entity and remove it
	if(m_PrimaryEntity != NULL){

		//bool res = RemoveEntity(m_PrimaryEntity, false);
		RemoveEntity(m_PrimaryEntity, false);

		m_Mutex.unlock();
		return true;
	}

	m_Mutex.unlock();
	
	return false;

}

//----------------------------------------------------------------------------
const
OpenGLEntity *
OpenGLVisualizationWidget::GetPrimaryEntity(){
	return m_PrimaryEntity;
}


} // end namespace ritk
