#include "RISimulatorPlugin.h"

#include <itkImageRegionIterator.h>
#include <itkImageRegionIteratorWithIndex.h>
#include <vtkRendererCollection.h>
#include <QFileDialog>



// Euclidean distance between 2 3D points
#define DIST3D(x,y) sqrt((x[0]-y[0])*(x[0]-y[0]) + (x[1]-y[1])*(x[1]-y[1]) + (x[2]-y[2])*(x[2]-y[2]))

// Convert depth values
#define REAL_DEPTH( zBufferVal , zNear , zFar ) ( zNear + zBufferVal *(zFar - zNear) )
//#define REAL_DEPTH( zBufferVal , zNear , zFar ) ( zNear * zFar / (zBufferVal * (zNear - zFar ) + zFar ) )


RISimulatorPlugin::RISimulatorPlugin()
{
	// Create the widget
	m_Widget = new RISimulatorWidget();

	// Create scene dialog
	m_SceneDialog = new EditSceneDialog(m_Widget);
	//m_SceneDialog->m_VisualizationWidget3D->m_VTKWidget->GetRenderWindow()->SetSize(640,480);

	// Create the simulator and its inputs
	m_RISimulator = RISimulatorType::New();
	m_DepthImage = DepthImageType::New();
	m_RGBAImage = RGBAImageType::New();

	// Idle by default
	SetState(Idle);
	m_TargetFPS = 25;

	// Initialize timer
	m_Timer = new QTimer();		
	m_Timer->stop();
	//connect(m_Timer,									SIGNAL(timeout()),					this,				SLOT(RequestFrame())						);

	// Connect elements from the widget
	connect(m_Widget->m_EditSceneButton,				SIGNAL(clicked()),					this,				SLOT(EditScene())							);
	connect(m_Widget->m_PushButtonPause,				SIGNAL(clicked()),					this,				SLOT(Pause())								);
	connect(m_Widget->m_PushButtonPlayForward,			SIGNAL(clicked()),					this,				SLOT(Play())								);
	connect(m_Widget->m_PushButtonStepForward,			SIGNAL(clicked()),					this,				SLOT(RequestFrame())						);

	connect(m_Widget->m_EnableNoiseCheckBox,			SIGNAL(clicked()),					this,				SLOT(ToggleNoiseSimulation())				);
	connect(m_Widget->m_NoiseSigmaSlider,				SIGNAL(sliderMoved(int)),			this,				SLOT(SetNoiseVariance(int))					);
	connect(m_Widget->m_IncorporateAmpCheckBox,			SIGNAL(clicked()),					this,				SLOT(ToggleIncorporateAmplitudes())			);

	connect(m_Widget->m_SimulateReflectionCheckBox,		SIGNAL(clicked()),					this,				SLOT(ToggleReflectionSimulation())			);
	connect(m_Widget->m_ReflectionFrequencyComboBox,	SIGNAL(currentIndexChanged(int)),	this,				SLOT(ReflectionFrequencyChanged(int))		);
	connect(m_Widget->m_ReflectionOctaveComboBox,		SIGNAL(currentIndexChanged(int)),	this,				SLOT(ReflectionOctaveChanged(int))			);
	connect(m_Widget->m_ReflectionPersistenceSpinBox,	SIGNAL(valueChanged(double)),		this,				SLOT(ReflectionPersistanceChanged(double))	);
	connect(m_Widget->m_LoadPerlinMaskPushButton,	    SIGNAL(clicked()),		            this,				SLOT(LoadPerlinMaskPushButton())	        );
	connect(m_Widget->m_UnloadPerlinMaskPushButton,	    SIGNAL(clicked()),		            this,				SLOT(UnloadPerlinMaskPushButton())	        );

	// Connect the UpdateGUI mechanism
	connect(this,										SIGNAL(UpdateGUI()),				m_Widget,			SLOT(UpdateGUI())							);
	connect(m_Widget->m_TargetFrameRateSpinBox,			SIGNAL(valueChanged(int)),			this,				SLOT(SetFPS(int))							);

	// Initialize the noise mode and the noise sigma
	ToggleNoiseSimulation();
	SetNoiseVariance(m_Widget->m_NoiseSigmaSlider->value());
	ToggleIncorporateAmplitudes();
}

RISimulatorPlugin::~RISimulatorPlugin()
{
	// Delete our widget. This should also delete the scene dialog
	if ( m_Widget )
		delete m_Widget;

	if ( m_Timer )
		delete m_Timer;
}


