/* +---------------------------------------------------------------------------+
   |                     Mobile Robot Programming Toolkit (MRPT)               |
   |                          https://www.mrpt.org/                            |
   |                                                                           |
   | Copyright (c) 2005-2022, Individual contributors, see AUTHORS file        |
   | See: https://www.mrpt.org/Authors - All rights reserved.                  |
   | Released under BSD License. See details in https://www.mrpt.org/License   |
   +---------------------------------------------------------------------------+
   */
#include "CViewerContainer.h"

#include <QDebug>
#include <QTextEdit>

#include "CGLWidget.h"
#include "gui/observationTree/CRangeScanNode.h"
#include "ui_CViewerContainer.h"

CViewerContainer::CViewerContainer(QWidget* parent)
	: QWidget(parent),
	  m_ui(std::make_unique<Ui::CViewerContainer>()),
	  m_information(new QTextEdit(this)),
	  m_helpLoadMap(
		  tr("For loading the map, press File -> Open simplemap or Ctrl + "
			 "O.\n\n\n")),
	  m_helpLoadConfig(tr(
		  "The list of metric maps is empty. Either:\n\n"
		  "- Select File -> Load config or Ctrl + E; or\n"
		  "- manually define the metric maps from the 'Configuration' button."))
{
	m_ui->setupUi(this);
	connect(
		m_ui->m_zoom, SIGNAL(valueChanged(double)), SLOT(zoomChanged(double)));
	connect(
		m_ui->m_zoomSlider, SIGNAL(valueChanged(int)), SLOT(zoomChanged(int)));

	connect(
		m_ui->m_azimDeg, SIGNAL(valueChanged(double)),
		SLOT(updateAzimuthDegrees(double)));
	connect(
		m_ui->m_elevationDeg, SIGNAL(valueChanged(double)),
		SLOT(updateElevationDegrees(double)));

	connect(
		m_ui->m_tabWidget, &QTabWidget::currentChanged, this,
		&CViewerContainer::updatePanelInfo);

	m_information->setReadOnly(true);
	m_information->setText(m_helpLoadMap);

	m_ui->m_tabWidget->addTab(m_information, "Welcome!");
}

CViewerContainer::~CViewerContainer()
{
	for (int i = 0; i < m_ui->m_tabWidget->count(); ++i)
	{
		delete m_ui->m_tabWidget->widget(i);
	}
}

void CViewerContainer::changeHelpTextToAboutConfig()
{
	m_information->setText(m_helpLoadConfig);
}

void CViewerContainer::showRangeScan(CNode* node)
{
	CRangeScanNode* obsNode = dynamic_cast<CRangeScanNode*>(node);
	ASSERT_(obsNode);

	auto obj = mrpt::opengl::CPlanarLaserScan::Create();
	obj->setScan(*(obsNode->observation().get()));
	obj->setPose(obsNode->getPose());
	obj->setSurfaceColor(1.0f, 0.0f, 0.0f, 0.5f);

	forEachGl([obsNode, obj](CGlWidget* gl) {
		gl->setSelected(obsNode->getPose().asTPose());
		gl->setLaserScan(obj);
	});
}

void CViewerContainer::forEachGl(const std::function<void(CGlWidget*)>& func)
{
	if (containsHelpInfo()) return;

	for (int i = 0; i < m_ui->m_tabWidget->count(); ++i)
	{
		CGlWidget* gl = dynamic_cast<CGlWidget*>(m_ui->m_tabWidget->widget(i));
		ASSERT_(gl);
		func(gl);
	}
}

void CViewerContainer::showRobotDirection(const mrpt::poses::CPose3D& pose)
{
	forEachGl([pose](CGlWidget* gl) { gl->setSelected(pose.asTPose()); });
}

void CViewerContainer::applyConfigChanges(RenderizableMaps renderizableMaps)
{
	if (containsHelpInfo()) return;

	for (int i = 0; i < m_ui->m_tabWidget->count(); ++i)
	{
		auto sTypeIter = m_tabsInfo.find(i);
		ASSERT_(sTypeIter != m_tabsInfo.end());

		auto it = renderizableMaps.find(sTypeIter->second);
		ASSERT_(it != renderizableMaps.end());
		CGlWidget* gl = dynamic_cast<CGlWidget*>(m_ui->m_tabWidget->widget(i));
		ASSERT_(gl);

		gl->fillMap(it->second);
		gl->updateObservations();
	}
}

