/* +---------------------------------------------------------------------------+
   |                     Mobile Robot Programming Toolkit (MRPT)               |
   |                          https://www.mrpt.org/                            |
   |                                                                           |
   | Copyright (c) 2005-2023, 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 "CConfigWidget.h"

#include <QCheckBox>
#include <QFileDialog>
#include <QListWidget>
#include <QListWidgetItem>
#include <QMessageBox>

#include "CBeaconConfig.h"
#include "CGasGridConfig.h"
#include "CGeneralConfig.h"
#include "CLandmarksConfig.h"
#include "COccupancyConfig.h"
#include "CPointsConfig.h"
#include "CSelectType.h"
#include "mrpt/gui/error_box.h"
#include "mrpt/io/CFileOutputStream.h"
#include "ui_CConfigWidget.h"

using namespace mrpt;
using namespace maps;
using namespace opengl;

CConfigWidget::CConfigWidget(QWidget* parent)
	: QWidget(parent),
	  m_general(new CGeneralConfig()),
	  m_ui(std::make_unique<Ui::CConfigWidget>())
{
	m_ui->setupUi(this);

	addWidget(TypeOfConfig::General, m_general);

	connect(
		m_ui->m_loadConfig, &QPushButton::released, this,
		&CConfigWidget::openConfig);
	connect(
		m_ui->m_saveConfig, &QPushButton::released, this,
		&CConfigWidget::saveConfig);
	connect(m_ui->m_add, &QPushButton::released, this, &CConfigWidget::addMap);
	connect(
		m_ui->m_remove, &QPushButton::released, this,
		&CConfigWidget::removeMap);
	connect(
		m_ui->m_apply, &QPushButton::released, this,
		&CConfigWidget::applyConfigurationForCurrentMaps);

	connect(
		m_ui->m_config, &QListWidget::currentItemChanged, this,
		&CConfigWidget::currentConfigChanged);
}

CConfigWidget::~CConfigWidget() { clearConfig(true); }
void CConfigWidget::openConfig()
{
	mrpt::gui::tryCatch(
		[this]() {
			QString configName = QFileDialog::getOpenFileName(
				this, tr("Open Config File"), "", tr("Files (*.ini)"));
			if (configName.isEmpty()) return;
			QFile file(configName);
			if (!file.open(QIODevice::ReadOnly)) throw "";

			emit openedConfig(configName.toStdString());
		},
		"The file is corrupted and cannot be opened!");
}

void CConfigWidget::saveConfig()
{
	QString configName = QFileDialog::getSaveFileName(
		this, QObject::tr("Save Config File"), "", "Files (*.ini)");
	if (configName.isEmpty()) return;

	QFile file(configName);
	if (!file.open(QIODevice::WriteOnly))
	{
		QMessageBox::information(
			this, tr("Unable to open file"), file.errorString());
		return;
	}
	mrpt::io::CFileOutputStream f(
		configName.toStdString(), mrpt::io::OpenMode::APPEND);

	for (int i = 0; i < m_ui->stackedWidget->count(); ++i)
	{
		QWidget* w = m_ui->stackedWidget->widget(i);
		COccupancyConfig* o = dynamic_cast<COccupancyConfig*>(w);
		Q_UNUSED(o);
		// TODO: saving configurations
	}
}

void CConfigWidget::addMap()
{
	std::unique_ptr<CSelectType> dialog = std::make_unique<CSelectType>();
	int result = dialog->exec();
	if (result == QDialog::Accepted)
	{
		int type = dialog->selectedItem();
		auto typeOfConfig = static_cast<TypeOfConfig>(type);
		CBaseConfig* w = configByType(typeOfConfig);
		if (w) addWidget(typeOfConfig, w);
	}
}

void CConfigWidget::removeMap()
{
	int currentRow = m_ui->m_config->currentRow();
	if (currentRow <= 0) return;

	QWidget* w = m_ui->stackedWidget->widget(currentRow);
	ASSERT_(w);

	auto* base = dynamic_cast<CBaseConfig*>(w);
	ASSERT_(base);

	TypeOfConfig type = base->type();

	auto it = m_configs.find(type);
	if (it != m_configs.end())
	{
		for (auto vectorIter = it->second.begin();
			 vectorIter != it->second.end(); ++vectorIter)
			if (*vectorIter == base)
			{
				it->second.erase(vectorIter);
				break;
			}
		delete (m_ui->m_config->takeItem(m_ui->m_config->currentRow()));
		emit removedMap();
	}
	delete base;
}

void CConfigWidget::currentConfigChanged(
	QListWidgetItem* current, QListWidgetItem* /*previous*/)
{
	if (!current) return;

	m_ui->stackedWidget->setCurrentIndex(m_ui->m_config->currentRow());
}

CBaseConfig* CConfigWidget::configByType(TypeOfConfig type) const
{
	switch (type)
	{
		case TypeOfConfig::PointsMap:
		{
			return new CPointsConfig();
		}
		case TypeOfConfig::Occupancy:
		{
			return new COccupancyConfig();
		}
		case TypeOfConfig::Beacon:
		{
			return new CBeaconConfig();
		}
		case TypeOfConfig::GasGrid:
		{
			return new CGasGridConfig();
		}
		case TypeOfConfig::Landmarks:
		{
			return new CLandmarksConfig();
		}
		default: break;
	}
	return nullptr;
}

