#include "EditSceneDialog.h"
#include "ritkActorPipeline.h"
#include "ritkManager.h"
#include <vtkOBJReader.h>
#include <vtkSphereSource.h>
#include <vtkConeSource.h>
#include <vtkCubeSource.h>
#include <vtkRendererCollection.h>
#include <vtkPolyDataReader.h>
#include <vtkPolyDataWriter.h>
#include <vtkInteractorStyleTrackballActor.h>
#include <vtkInteractorStyleTrackballCamera.h>
#include <vtkCameraActor.h>
#include <vtkTransform.h>
#include <vtkTransformPolyDataFilter.h>
#include <vtkCamera.h>
#include <vtkLight.h>
#include <vtkProcessObject.h>
#include <vtkCallbackCommand.h>
#include <vtkCommand.h>

#include <vtkJPEGReader.h>
#include <vtkTexture.h>
#include <QFileDialog>



//----------------------------------------------------------------------------
EditSceneDialog::EditSceneDialog(QWidget* parent) :
	QDialog(parent)
{
	setupUi(this);

	// Memers
	m_MessageBox = NULL;

	// Camera mode by default
	SetViewMode();

#if 0
	// Set up camera
	m_VisualizationWidget3D->GetRenderWindow()->GetRenderers()->GetFirstRenderer()->ResetCamera();
	m_VisualizationWidget3D->GetRenderWindow()->GetRenderers()->GetFirstRenderer()->GetActiveCamera()->SetPosition(0,0,0);
	m_VisualizationWidget3D->GetRenderWindow()->GetRenderers()->GetFirstRenderer()->GetActiveCamera()->SetFocalPoint(0,0,1500);
	m_VisualizationWidget3D->GetRenderWindow()->GetRenderers()->GetFirstRenderer()->GetActiveCamera()->SetClippingRange(1000,3000);

	// Add a camera actor
	vtkSmartPointer<vtkCameraActor> CameraActor = vtkSmartPointer<vtkCameraActor>::New();
	CameraActor->SetCamera(m_VisualizationWidget3D->GetRenderWindow()->GetRenderers()->GetFirstRenderer()->GetActiveCamera());
	m_VisualizationWidget3D->GetRenderWindow()->GetRenderers()->GetFirstRenderer()->AddActor(CameraActor);
#endif


	// Add an actor
	/*vtkSmartPointer<ActorPipeline> Actor = vtkSmartPointer<ActorPipeline>::New();
	vtkSmartPointer<vtkSphereSource> SphereSource = vtkSmartPointer<vtkSphereSource>::New();
	SphereSource->SetCenter(0,0,1500);
	SphereSource->SetRadius(100);
	SphereSource->SetThetaResolution(20);
	SphereSource->SetPhiResolution(20);
	Actor->SetData(SphereSource->GetOutput());
	m_VisualizationWidget3D->AddActor(Actor);*/

	// Add an actor
	vtkSmartPointer<ritk::ActorPipeline> Actor = vtkSmartPointer<ritk::ActorPipeline>::New();
	vtkSmartPointer<vtkConeSource> ConeSource = vtkSmartPointer<vtkConeSource>::New();
	ConeSource->SetCenter(0,0,1500);
	ConeSource->SetRadius(50);
	ConeSource->SetResolution(20);
	ConeSource->SetHeight(200);
	Actor->SetData(ConeSource->GetOutput());
	//m_VisualizationWidget3D->AddActor(Actor);

	vtkSmartPointer<ritk::ActorPipeline> Actor2 = vtkSmartPointer<ritk::ActorPipeline>::New();
	vtkSmartPointer<vtkCubeSource> CubeSource = vtkSmartPointer<vtkCubeSource>::New();
	CubeSource->SetCenter(-150,0,1500);
	CubeSource->SetXLength(200);
	CubeSource->SetYLength(400);
	CubeSource->SetZLength(300);
	Actor2->SetData(CubeSource->GetOutput());
	//m_VisualizationWidget3D->AddActor(Actor2);

	m_VisualizationWidget3D->GetRenderWindow()->GetRenderers()->GetFirstRenderer()->ResetCamera();



	// Connect signals
	connect(m_MovePushButton,				SIGNAL(clicked()),			this,	SLOT(MoveCam())					);
	connect(m_LoadActorPushButton,			SIGNAL(clicked()),			this,	SLOT(AddActor())				);
	connect(m_RemoveActorPushButton,		SIGNAL(clicked()),			this,	SLOT(RemoveActor())				);
	connect(m_CameraRadioButton,			SIGNAL(clicked()),			this,	SLOT(SetViewMode())				);
	connect(m_ManipulationRadioButton,		SIGNAL(clicked()),			this,	SLOT(SetManipulationMode())		);

	connect(m_LoadModelPushButton,			SIGNAL(clicked()),			this,	SLOT(LoadModel())				);
	connect(m_MOVSlider1,					SIGNAL(valueChanged(int)),	this,	SLOT(SetMOV1Weight(int))		);
	connect(m_MOVSlider2,					SIGNAL(valueChanged(int)),	this,	SLOT(SetMOV2Weight(int))		);
	connect(m_MOVSlider3,					SIGNAL(valueChanged(int)),	this,	SLOT(SetMOV3Weight(int))		);
	connect(m_MOVSlider4,					SIGNAL(valueChanged(int)),	this,	SLOT(SetMOV4Weight(int))		);
	connect(m_MOVSlider5,					SIGNAL(valueChanged(int)),	this,	SLOT(SetMOV5Weight(int))		);

	// Default state
	m_ASMGroupBox->setVisible(false);

	// Invalid model
	m_StatisticalModel = NULL;
	m_ModelVTK = NULL;
	m_ModelActor.first = NULL;
	m_ModelActor.second = NULL;
}


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


