/*****************************************************************************/
/*  Copyright 2019 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 "MainPanel.h"
#include "SectionButton.h"
#include "WorkflowPanel.h"
#include "src/main/colors.h"
#include "src/main/constants.h"
#include "src/main/inishell.h"
#include "src/main/os.h"
#include "src/main/settings.h"
#include "src/main/XMLReader.h"
#include "src/gui_elements/gui_elements.h"

#include <QGroupBox>
#include <QHBoxLayout>
#include <QListWidget>
#include <QPushButton>
#include <QRegularExpression>
#include <QSplitter>
#include <QVBoxLayout>

#ifdef DEBUG
	#include <iostream>
#endif

/**
 * @class MainPanel
 * @brief Constructor for the main panel, i. e. the tab widget with scroll area children.
 * @param[in] parent The parent widget (the main window).
 */
MainPanel::MainPanel(QWidget *parent) : QWidget(parent)
{
	/* widget(s) on the left side and main tab bar */
	workflow_panel_ = new WorkflowPanel;
	workflow_stack_ = new QStackedWidget;
	section_tab_ = new SectionTab;
	workflow_stack_->addWidget(section_tab_);

	/* main layout */
	auto *main_layout( new QHBoxLayout );
	splitter_ = new QSplitter;
	splitter_->addWidget(workflow_panel_); //allow to resize with mouse
	splitter_->addWidget(workflow_stack_);
	setSplitterSizes();
	main_layout->addWidget(splitter_);
	this->setLayout(main_layout);

	displayInfo(); //startup info text
}

/**
 * @brief Run through panels and query their user-set values.
 * @details This function finds all panel classes, gets their set values, and modifies the
 * corresponding INI setting. It also performs checks for missing values.
 * Note: Originally INI values were updated live in the panels on input, eliminating the need
 * for this loop. However, more and more intermediate loops needed to be introduced (for example
 * to deactivate panels that are hidden by a parent element like a Dropdown or Choice).
 * Furthermore, dealing with missing mandatory values and keys that are present multiple times
 * (e. g. needing to check if a second panel manipulates the same key before deleting it in a
 * Selector) convoluted the program so heavily that this solution is much cleaner and more robust.
 * @param[in] ini The INIParser for which to set the values (the one that will be output).
 * @return A comma-separated list of missing mandatory INI keys.
 */
QString MainPanel::setIniValuesFromGui(INIParser *ini)
{
	//TODO: For some combination of properties (optional, will be defaulted by the
	//software, ...) we may be able to skip output of some keys to avoid
	//information redundancy. Parameter checks:
	//const QString default_value( panel->property("default_value").toString() ); //default value
	//( !ini->get(section, key).isNull()) ) //key present in input INI file
	//( is_mandatory ) //key is set non-optional

	QString missing;
	for (int ii = 0; ii < section_tab_->count(); ++ii) { //run through sections
		const QWidget *section_tab( section_tab_->widget(ii) );
		const QList<Atomic *> panel_list( section_tab->findChildren<Atomic *>() );
		for (auto &panel : panel_list) { //run through panels in section
			if (!panel->isVisibleTo(section_tab)) //visible if section_tab was visible?
				continue;
			QString section, key;
			const QString value( panel->getIniValue(section, key) );
			if (panel->property("no_ini").toBool())
				continue;
			if (key.isNull()) //GUI element that is not an interactive panel
				continue;

			const bool is_mandatory = panel->property("is_mandatory").toBool();
			if (is_mandatory && value.isEmpty()) //collect missing non-optional keys
				missing += key + ", ";
			if (!value.isEmpty())
				ini->set(section, key, value, is_mandatory);
		} //endfor panel_list
	} //endfor ii
	missing.chop(2); //trailing ", "
	return missing;
}

/**
 * @brief Display some info and the SLF logo on program start.
 */