void CViewerContainer::updateConfigChanges(
	RenderizableMaps renderizableMaps, CDocument* doc, bool isShowAllObs)
{
	for (int i = 0; i < m_ui->m_tabWidget->count(); ++i)
	{
		QWidget* w = m_ui->m_tabWidget->widget(i);
		if (w != m_information) w->deleteLater();
	}
	m_tabsInfo.clear();
	m_ui->m_tabWidget->clear();

	if (renderizableMaps.empty())
	{
		m_ui->m_tabWidget->addTab(m_information, "Welcome!");
		return;
	}

	for (auto& it : renderizableMaps)
	{
		bool is2D = it.first.type == TypeOfConfig::PointsMap ||
			it.first.type == TypeOfConfig::Occupancy;
		CGlWidget* gl = new CGlWidget(is2D);
		gl->fillMap(it.second);

		QString name = QString::fromStdString(typeToName(it.first.type)) +
			QString::number(it.first.index);
		int tabIndex = m_ui->m_tabWidget->addTab(gl, name);
		m_tabsInfo.emplace(tabIndex, it.first);

		gl->setDocument(doc);
		gl->setSelectedObservation(isShowAllObs);

		connect(
			gl, &CGlWidget::zoomChanged, this,
			&CViewerContainer::changeZoomInfo);
		connect(
			gl, &CGlWidget::mousePosChanged, this,
			&CViewerContainer::updateMouseInfo);
		connect(
			gl, &CGlWidget::azimuthChanged, this,
			&CViewerContainer::changeAzimuthDeg);
		connect(
			gl, &CGlWidget::elevationChanged, this,
			&CViewerContainer::changeElevationDeg);

		connect(
			gl, SIGNAL(deleteRobotPoses(const std::vector<size_t>&)),
			SIGNAL(deleteRobotPoses(const std::vector<size_t>&)));

		connect(
			gl, SIGNAL(showPoseDirection(size_t, double, double, double)),
			SIGNAL(showPoseDirection(size_t, double, double, double)));

		connect(
			gl,
			SIGNAL(moveRobotPoses(const std::vector<size_t>&, const QPointF&)),
			SIGNAL(moveRobotPoses(const std::vector<size_t>&, const QPointF&)));

		connect(
			gl, &CGlWidget::selectedChanged, this,
			&CViewerContainer::changedSelected);

		connect(
			gl, &CGlWidget::selectedChanged, this,
			&CViewerContainer::changedSelected);

		connect(
			this, &CViewerContainer::selectedChanged, gl,
			&CGlWidget::updateSelectionWithoutSignals);
	}
}

void CViewerContainer::setDocument(CDocument* doc)
{
	forEachGl([doc](CGlWidget* gl) { gl->setDocument(doc); });
}

void CViewerContainer::showAllObservation(bool is)
{
	forEachGl([is](CGlWidget* gl) { gl->setSelectedObservation(is); });
}

void CViewerContainer::changedSelected(
	const std::vector<CRobotPose::Ptr>& robotPoses)
{
	std::vector<size_t> indexes;
	for (auto& pose : robotPoses)
		indexes.push_back(pose->getId());
	emit selectedChanged(indexes);
}

void CViewerContainer::updatePanelInfo(int index)
{
	if (containsHelpInfo() || m_ui->m_tabWidget->count() == 0) return;

	CGlWidget* gl = dynamic_cast<CGlWidget*>(m_ui->m_tabWidget->widget(index));
	ASSERT_(gl);

	changeZoomInfo(gl->getZoom());
	changeAzimuthDeg(gl->getAzimuthDegrees());
	changeElevationDeg(gl->getElevationDegrees());
}

void CViewerContainer::changeZoomInfo(float zoom)
{
	int zoomInt = zoom;
	m_ui->m_zoomSlider->blockSignals(true);
	m_ui->m_zoomSlider->setValue(zoomInt);
	m_ui->m_zoomSlider->blockSignals(false);

	double zoomDouble = zoom;
	m_ui->m_zoom->blockSignals(true);
	m_ui->m_zoom->setValue(zoomDouble);
	m_ui->m_zoom->blockSignals(false);
}