//----------------------------------------------------------------------------
QWidget*
RISimulatorPlugin::GetPluginGUI()
{
	return m_Widget;
}


//----------------------------------------------------------------------------
RISimulatorPlugin::SourcePlugin::SourcePointer
RISimulatorPlugin::GetSource()
{
	return (SourceType*)m_RISimulator;
}


//----------------------------------------------------------------------------
void
RISimulatorPlugin::SetState(StateType State)
{
	// Lock
	{
		QMutexLocker locker(&m_StateMutex);
		m_State = State;

		// Update widget
		if ( m_Widget )
		{
			switch ( m_State )
			{
			case Idle:
				m_Widget->m_PushButtonPause->setEnabled(false);
				m_Widget->m_PushButtonPlayForward->setEnabled(true);
				m_Widget->m_PushButtonStepForward->setEnabled(true);
				break;
			case Playing:
				m_Widget->m_PushButtonPause->setEnabled(true);
				m_Widget->m_PushButtonPlayForward->setEnabled(false);
				m_Widget->m_PushButtonStepForward->setEnabled(false);
				break;
			}
			emit UpdateGUI();
		}
	}
}


//----------------------------------------------------------------------------
void
RISimulatorPlugin::EditScene()
{
	m_SceneDialog->setWindowModality(Qt::NonModal);
	m_SceneDialog->show();
	m_SceneDialog->activateWindow();
}


//----------------------------------------------------------------------------
void
RISimulatorPlugin::Play()
{
	SetState(Playing);
	/*double d = 1000.0/m_TargetFPS;
	m_Timer->start(d);*/

	// Connecting to the pipeline's new frame signal will automaticall trigger a frame request
	connect(m_AcquisitionPipeline, SIGNAL(NewFrame()), this, SLOT(RequestFrame()) );

	// One initial request mandatory. This will eventually trigger the source
	RequestFrame();
}


//----------------------------------------------------------------------------
void
RISimulatorPlugin::Pause()
{
	//m_Timer->stop();

	disconnect(m_AcquisitionPipeline, SIGNAL(NewFrame()), this, SLOT(RequestFrame()) );
	SetState(Idle);
}


//----------------------------------------------------------------------------
void
RISimulatorPlugin::SetFPS(int FPS)
{
	m_TargetFPS = FPS;

	// Lock
	{
		//QMutexLocker locker(&m_StateMutex);

		switch ( m_State )
		{
			case Idle:
				break;
			case Playing:
				Pause();
				Play();
				break;
		}
	}

}


//----------------------------------------------------------------------------
void
RISimulatorPlugin::RequestFrame()
{
	// Get the view frustum parameters
	double ClippingPlanes[2];
	m_SceneDialog->m_VisualizationWidget3D->GetRenderWindow()->GetRenderers()->GetFirstRenderer()->GetActiveCamera()->GetClippingRange(ClippingPlanes[0],ClippingPlanes[1]);
	double CameraPosition[3];
	m_SceneDialog->m_VisualizationWidget3D->GetRenderWindow()->GetRenderers()->GetFirstRenderer()->GetActiveCamera()->GetPosition(CameraPosition[0],CameraPosition[1],CameraPosition[2]);
	double FocalPoint[3];
	m_SceneDialog->m_VisualizationWidget3D->GetRenderWindow()->GetRenderers()->GetFirstRenderer()->GetActiveCamera()->GetFocalPoint(FocalPoint[0],FocalPoint[1],FocalPoint[2]);
	double ViewAngle = m_SceneDialog->m_VisualizationWidget3D->GetRenderWindow()->GetRenderers()->GetFirstRenderer()->GetActiveCamera()->GetViewAngle();

	// The region is the same for the depth and amplitude image: The entire render window
	SizeType Size;
	Size[0] = m_SceneDialog->m_VisualizationWidget3D->GetRenderWindow()->GetSize()[0];
	Size[1] = m_SceneDialog->m_VisualizationWidget3D->GetRenderWindow()->GetSize()[1];
	
	// If the region changed we have to re-allocate our buffers
	if ( m_CurrentSize != Size )
	{
		m_DepthImage->SetRegions(Size);
		m_DepthImage->Allocate();
		m_RGBAImage->SetRegions(Size);
		m_RGBAImage->Allocate();
		m_CurrentSize = Size;
	}

	// Lock the widget
	m_SceneDialog->m_VisualizationWidget3D->m_VTKWidget->Lock();

	// Get the depth buffer data
	m_SceneDialog->m_VisualizationWidget3D->GetRenderWindow()->GetZbufferData(0,0,Size[0]-1,Size[1]-1,m_DepthImage->GetBufferPointer());
	m_DepthImage->Modified();
	
	// Get the color data from the render window
	// TODO: Check GetRGBAPixelData blend parameter, maybe this is graphic card specific (?)
	float *tmp = m_SceneDialog->m_VisualizationWidget3D->GetRenderWindow()->GetRGBAPixelData(0,0,Size[0]-1,Size[1]-1,0);
	memcpy(m_RGBAImage->GetBufferPointer(), tmp, Size[0]*Size[1]*sizeof(float)*4);
	m_RGBAImage->Modified();
	delete tmp;

	// Unlock the widget
	m_SceneDialog->m_VisualizationWidget3D->m_VTKWidget->Unlock();

	// Set the data to the simulator (we're using RGBAs)
	m_RISimulator->Lock();
	m_RISimulator->SetViewFrustumParameters(CameraPosition,FocalPoint,ClippingPlanes, ViewAngle);
	m_RISimulator->SetDepthImageInput(m_DepthImage);
	m_RISimulator->SetRGBAImageInput(m_RGBAImage);
	m_RISimulator->Unlock();


	// Lock the player as some members may be changed from outside (especially when the player is stopped)
	LockPlugin();

	// If we're not started (propably stopped from outside) hide the scene dialog and stop time (if running). Otherwise execute the pipeline
	if ( !Started() )
	{
		m_SceneDialog->hide();
		Pause();
	}
	else
	{
		// Trigger pipeline execution (other thread)
		m_AcquisitionPipeline->ExecuteOnce();
	}

	// Done here, unlock
	UnlockPlugin();
}


