/*****************************************************************************/
/*  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
	orig_settings_ini_ = new INIParser(); //remember settings at time of loading in here

	/* create an info label */
	auto *settings_location = new QLabel(getSettingsFileName());
	settings_location->setStyleSheet("QLabel {color: " + colors::getQColor("helptext").name() + "}");
	settings_location->setWordWrap(false);
	settings_location->setSizePolicy(QSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed));
	settings_location->setAlignment(Qt::AlignCenter);
	settings_location->setToolTip(settings_location->text());

	/* create reset, close and save buttons */
	auto reset_button( new QPushButton(tr("Reset"), this));
	reset_button->setToolTip(tr("Reset all INIshell settings to default values"));
	reset_button->setStyleSheet("QPushButton {background-color: " + colors::getQColor("important").name() + "}");
	reset_button->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
	connect(reset_button, &QPushButton::clicked, this, &SettingsWindow::clearSettings);
	auto *close_button = new QPushButton(getIcon("application-exit"), tr("&Cancel"));
	close_button->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
	connect(close_button, &QPushButton::clicked, this, [this]{ this->close(); });
	auto *save_button = new QPushButton(getIcon("document-save"), tr("&Save"));
	save_button->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
	connect(save_button, &QPushButton::clicked, this, &SettingsWindow::setSettings); //will be saved on main exit

	/* button layout */
	auto button_layout( new QHBoxLayout );
	button_layout->addWidget(reset_button, 1, Qt::AlignLeft);
	button_layout->addWidget(settings_location, 100, Qt::AlignHCenter);
	button_layout->addWidget(close_button, 1, Qt::AlignRight);
	button_layout->addWidget(save_button, 1, Qt::AlignRight);
	button_layout->setContentsMargins(10, 0, 10, 0);

	/* main layout */
	auto main_layout( new QVBoxLayout );
	main_layout->addWidget(settings_tab_);
	main_layout->addLayout(button_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->setWindowFlag(Qt::Dialog);
	this->setWindowFlag(Qt::WindowStaysOnTopHint);
	this->setWindowTitle(tr("Settings") + " ~ " + QCoreApplication::applicationName());
}

/**
 * @brief Destructor with minimal cleanup.
 */
SettingsWindow::~SettingsWindow()
{
	delete orig_settings_ini_;
}

/**
 * @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
	settings_tab_->setIniValuesFromGui(orig_settings_ini_);
	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 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()
{
	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)
			//for if settings are closed and reloaded --> the property will not yet be updated (not firing onPropertySet):
			panels.at(0)->setProperty("ini_value", QString());
			panels.at(0)->setProperty("ini_value", string_value);
		}
	}

	/* search path settings */
	auto *path_rep( settings_tab_->getSectionScrollArea(0)->
		findChild<Replicator *>(Atomic::getQtKey("SETTINGS::user::xmlpaths::path#")) );
	//first we must remove the path Replicator's children (they would not get deleted otherwise):
	path_rep->clear();
	const QStringList search_dirs( getListSetting("user::xmlpaths", "path") );
	for (int ii = 1; ii <= search_dirs.size(); ++ii) {
		path_rep->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()
{
	/* check if appearance settings have been modified */
	bool appearance_changed = false;
	INIParser *current_settings = new INIParser();
	settings_tab_->setIniValuesFromGui(current_settings);
	auto sec( *current_settings->getSectionsCopy().getSectionsList().begin() ); //the "Settings" section
	for (auto &kv : sec.getKeyValueList()) { //compare settings from time of loading to now
		if (kv.second.getKey().contains("::appearance")) {
			if (orig_settings_ini_->get("Settings", kv.second.getKey()) != kv.second.getValue()) {
				appearance_changed = true;
				break;
			}
		}
	} //endfor
	delete current_settings;

	int msg_answer;
	if (appearance_changed) {
		msg_answer = appearanceWarnBox();
		if (msg_answer == QMessageBox::Cancel) //exit but keep settings open
			return;
	}

	/* run through all non-dynamic settings */
	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);
	}

	/* dynamic settings (such as expandable list of paths) */
	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

	if (msg_answer == QMessageBox::Yes)
		getMainWindow()->resetAppearance(true);
	else
		this->close();
}

/**
 * @brief Helper function to display a warning message box.
 * @return Button the user has clicked.
 */
int SettingsWindow::appearanceWarnBox()
{
	QMessageBox msgWarnClear(this);
	msgWarnClear.setWindowTitle("Warning ~ " + QCoreApplication::applicationName());
	msgWarnClear.setText(tr("<b>Appearance settings changed</b>"));
	msgWarnClear.setInformativeText(
		tr("A restart of INIshell is required for appearance settings to take effect. Restart now?\n\nMake sure you have all your work saved!"));
	msgWarnClear.setIcon(QMessageBox::Warning);
	msgWarnClear.setStandardButtons(QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
	msgWarnClear.setDefaultButton(QMessageBox::No);
	return msgWarnClear.exec();
}

/**
 * @brief Reset all settings to default.
 */
void SettingsWindow::clearSettings()
{
	int clicked = appearanceWarnBox();
	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()->resetAppearance(clicked == QMessageBox::Yes); //reset sizes of help window, preview, ...
	this->raise();
}