//----------------------------------------------------------------------------
void
EditSceneDialog::SetViewMode()
{
	// Set the interactor style
	vtkSmartPointer<vtkInteractorStyleTrackballCamera> style = vtkSmartPointer<vtkInteractorStyleTrackballCamera>::New();
	//style->SetAutoAdjustCameraClippingRange(0);
	m_VisualizationWidget3D->GetRenderWindow()->GetInteractor()->SetInteractorStyle(style);
}


//----------------------------------------------------------------------------
void
EditSceneDialog::SetManipulationMode()
{
	// Set the interactor style
	vtkSmartPointer<vtkInteractorStyleTrackballActor> style = vtkSmartPointer<vtkInteractorStyleTrackballActor>::New();
	//style->SetAutoAdjustCameraClippingRange(0);
	m_VisualizationWidget3D->GetRenderWindow()->GetInteractor()->SetInteractorStyle(style);
}


//----------------------------------------------------------------------------
void
EditSceneDialog::LoadModel()
{
	// Get the directory that contains the model
	QString DirectoryName = QFileDialog::getExistingDirectory(this, "Set input directory", "D:/Data/");
	//QString DirectoryName = tr("D:/Data/StatisticalModels/Dominik/PCA");

	// Sanity check if the required files exist in this directory
	QFile ModelFile(DirectoryName + tr("/Model.sm"));
	QFile VTKFile(DirectoryName + tr("/Model.vtk"));
	if ( !ModelFile.exists() || !VTKFile.exists() )
	{
		QMessageBox Error(this);
		Error.setIcon(QMessageBox::Critical);
		Error.setText(tr("Error loading model..."));
		Error.setInformativeText(tr("Could not find <Model.sm> or <Model.vtk> in directory <")  + DirectoryName + tr(">"));
		Error.setStandardButtons(QMessageBox::Ok);
		Error.exec();
		return;
	}

	// Load the model from file
	SMType::Pointer Model = SMType::New();
	Model->SetFileName((DirectoryName + tr("/Model.sm")).toStdString());
	Model->Update();

	// Load the vtk data from file
	vtkSmartPointer<vtkPolyDataReader> Reader = vtkSmartPointer<vtkPolyDataReader>::New();
	Reader->SetFileName((DirectoryName + tr("/Model.vtk")).toStdString().c_str());
	Reader->Update();
	vtkSmartPointer<vtkPolyData> VTKData = Reader->GetOutput();
	
	// Some sanity checks
	if ( Model->GetMeanVectorOutput()->Get().size()/3 != VTKData->GetNumberOfPoints() )
	{
		QMessageBox Error(this);
		Error.setIcon(QMessageBox::Critical);
		Error.setText(tr("Error loading model..."));
		Error.setInformativeText(tr("Point size mismatch detected..."));
		Error.setStandardButtons(QMessageBox::Ok);
		Error.exec();
		return;
	}
	else
	{
	}

	// Update our members
	m_StatisticalModel = Model;
	m_ModelVTK = VTKData;

	// Reset the model weights and GUI
	m_MOVWeights = itk::Array<double>(m_StatisticalModel->GetModesOfVariationOutput()->Get().size());
	m_MOVWeights.fill(0);
	m_MOVSlider1->setValue(0);
	m_MOVSlider2->setValue(0);
	m_MOVSlider3->setValue(0);
	m_MOVSlider4->setValue(0);
	m_MOVSlider5->setValue(0);

	// Dummy model
	/*vtkSmartPointer<vtkCubeSource> CubeSource = vtkSmartPointer<vtkCubeSource>::New();
	CubeSource->SetCenter(-150,0,1500);
	CubeSource->SetXLength(200);
	CubeSource->SetYLength(300);
	CubeSource->SetZLength(400);
	CubeSource->Update();
	m_ModelVTK = CubeSource->GetOutput();*/

	// Remove the old actor
	if ( m_ModelActor.first )
	{
		m_VisualizationWidget3D->RemoveActor(m_ModelActor.first);
	}

	// Add the actor to the render window
	m_ModelActor.first = vtkSmartPointer<vtkActor>::New();
	m_ModelActor.second = vtkSmartPointer<vtkPolyDataMapper>::New();
	m_ModelActor.first->SetMapper(m_ModelActor.second);
	m_ModelActor.second->SetInput(m_ModelVTK);
	m_VisualizationWidget3D->AddActor(m_ModelActor.first);


	m_VisualizationWidget3D->GetRenderWindow()->GetRenderers()->GetFirstRenderer()->ResetCamera();

	// Create polydata for our model and render
	RenderModel();
}


