//##########################################################################
//#                                                                        #
//#                              CLOUDCOMPARE                              #
//#                                                                        #
//#  This program is free software; you can redistribute it and/or modify  #
//#  it under the terms of the GNU General Public License as published by  #
//#  the Free Software Foundation; version 2 or later of the License.      #
//#                                                                        #
//#  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 the          #
//#  GNU General Public License for more details.                          #
//#                                                                        #
//#          COPYRIGHT: EDF R&D / TELECOM ParisTech (ENST-TSI)             #
//#                                                                        #
//##########################################################################

#include "ccNormalComputationDlg.h"

//qCC_db
#include <ccPointCloud.h>
#include <ccOctree.h>
#include <ccProgressDialog.h>

//Qt
#include <QComboBox>

//system
#include <assert.h>

ccNormalComputationDlg::ccNormalComputationDlg(bool withScanGrid, bool withSensor, QWidget* parent/*=nullptr*/)
	: QDialog(parent, Qt::Tool)
	, Ui::NormalComputationDlg()
	, m_cloud(nullptr)
{
	setupUi(this);

	//by default, the 'auto' button is hidden (as long as setCloud is not called)
	autoRadiusToolButton->setVisible(false);

	connect(localModelComboBox, qOverload<int>(&QComboBox::currentIndexChanged), this, &ccNormalComputationDlg::localModelChanged);
	connect(autoRadiusToolButton, &QToolButton::clicked,												this, &ccNormalComputationDlg::autoEstimateRadius);

	if (withScanGrid)
	{
		useScanGridCheckBox->setChecked(true);
		scanGridsOrientCheckBox->setChecked(true);
	}
	else
	{
		//disable 'scan grid' options
		useScanGridCheckBox->setChecked(false);
		useScanGridCheckBox->setEnabled(false);
		gridAngleFrame->setEnabled(false);

		scanGridsOrientCheckBox->setChecked(false);
		scanGridsOrientCheckBox->setEnabled(false);
	}

	if (withSensor)
	{
		sensorOrientCheckBox->setChecked(true);
	}
	else
	{
		//disable 'sensor' options
		sensorOrientCheckBox->setChecked(false);
		sensorOrientCheckBox->setEnabled(false);
	}
}

void ccNormalComputationDlg::setLocalModel(CCCoreLib::LOCAL_MODEL_TYPES  model)
{
	int index = -1;
	switch (model)
	{
	case CCCoreLib::LS:
		index = 0;
		break;
	case CCCoreLib::QUADRIC:
		index = 1;
		break;
	case CCCoreLib::TRI:
		index = 2;
		break;
	default:
		assert(false);
		break;
	}
	localModelComboBox->setCurrentIndex(index);
}

CCCoreLib::LOCAL_MODEL_TYPES ccNormalComputationDlg::getLocalModel() const
{
	switch (localModelComboBox->currentIndex())
	{
	case 0:
		return CCCoreLib::LS;
	case 1:
		return CCCoreLib::QUADRIC;
	case 2:
		return CCCoreLib::TRI;
	}

	assert(false);
	return CCCoreLib::LS;
}

void ccNormalComputationDlg::localModelChanged(int index)
{
	//DGM: we don't disable the parent frame anymore as it is used by the octree/grid toggling
	radiusDoubleSpinBox->setEnabled(index != 2);
	autoRadiusToolButton->setEnabled(index != 2);
}

void ccNormalComputationDlg::setRadius(PointCoordinateType radius)
{
	radiusDoubleSpinBox->setValue(static_cast<double>(radius));
}

void ccNormalComputationDlg::setCloud(ccPointCloud* cloud)
{
	m_cloud = cloud;

	autoRadiusToolButton->setVisible(m_cloud != nullptr);
}

PointCoordinateType ccNormalComputationDlg::getRadius() const
{
	return static_cast<PointCoordinateType>(radiusDoubleSpinBox->value());
}

void ccNormalComputationDlg::setPreferredOrientation(ccNormalVectors::Orientation orientation)
{
	if (orientation == ccNormalVectors::UNDEFINED)
	{
		preferredOrientRadioButton->setChecked(false);
	}
	else
	{
		preferredOrientRadioButton->setChecked(true);
		preferredOrientationComboBox->setCurrentIndex(orientation);
	}
}

bool ccNormalComputationDlg::useScanGridsForComputation() const
{
	return useScanGridCheckBox->isChecked();
}

double ccNormalComputationDlg::getMinGridAngle_deg() const
{
	return gridAngleDoubleSpinBox->value();
}

void ccNormalComputationDlg::setMinGridAngle_deg(double value)
{
	gridAngleDoubleSpinBox->setValue(value);
}

bool ccNormalComputationDlg::orientNormals() const
{
	return normalsOrientGroupBox->isChecked();
}

void ccNormalComputationDlg::setOrientNormals(bool state)
{
	normalsOrientGroupBox->setChecked(state);
}

bool ccNormalComputationDlg::useScanGridsForOrientation() const
{
	return scanGridsOrientCheckBox->isChecked();
}

bool ccNormalComputationDlg::useSensorsForOrientation() const
{
	return sensorOrientCheckBox->isChecked();
}

bool ccNormalComputationDlg::usePreferredOrientation() const
{
	return preferredOrientRadioButton->isChecked();
}

bool ccNormalComputationDlg::useMSTOrientation() const
{
	return mstOrientRadioButton->isChecked();
}

void ccNormalComputationDlg::setMSTNeighborCount(int n)
{
	mstNeighborsSpinBox->setValue(n);
}

int ccNormalComputationDlg::getMSTNeighborCount() const
{
	return mstNeighborsSpinBox->value();
}

ccNormalVectors::Orientation ccNormalComputationDlg::getPreferredOrientation() const
{
	int index = preferredOrientRadioButton->isChecked() ? preferredOrientationComboBox->currentIndex() : -1;

	return (index >= 0 && index <= ccNormalVectors::MINUS_SENSOR_ORIGIN ? static_cast<ccNormalVectors::Orientation>(index) : ccNormalVectors::UNDEFINED);
}

void ccNormalComputationDlg::autoEstimateRadius()
{
	ccOctree::BestRadiusParams params;
	{
		params.aimedPopulationPerCell = 16;
		params.aimedPopulationRange = 4;
		params.minCellPopulation = 6;
		params.minAboveMinRatio = 0.97;
	}

	PointCoordinateType radius = ccOctree::GuessBestRadiusAutoComputeOctree(m_cloud, params, this);
	if (radius > 0)
	{
		radiusDoubleSpinBox->setValue(radius);
	}
	else
	{
		ccLog::Error("Failed to estimate the radius");
	}
}