void CViewerContainer::changeAzimuthDeg(float deg)
{
	m_ui->m_azimDeg->blockSignals(true);
	m_ui->m_azimDeg->setValue(deg);
	m_ui->m_azimDeg->blockSignals(false);
}

void CViewerContainer::changeElevationDeg(float deg)
{
	m_ui->m_elevationDeg->blockSignals(true);
	m_ui->m_elevationDeg->setValue(deg);
	m_ui->m_elevationDeg->blockSignals(false);
}

void CViewerContainer::zoomChanged(double d)
{
	if (containsHelpInfo() || m_ui->m_tabWidget->count() == 0) return;

	float zoomFloat = d;
	int zoomInt = zoomFloat;
	m_ui->m_zoomSlider->setValue(zoomInt);
	CGlWidget* gl =
		dynamic_cast<CGlWidget*>(m_ui->m_tabWidget->currentWidget());
	ASSERT_(gl);
	gl->setZoom(zoomFloat);
}

void CViewerContainer::zoomChanged(int d)
{
	if (containsHelpInfo() || m_ui->m_tabWidget->count() == 0) return;

	double zoomD = d;
	float zoomFloat = zoomD;
	m_ui->m_zoom->setValue(zoomD);
	CGlWidget* gl =
		dynamic_cast<CGlWidget*>(m_ui->m_tabWidget->currentWidget());
	ASSERT_(gl);
	gl->setZoom(zoomFloat);
}

void CViewerContainer::updateMouseInfo(double x, double y)
{
	QString str = "x: " + QString::number(x) + "; y: " + QString::number(y);
	m_ui->m_infoUnderCursor->setText(str);
}

void CViewerContainer::updateAzimuthDegrees(double deg)
{
	CGlWidget* gl = getCurrentTabWidget();
	if (!gl) return;

	gl->blockSignals(true);
	gl->setAzimuthDegrees(deg);
	gl->blockSignals(false);
	gl->update();
}

void CViewerContainer::updateElevationDegrees(double deg)
{
	CGlWidget* gl = getCurrentTabWidget();
	if (!gl) return;

	gl->blockSignals(true);
	gl->setElevationDegrees(deg);
	gl->blockSignals(false);
	gl->update();
}

void CViewerContainer::setGeneralSetting(const SGeneralSetting& setting)
{
	float rBack = setting.backgroundColor.red() / 255.0;
	float gBack = setting.backgroundColor.green() / 255.0;
	float bBack = setting.backgroundColor.blue() / 255.0;
	float aBack = setting.backgroundColor.alpha() / 255.0;
	float r = setting.gridColor.red() / 255.0;
	float g = setting.gridColor.green() / 255.0;
	float b = setting.gridColor.blue() / 255.0;
	float a = setting.gridColor.alpha() / 255.0;

	forEachGl([r, g, b, a, rBack, gBack, bBack, aBack, setting](CGlWidget* gl) {
		gl->setGridColor(r, g, b, a);
		gl->setVisibleGrid(setting.isGridVisibleChanged);
		gl->setBackgroundColor(rBack, gBack, bBack, aBack);
		gl->setBot(setting.currentBot);

		gl->setObservationSize(setting.robotPosesSize);
		gl->setSelectedObservationSize(setting.selectedRobotPosesSize);
		gl->setObservationColor(setting.robotPosesColor);
		gl->setSelectedObservationColor(setting.selectedRobotPosesColor);
	});

	CGlWidget* gl = getCurrentTabWidget();
	if (gl) gl->update();
}

CGlWidget* CViewerContainer::getCurrentTabWidget() const
{
	if (containsHelpInfo() || m_ui->m_tabWidget->count() <= 0) return nullptr;

	CGlWidget* gl =
		dynamic_cast<CGlWidget*>(m_ui->m_tabWidget->currentWidget());
	ASSERT_(gl);

	return gl;
}

bool CViewerContainer::containsHelpInfo() const
{
	return m_ui->m_tabWidget->widget(0) == m_information;
}