//----------------------------------------------------------------------------
void
EditSceneDialog::SetMOV1Weight(int i)
{
	if ( m_MOVWeights.size() < 1 )
		return;
	m_MOVWeights[0] = (i/100.0);
	RenderModel();
}


//----------------------------------------------------------------------------
void
EditSceneDialog::SetMOV2Weight(int i)
{
	if ( m_MOVWeights.size() < 2 )
		return;
	m_MOVWeights[1] = (i/100.0);
	RenderModel();
}


//----------------------------------------------------------------------------
void
EditSceneDialog::SetMOV3Weight(int i)
{
	if ( m_MOVWeights.size() < 3 )
		return;
	m_MOVWeights[2] = (i/100.0);
	RenderModel();
}


//----------------------------------------------------------------------------
void
EditSceneDialog::SetMOV4Weight(int i)
{
	if ( m_MOVWeights.size() < 4 )
		return;
	m_MOVWeights[3] = (i/100.0);
	RenderModel();
}


//----------------------------------------------------------------------------
void
EditSceneDialog::SetMOV5Weight(int i)
{
	if ( m_MOVWeights.size() < 5 )
		return;
	m_MOVWeights[4] = (i/100.0);
	RenderModel();
}


//----------------------------------------------------------------------------
void
EditSceneDialog::RenderModel()
{
	// Get the mean vector, MOVs and their weights
	SMType::VectorType &Mean = m_StatisticalModel->GetMeanVectorOutput()->Get();
	SMType::VectorContainerType &MOVs = m_StatisticalModel->GetModesOfVariationOutput()->Get();
	SMType::VectorType &MOVWeights = m_StatisticalModel->GetModesOfVariationWeightsOutput()->Get();

	// Number of modes
	int NumModes = MOVs.size();

	// Compute the effective weight
	itk::Array<double> EffectiveWeights(NumModes);
	for ( unsigned int i = 0; i < NumModes; i++ )
		EffectiveWeights[i] =  m_MOVWeights[i]*3*sqrt(MOVWeights[i]);
	/*EffectiveWeights.fill(0);
	EffectiveWeights[0] = -3000;
	EffectiveWeights[1] = 1600;*/
	/*EffectiveWeights[0] = -2000;
	EffectiveWeights[1] = 900;*/
	std::cout << "Rendering with " << EffectiveWeights << std::endl;
	
	// Loop
	double Point1[3];
	//double Point2[3];
	vtkIdType n = m_ModelVTK->GetPoints()->GetNumberOfPoints();
	for ( vtkIdType i = 0; i < n; i++ )
	{
		// Compute the model instance
		for ( int dim = 0; dim < 3; dim++ )
		{
			Point1[dim] = Mean[i*3+dim];
			for ( unsigned int Mode = 0; Mode < MOVs.size(); Mode++ )
			{
				Point1[dim] += EffectiveWeights[Mode]*MOVs[Mode][i*3+dim];//  + (rand()/(double)RAND_MAX)/300.0;
			}
		}
		m_ModelVTK->GetPoints()->SetPoint(i,Point1);

		// Compute the displacement data
		/*m_DisplacementData->GetPoint(i,Point1);
		m_TransformedData->GetPoint(i, Point2);
		m_DisplacementData->GetPointData()->GetScalars("phiGlyph")->SetTuple3(i, Point2[0]-Point1[0], Point2[1]-Point1[1], Point2[2]-Point1[2]);*/
	}

	/*vtkSmartPointer<vtkPolyDataWriter> Writer = vtkSmartPointer<vtkPolyDataWriter>::New();
	Writer->SetInput(m_ModelVTK);
	Writer->SetFileName("D:/Temp/Cube.vtk");
	Writer->Update();*/

	vtkSmartPointer<vtkTransform> Transform = vtkSmartPointer<vtkTransform>::New();
	Transform->Translate(0, 0, 0);
	Transform->RotateX(183); // 180
	Transform->RotateZ(95); // 90
	Transform->RotateY(-65); // 65 backwards
	Transform->Update();
	vtkSmartPointer<vtkTransformPolyDataFilter> TransformFilter = vtkSmartPointer<vtkTransformPolyDataFilter>::New();
	TransformFilter->SetInput(m_ModelVTK);
	TransformFilter->SetTransform(Transform);
	TransformFilter->Update();
	m_ModelVTK = TransformFilter->GetOutput();



	// Update the render window
	m_ModelVTK->Modified();
	m_ModelActor.second->SetInput(m_ModelVTK);
	m_VisualizationWidget3D->UpdateGUI();
}