void MainPanel::displayInfo()
{
	static const QString message(tr("<br> \
	    &nbsp;&nbsp;Welcome to <b>INIshell</b>, a dynamic graphical user interface builder to manage INI files.<br> \
	    &nbsp;&nbsp;Double-click an application to the left to begin.<br><br> \
	    &nbsp;&nbsp;For help, click \"Help\" in the menu and visit <a href=\"https://models.slf.ch/p/inishell-ng/\">INIshell's project page</a>.<br> \
	    &nbsp;&nbsp;There, you will also find the well-documented <a href=\"https://models.slf.ch/p/inishell-ng/source/tree/master/\">source code</a>.<br> \
	    &nbsp;&nbsp;If you don't know where to begin, press %1. \
	").arg(os::getHelpSequence()));

	auto *info_scroll( new QScrollArea );
	info_scroll->setWidgetResizable(true);

	/* SLF logo */
	QLabel *info_label( new QLabel );
	info_label->setAlignment(Qt::AlignBottom | Qt::AlignRight);
	info_label->setStyleSheet("QLabel {background-color: " + colors::getQColor("app_bg").name() + "}");
	const QPixmap logo(":/icons/slf_desaturated.svg");
	info_label->setPixmap(logo);

	/* info text */
	QLabel *info_text( new QLabel(info_label) );
	info_text->setTextFormat(Qt::RichText);
	info_text->setTextInteractionFlags(Qt::TextBrowserInteraction);
	info_text->setOpenExternalLinks(true);
	info_text->setText(message);
	info_scroll->setWidget(info_label);

	section_tab_->addTab(info_scroll, "Info");
}

/**
 * @brief Query the splitter's current sizes.
 * @return Current sizes of the splitter's panels.
 */
QList<int> MainPanel::getSplitterSizes() const
{
	return splitter_->sizes();
}

/**
 * @brief Set sizes of the panels the splitter manages.
 * @param[in] sizes The sizes the splitter should give to its panels.
 */
void MainPanel::setSplitterSizes(QList<int> sizes)
{
	splitter_->setStretchFactor(0, Cst::proportion_workflow_horizontal_percent);
	splitter_->setStretchFactor(1, 100 - Cst::proportion_workflow_horizontal_percent);
	if (!sizes.isEmpty()) {
		splitter_->setSizes(sizes);
		return;
	}
	bool workflow_success, main_success;
	const int width_workflow = getSetting("auto::sizes::splitter_workflow", "size").toInt(&workflow_success);
	const int width_mainpanel = getSetting("auto::sizes::splitter_mainpanel", "size").toInt(&main_success);
	if (workflow_success && main_success)
		splitter_->setSizes(sizes << width_workflow << width_mainpanel);
}

/**
 * @brief Remove all GUI elements.
 * @details This function clears the main GUI itself, i. e. all panels read from XML, including
 * the workflow panels.
 */
void MainPanel::clearGuiElements()
{
	section_tab_->clear();
	workflow_panel_->clearXmlPanels();
}

/**
 * @brief Reset the whole GUI to default values.
 */
void MainPanel::clearGui(const bool &set_default)
{
	clearDynamicPanels<Replicator>(); //clear special panels (the ones that can produce INI keys)
	clearDynamicPanels<Selector>(); //cf. notes there
	const QList<Atomic *> panel_list( section_tab_->findChildren<Atomic *>() ); //clear all others
	for (auto &panel : panel_list)
		panel->clear(set_default);
}

/**
 * @brief Clear panels which can add/remove children at will.
 * @details This function clears panels that have the ability to create and more importantly delete
 * an arbitrary number of child panels. Those stand for a group of INI keys rather than a single one,
 * and can create child panels for INI keys such as "STATION1, STATION2, ...".
 */
template <class T>
void MainPanel::clearDynamicPanels()
{
	/*
	 * Fetching a list of suitable panels and iterating through them does not work out
	 * because clearing one dynamic panel can delete another one, which means the pointers
	 * we receive could be invalidated.
	 * So, we look for the first dynamic panel with children, clear that, and start the
	 * search again until there are no more dynamic panels with children found.
	 */
	const QList<T *> panel_list( section_tab_->findChildren<T *>() );
	for (auto &pan : panel_list) {
		//the panels themselves are not deleted, so we stop if it's empty:
		if (pan->count() > 0) {
			pan->clear();
			clearDynamicPanels<T>(); //repeat until all dynamic panels are empty
			return;
		}
	}
}
//this is a list of panels that can produce and delete an aribtrary number of child panels:
template void MainPanel::clearDynamicPanels<Selector>();
template void MainPanel::clearDynamicPanels<Replicator>();
