/*****************************************************************************/
/*  Copyright 2021 WSL Institute for Snow and Avalanche Research  SLF-DAVOS  */
/*****************************************************************************/
/* This file is part of INIshell.
   INIshell 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, either version 3 of the License, or
   (at your option) any later version.

   INIshell 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.

   You should have received a copy of the GNU General Public License
   along with INIshell.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "SettingsWindow.h"
#include "src/main/colors.h"
#include "src/main/inishell.h"
#include "src/main/settings.h"
#include "src/main/XMLReader.h"

#include <QHBoxLayout>
#include <QMessageBox>
#include <QStatusBar>
#include <QVBoxLayout>
#include <QtXml>

/**
 * @class SettingsWindow
 * @brief Constructor for the Settings window.
 * @param[in] parent The window's parent.
 */
SettingsWindow::SettingsWindow(QMainWindow *parent) : QMainWindow(parent)
{
	/* create a scroll area and put a Group in it */
	settings_tab_ = new SectionTab();
	settings_tab_->tabBar()->setVisible(false); //only the "Settings" tab

	/* create an info label */
	auto *settings_location = new QLabel(
		tr(R"(The settings file is located at path <i>"%1"</i> and can be deleted at any time, but will be re-created when running <i>INIshell</i>.)").arg(
		getSettingsFileName()));
	settings_location->setStyleSheet("QLabel {color: " + colors::getQColor("helptext").name() + "}");
	settings_location->setWordWrap(true);
	settings_location->setAlignment(Qt::AlignCenter);

	/* info layout with label and clear button */
	auto info_layout( new QHBoxLayout );
	info_layout->addWidget(settings_location);
	auto clear_settings_button( new QPushButton(tr("Clear settings"), this));
	clear_settings_button->setToolTip(tr("Reset all INIshell settings to default values"));
	clear_settings_button->setStyleSheet("QPushButton {background-color: " + colors::getQColor("important").name() + "}");
	clear_settings_button->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
	connect(clear_settings_button, &QPushButton::clicked, this, &SettingsWindow::clearSettings);
	info_layout->addWidget(clear_settings_button);
	info_layout->setContentsMargins(0, 0, 10, 0);

	/* main layout */
	auto main_layout( new QVBoxLayout );
	main_layout->addWidget(settings_tab_);
	main_layout->addLayout(info_layout);
	main_layout->setContentsMargins(0, 0, 0, 10);
	auto dummy_widget = new QWidget(this); //we need a central widget, not a layout
	dummy_widget->setLayout(main_layout);
	this->setCentralWidget(dummy_widget);

	this->setWindowTitle(tr("Settings") + " ~ " + QCoreApplication::applicationName());
}

/**
 * @brief Open the Settings window.
 */
void SettingsWindow::loadSettings()
{
	if (!settings_are_loaded_) {
		XMLReader xmls;
		QString err;
		xmls.read(":settings_dialog.xml", err);
#ifdef DEBUG
		if (!err.isEmpty())
			qDebug() << "There is an error in the settings XML: " << err;
#endif //def DEBUG
		QDomDocument xml(xmls.getXml());
		buildGui(xml, settings_tab_);
		settings_are_loaded_ = true;
	} //endif is_loaded

	displaySettings();	//may have changed from e. g. a dialog box
	this->show();
	this->raise();
}

/**
 * @brief Event listener for the ESC key.
 * @details Close the settings on pressing ESC or display the logger.
 * @param[in] event The key press event that is received.
 */
void SettingsWindow::keyPressEvent(QKeyEvent *event)
{
	if (event->key() == Qt::Key_Escape || keyToSequence(event) == QKeySequence::Close) {
		this->close();
	} else if (event->modifiers() == Qt::CTRL && event->key() == Qt::Key_L) {
		getMainWindow()->getLogger()->show();
		getMainWindow()->getLogger()->raise();
	}
}

/**
 * @brief Event listener for when a window is closed e. g. through clicking "x".
 * @param[in] event The received close event.
 */
void SettingsWindow::closeEvent(QCloseEvent *event)
{
	setSettings(); //will be saved to disk when closing the MainWindow
	event->accept();
}

/**
 * @brief Helper function to get a Settings page panel's value.
 * @param[in] parent The parent widget to search.
 * @param[in] option The setting name to retrieve.
 * @return The setting's current value in the GUI.
 */