//----------------------------------------------------------------------------
void
RISimulatorPlugin::ToggleNoiseSimulation()
{
	LockPlugin();

	if ( m_Widget->m_EnableNoiseCheckBox->isChecked() )
	{
		m_Widget->m_NoiseSigmaSlider->setEnabled(true);
		m_Widget->m_IncorporateAmpCheckBox->setEnabled(true);
	}
	else
	{
		m_Widget->m_NoiseSigmaSlider->setEnabled(false);
		m_Widget->m_IncorporateAmpCheckBox->setEnabled(false);
	}

	// We may not have the pipeline yet
	if ( m_AcquisitionPipeline )
		this->m_AcquisitionPipeline->Lock();
	m_RISimulator->SetNoiseSimulation(m_Widget->m_EnableNoiseCheckBox->isChecked());
	if ( m_AcquisitionPipeline )
		this->m_AcquisitionPipeline->Unlock();

	UnlockPlugin();
}


//----------------------------------------------------------------------------
void
RISimulatorPlugin::SetNoiseVariance(int value)
{
	LockPlugin();

	float scale = 100*20; // = max 50 mm

	// We may not have the pipeline yet
	if ( m_AcquisitionPipeline )
		this->m_AcquisitionPipeline->Lock();
	m_RISimulator->SetNoiseVariance(value/scale);
	if ( m_AcquisitionPipeline )
		this->m_AcquisitionPipeline->Unlock();

	m_Widget->m_NoiseSigmaLabel->setText(QString::number(value*1000/scale)+QString(" mm"));

	UnlockPlugin();
}


//----------------------------------------------------------------------------
void
RISimulatorPlugin::ToggleIncorporateAmplitudes()
{
	// We may not have the pipeline yet
	if ( m_AcquisitionPipeline )
		this->m_AcquisitionPipeline->Lock();
	m_RISimulator->IncorporateAmplitudesIntoNoiseComputation(m_Widget->m_IncorporateAmpCheckBox->isChecked());
	if ( m_AcquisitionPipeline )
		this->m_AcquisitionPipeline->Unlock();
}


//----------------------------------------------------------------------------
void
RISimulatorPlugin::NewFrame()
{
}