//----------------------------------------------------------------------------
void
EditSceneDialog::AddActor()
{
	// Get file name through dialog
	m_FileName = QFileDialog::getOpenFileName(this, "Open VTK poly data", QCoreApplication::applicationDirPath(), "VTK poly data (*.vtk)");		

	// Found a valid file name
	if ( !m_FileName.isEmpty() ) 
	{
		// Create the job and connect to slot
		LoadVTKDataJob *job = new LoadVTKDataJob(m_FileName);
		connect(
			job, SIGNAL(Finished(bool,vtkSmartPointer<vtkPolyData>,QString)),
			this,SLOT(LoadDataResult(bool,vtkSmartPointer<vtkPolyData>,QString)));

		// Add
		ritk::Manager::GetInstance().AddThreadedJob(job);

		// Show a message box
		m_MessageBox = new QMessageBox(this);
		m_MessageBox->setText("Importing VTK data from <" + m_FileName + ">...");
		m_MessageBox->setInformativeText("Depending on the data size this will take a few seconds!");
		m_MessageBox->setStandardButtons(QMessageBox::NoButton);
		m_MessageBox->setIcon(QMessageBox::Information);
		m_MessageBox->exec();
	}
}


//----------------------------------------------------------------------------
void
EditSceneDialog::MoveCam()
{
	m_ActorList[0]->AddPosition(0.05,0.05,0);
//	m_ActorList[0]->AddOrientation(0,0.1,0);
// If z changes -> change clipping planes

	m_VisualizationWidget3D->update();
	m_VisualizationWidget3D->GetRenderWindow()->Render();
}


//----------------------------------------------------------------------------
void
EditSceneDialog::AddActor(QString FileName)
{
	m_FileName = FileName;

	// Found a valid file name
	if ( !m_FileName.isEmpty() ) 
	{
		// Create the job and connect to slot
		LoadVTKDataJob *job = new LoadVTKDataJob(m_FileName);
		connect(
			job, SIGNAL(Finished(bool,vtkSmartPointer<vtkPolyData>,QString)),
			this,SLOT(LoadDataResult(bool,vtkSmartPointer<vtkPolyData>,QString)));

		// Add
		ritk::Manager::GetInstance().AddThreadedJob(job);

		// Show a message box
		m_MessageBox = new QMessageBox(this);
		m_MessageBox->setText("Importing VTK data from <" + m_FileName + ">...");
		m_MessageBox->setInformativeText("Depending on the data size this will take a few seconds!");
		m_MessageBox->setStandardButtons(QMessageBox::NoButton);
		m_MessageBox->setIcon(QMessageBox::Information);
		m_MessageBox->exec();
	}
}

//----------------------------------------------------------------------------
void
EditSceneDialog::RemoveActor()
{
	int row = listWidget->currentRow();

	// Remove actor from visulization
	m_VisualizationWidget3D->RemoveActor(m_ActorList[row]);

	// Remove from ListWidget
	QListWidgetItem* item = listWidget->takeItem(row);
	delete item;

	// Remove from internal list
	m_ActorList.erase(m_ActorList.begin()+row);

	if(listWidget->count() == 0)
		m_RemoveActorPushButton->setEnabled(false);
}