void CConfigWidget::clearConfig(bool deleteGeneral)
{
	for (int i = 0; i < m_ui->stackedWidget->count();)
	{
		QWidget* w = m_ui->stackedWidget->widget(i);

		if (deleteGeneral || i != 0)
		{
			m_ui->stackedWidget->removeWidget(w);
			delete w;
			QListWidgetItem* item = m_ui->m_config->takeItem(i);
			delete item;
		}
		else
			++i;
	}

	m_configs.clear();
}

TSetOfMetricMapInitializers CConfigWidget::config()
{
	using mrpt::maps::internal::TMetricMapTypesRegistry;
	TMetricMapTypesRegistry& mmr = TMetricMapTypesRegistry::Instance();
	TSetOfMetricMapInitializers mapCfg;
	MRPT_START
	mapCfg.clear();
	for (auto& it : m_configs)
	{
		int index = 0;
		for (auto& map : it.second)
		{
			const std::string sMapName = map->getName().toStdString();
			auto mi = mmr.factoryMapDefinition(sMapName);
			ASSERT_(mi);

			map->updateConfiguration(mi.get());
			mapCfg.push_back(mi);
			++index;
		}
	}
	MRPT_END
	return mapCfg;
}

void CConfigWidget::setConfig(const CMultiMetricMap::TListMaps& config)
{
	clearConfig();

	for (auto& m : config)
	{
		bool found = false;
		{
			if (auto ptr = std::dynamic_pointer_cast<CSimplePointsMap>(m); ptr)
			{
				CPointsConfig* pConfig = new CPointsConfig();
				addWidget(TypeOfConfig::PointsMap, pConfig);
				pConfig->setInsertOpt(ptr->insertionOptions);
				pConfig->setLikelihoodOpt(ptr->likelihoodOptions);
				found = true;
			}
		}
		if (!found)
		{
			if (auto ptr = std::dynamic_pointer_cast<COccupancyGridMap2D>(m);
				ptr)
			{
				COccupancyConfig* pConfig = new COccupancyConfig();
				addWidget(TypeOfConfig::Occupancy, pConfig);
				pConfig->setCreationOpt(
					ptr->getXMin(), ptr->getXMax(), ptr->getYMin(),
					ptr->getYMax(), ptr->getResolution());
				pConfig->setInsertOpt(ptr->insertionOptions);
				pConfig->setLikelihoodOpt(ptr->likelihoodOptions);
				found = true;
			}
		}
		if (!found)
		{
			if (auto ptr =
					std::dynamic_pointer_cast<CGasConcentrationGridMap2D>(m);
				m)
			{
				CGasGridConfig* pConfig = new CGasGridConfig();
				addWidget(TypeOfConfig::GasGrid, pConfig);
				pConfig->setCreationOpt(
					ptr->getXMin(), ptr->getXMax(), ptr->getYMin(),
					ptr->getYMax(), ptr->getResolution());
				pConfig->setInsertOpt(ptr->insertionOptions);
				// pConfig->setMapTypeOpt(ptr->mapType);
				found = true;
			}
		}
		if (!found)
		{
			if (auto ptr = std::dynamic_pointer_cast<CBeaconMap>(m); ptr)
			{
				CBeaconConfig* pConfig = new CBeaconConfig();
				addWidget(TypeOfConfig::Beacon, pConfig);
				pConfig->setInsertOpt(ptr->insertionOptions);
				pConfig->setLikelihoodOpt(ptr->likelihoodOptions);
				found = true;
			}
		}
		if (!found)
		{
			if (auto ptr = std::dynamic_pointer_cast<CLandmarksMap>(m); m)
			{
				CLandmarksConfig* pConfig = new CLandmarksConfig();
				addWidget(TypeOfConfig::Landmarks, pConfig);
				pConfig->setInsertOpt(ptr->insertionOptions);
				pConfig->setLikelihoodOpt(ptr->likelihoodOptions);
				found = true;
			}
		}
	}
}

const SGeneralSetting& CConfigWidget::generalSetting()
{
	return m_general->generalSetting();
}

int CConfigWidget::addWidget(TypeOfConfig type, CBaseConfig* w)
{
	QString nameListWidgetItem = w->getName();

	auto it = m_configs.find(type);
	if (type != TypeOfConfig::General)
	{
		if (it == m_configs.end())
			it = m_configs.emplace(type, std::vector<CBaseConfig*>()).first;

		nameListWidgetItem += QString::number(it->second.size());
	}

	QListWidgetItem* item =
		new QListWidgetItem(nameListWidgetItem, m_ui->m_config);
	item->setData(Qt::UserRole, type);
	m_ui->m_config->addItem(item);
	m_ui->stackedWidget->addWidget(w);

	int index = 0;
	if (type != TypeOfConfig::General)
	{
		index = it->second.size();
		it->second.push_back(w);
	}

	emit addedMap();
	return index;
}