QString SettingsWindow::getShellSetting(QWidget *parent, const QString &option)
{
	const Atomic *option_element( parent->findChild<Atomic *>(
		Atomic::getQtKey("SETTINGS::" + option)) );
	if (option_element) {
		QString section, key; //unused here
		return option_element->getIniValue(section, key);
	}
	return QString();
}


/**
 * @brief Display INIshell's settings (XML file) in the Settings page.
 */
void SettingsWindow::displaySettings()
{
	//first we must remove the path Replicator's children (they would not get deleted otherwise):
	auto *path_rep( settings_tab_->getSectionScrollArea(0)->
		findChild<Replicator *>(Atomic::getQtKey("SETTINGS::user::xmlpaths::path#")) );
	path_rep->clear();

	const QStringList settings_list(getSimpleSettingsNames());
	for (auto &set : settings_list) {
		const QString string_value( getSetting(set, "value") );
		auto panels( settings_tab_->getSectionScrollArea(0)->findChildren<Atomic *>(
			Atomic::getQtKey("SETTINGS::" + set)) );
		if (panels.count() > 0) //no crash on settings from elsewhere (e. g. toolbar context menu)
			panels.at(0)->setProperty("ini_value", string_value);
	}
	const QStringList search_dirs( getListSetting("user::xmlpaths", "path") );
	for (int ii = 1; ii <= search_dirs.size(); ++ii) {
		auto *xml_panel = settings_tab_->getSectionScrollArea(0)->
			findChild<Replicator *>(Atomic::getQtKey("SETTINGS::user::xmlpaths::path#"));
		xml_panel->setProperty("ini_value", ii);
		auto path_panel = settings_tab_->getSectionScrollArea(0)->findChild<Atomic *>(
			Atomic::getQtKey(QString("SETTINGS::user::xmlpaths::path%1").arg(ii)));
		path_panel->setProperty("ini_value", search_dirs.at(ii-1));
	}
}

/**
 * @brief Save INIshell settings from the Settings page to the file system.
 * @details This function is called by a lambda from the save button.
 */
void SettingsWindow::setSettings()
{
	QWidget *section_tab( settings_tab_->widget(0) );
	const QStringList settings_list(getSimpleSettingsNames());
	for (auto &option : settings_list) {
		const QString value( getShellSetting(section_tab, option) );
		//there are user settings not in the settings page (e. g. "fix toolbar position"), so don't overwrite:
		if (!value.isEmpty())
			setSetting(option, "value", value);
	}

	QStringList search_dirs;
	const QList<Atomic *>panel_list( section_tab->findChildren<Atomic *>() );
	for (auto &pan : panel_list) {
		QString section, key;
		const QString value( pan->getIniValue(section, key) );
		if (key.startsWith("user::xmlpaths::path") && !value.isEmpty())
			search_dirs.push_back(value);
	}
	setListSetting("user::xmlpaths", "path", search_dirs);
	getMainWindow()->getControlPanel()->getWorkflowPanel()->scanFoldersForApps(); //auto-refresh apps
}

/**
 * @brief Reset all settings to default.
 */
void SettingsWindow::clearSettings()
{
	QMessageBox msgWarnClear(this);
	msgWarnClear.setWindowTitle("Warning ~ " + QCoreApplication::applicationName());
	msgWarnClear.setText(tr("<b>Reset all settings to default values?</b>"));
	msgWarnClear.setIcon(QMessageBox::Warning);
	msgWarnClear.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
	msgWarnClear.setDefaultButton(QMessageBox::Cancel);

	int clicked = msgWarnClear.exec();
	if (clicked == QMessageBox::Cancel)
		return;

	QFile sfile( getSettingsFileName() );
	sfile.remove();
	if (sfile.error())
		statusBar()->showMessage("Can't delete settings file: " + sfile.errorString());
	else
		statusBar()->hide();
	global_xml_settings = QDomDocument();
	checkSettings(); //re-create settings from template file
	settings_tab_->clear();
	settings_are_loaded_ = false;
	loadSettings();
	getMainWindow()->getControlPanel()->getWorkflowPanel()->scanFoldersForApps(); //auto-refresh apps
	getMainWindow()->sizeAllWindows(); //reset sizes of help window, preview, ...
	this->raise();
}