//----------------------------------------------------------------------------
void
EditSceneDialog::LoadDataResult(bool success, vtkSmartPointer<vtkPolyData> Data, QString message)
{
	// Show a message that the loading failed
	if ( !success )
	{
		if ( m_MessageBox )
			delete m_MessageBox;
		m_MessageBox = new QMessageBox(this);
		m_MessageBox->setIcon(QMessageBox::Critical);
		m_MessageBox->setText("Could not load data!");
		m_MessageBox->setInformativeText("An exception occured...");
		m_MessageBox->setStandardButtons(QMessageBox::Ok);
		m_MessageBox->exec();
		return;
	}

	// Add the actor to the render window
	vtkSmartPointer<ritk::ActorPipeline> Actor = vtkSmartPointer<ritk::ActorPipeline>::New();
	Actor->SetData(Data);

	// Texture
	QDir skinDir(QCoreApplication::applicationDirPath());
	QString skinFile = QFileDialog::getOpenFileName(this, "Open Texture file", QCoreApplication::applicationDirPath(), "Texture image (*.jpg)");		
	if(skinFile.isEmpty())
		skinFile = "Skin.jpg";

	if(skinDir.exists(skinFile))
	{
		vtkSmartPointer<vtkJPEGReader> jPEGReader = vtkSmartPointer<vtkJPEGReader>::New();
		jPEGReader->SetFileName(skinFile.toStdString().c_str());
		jPEGReader->Update();

		//apply the texture
		vtkSmartPointer<vtkTexture> texture = vtkSmartPointer<vtkTexture>::New();
		texture->SetInput(jPEGReader->GetOutput());
		
		Actor->SetTexture(texture);
	}
	else 
		LOG_DEB("Skin file for VTK Data not found: " << skinFile.toStdString().c_str());

	Actor->SetPickable(true);
	Actor->SetOrientation(90,0,0);

	m_VisualizationWidget3D->AddActor(Actor);
	m_VisualizationWidget3D->GetRenderWindow()->GetRenderers()->GetFirstRenderer()->ResetCamera();
	// Update the render window
	m_VisualizationWidget3D->GetRenderWindow()->Render();

	// Add to actor list:
	new QListWidgetItem(m_FileName, listWidget);
	m_ActorList.push_back(Actor);
	m_RemoveActorPushButton->setEnabled(true);

	// Clean up
	delete m_MessageBox;
	m_MessageBox = NULL;

}





//================//
// LoadVTKDataJob //
//================//

LoadVTKDataJob::LoadVTKDataJob(QString FileName)
{
	m_FileName = FileName;
	qRegisterMetaType<vtkSmartPointer<vtkPolyData> >("vtkSmartPointer<vtkPolyData>");
}


LoadVTKDataJob::~LoadVTKDataJob()
{
}


//----------------------------------------------------------------------------
void
LoadVTKDataJob::Execute()
{
	vtkSmartPointer<vtkPolyData> Result = NULL;
	try
	{
		// Read the poly data from file
		vtkSmartPointer<vtkPolyDataReader> Reader = vtkSmartPointer<vtkPolyDataReader>::New();
		Reader->SetFileName(m_FileName.toStdString().c_str());
		Reader->Update();


		// Transform such that the centroid of the data's boundingbox coincides with the focal point of the camera
		double FocalPoint[3];
		FocalPoint[0] = 0;
		FocalPoint[1] = 0;
		FocalPoint[2] = 1500;
		double *Bounds = Reader->GetOutput()->GetBounds();
		double Centroid[3];
		Centroid[0] = Bounds[0] + 0.5*(Bounds[1]-Bounds[0]);
		Centroid[1] = Bounds[2] + 0.5*(Bounds[3]-Bounds[2]);
		Centroid[2] = Bounds[4] + 0.5*(Bounds[5]-Bounds[4]);
		vtkSmartPointer<vtkTransform> Transform = vtkSmartPointer<vtkTransform>::New();
		Transform->Identity();
		Transform->Translate(FocalPoint[0]-Centroid[0], FocalPoint[1]-Centroid[1], FocalPoint[2]-Centroid[2]);
		Transform->Update();
		vtkSmartPointer<vtkTransformPolyDataFilter> TransformFilter = vtkSmartPointer<vtkTransformPolyDataFilter>::New();
		TransformFilter->SetTransform(Transform);
		TransformFilter->SetInput(Reader->GetOutput());
		TransformFilter->Update();
		Result = TransformFilter->GetOutput();
	}
	catch ( ... )
	{
		emit Finished(false);
		return;
	}

	emit Finished(true,Result);
}