//----------------------------------------------------------------------------
void
RISimulatorPlugin::ToggleReflectionSimulation()
{
	LockPlugin();

	if ( m_Widget->m_SimulateReflectionCheckBox->isChecked() )
	{
		m_Widget->m_ReflectionFrequencyComboBox->setEnabled(true);
		m_Widget->m_ReflectionOctaveComboBox->setEnabled(true);
		m_Widget->m_ReflectionPersistenceSpinBox->setEnabled(true);
		if (m_RISimulator->GetPerlinMaskLoaded() == false)
			m_Widget->m_LoadPerlinMaskPushButton->setEnabled(true);
		else
			m_Widget->m_UnloadPerlinMaskPushButton->setEnabled(true);
	}
	else 
	{
		m_Widget->m_ReflectionFrequencyComboBox->setEnabled(false);
		m_Widget->m_ReflectionOctaveComboBox->setEnabled(false);
		m_Widget->m_ReflectionPersistenceSpinBox->setEnabled(false);
		m_Widget->m_LoadPerlinMaskPushButton->setEnabled(false);
		m_Widget->m_UnloadPerlinMaskPushButton->setEnabled(false);
	}

	// We may not have the pipeline yet
	if ( m_AcquisitionPipeline )
		this->m_AcquisitionPipeline->Lock();
	m_RISimulator->SetSpecularReflectionSimulation(m_Widget->m_SimulateReflectionCheckBox->isChecked());
	if ( m_AcquisitionPipeline )
		this->m_AcquisitionPipeline->Unlock();

	UnlockPlugin();
}


//----------------------------------------------------------------------------
void
RISimulatorPlugin::ReflectionFrequencyChanged(int Index)
{
	LockPlugin();

	// We may not have the pipeline yet
	if ( m_AcquisitionPipeline )
		this->m_AcquisitionPipeline->Lock();
	m_RISimulator->SetSpecularReflectionFrequency(Index+1);
	if ( m_AcquisitionPipeline )
		this->m_AcquisitionPipeline->Unlock();

	UnlockPlugin();
}


//----------------------------------------------------------------------------
void
RISimulatorPlugin::ReflectionOctaveChanged(int Index)
{
	LockPlugin();

	// We may not have the pipeline yet
	if ( m_AcquisitionPipeline )
		this->m_AcquisitionPipeline->Lock();
	m_RISimulator->SetSpecularReflectionOctave(Index+2);
	if ( m_AcquisitionPipeline )
		this->m_AcquisitionPipeline->Unlock();

	UnlockPlugin();
}


//----------------------------------------------------------------------------
void
RISimulatorPlugin::ReflectionPersistanceChanged(double d)
{
	LockPlugin();

	// We may not have the pipeline yet
	if ( m_AcquisitionPipeline )
		this->m_AcquisitionPipeline->Lock();
	m_RISimulator->SetSpecularReflectionPersistence(d/100.l);
	if ( m_AcquisitionPipeline )
		this->m_AcquisitionPipeline->Unlock();

	UnlockPlugin();
}


//----------------------------------------------------------------------------
void
RISimulatorPlugin::SetParameter(QString Name, QString Value)
{
	if ( !strcmp(Name.toStdString().c_str(), "DataFile") )
	{
		m_SceneDialog->AddActor(Value);
	}
	SetState(Idle);
}

//----------------------------------------------------------------------------
void
RISimulatorPlugin::LoadPerlinMaskPushButton()
{
	QString FileName = QFileDialog::getOpenFileName(m_Widget,"Load a mask image for the Perlin noise","C:\\Program Files\\RITK\\Data","PNG Graphics (*.png)");

	LockPlugin();
	// We may not have the pipeline yet
	if ( m_AcquisitionPipeline )
		this->m_AcquisitionPipeline->Lock();
	if (m_RISimulator->LoadPerlinMask(FileName)) // Load and check  for success
	{
		m_Widget->m_LoadPerlinMaskPushButton->setEnabled(false);
		m_Widget->m_UnloadPerlinMaskPushButton->setEnabled(true);
	}
	if ( m_AcquisitionPipeline )
		this->m_AcquisitionPipeline->Unlock();

	UnlockPlugin();
}

//----------------------------------------------------------------------------
void
RISimulatorPlugin::UnloadPerlinMaskPushButton()
{
	LockPlugin();
	// We may not have the pipeline yet
	if ( m_AcquisitionPipeline )
		this->m_AcquisitionPipeline->Lock();
	m_RISimulator->UnloadPerlinMask();
	m_Widget->m_LoadPerlinMaskPushButton->setEnabled(true);
	m_Widget->m_UnloadPerlinMaskPushButton->setEnabled(false);
	if ( m_AcquisitionPipeline )
		this->m_AcquisitionPipeline->Unlock();

	UnlockPlugin();
}
