/*****************************************************************************/
/*  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 "RememberDialog.h"
#include "SectionTab.h"
#include "SectionButton.h"
#include "src/main/colors.h"
#include "src/main/inishell.h"
#include "src/main/os.h"
#include "src/main/settings.h"
#include "src/main/XMLReader.h"

#include <QTimer>

/**
 * @class ScrollPanel
 * @brief The main scroll panel controlling all dynamically built panels.
 * @param[in] section The section this panel corresponds to. It could be used for styling but is
 * unused otherwise.
 * @param[in] parent The parent widget (the main tab bar).
 */
ScrollPanel::ScrollPanel(const QString &tab_color, QWidget *parent)
	: QWidget(parent)
{
	/* create a scroll area and put a Group in it */
	main_area_ = new QScrollArea;
	main_area_->setWidgetResizable(true); //best to let the manager do its job - troubles ahead if we don't
	main_area_->setStyleSheet("QScrollArea {border: none}"); //we have one from the tabs already
	main_group_ = new Group(QString(), QString(), false, false, false, false, QString(), QString(), tab_color);
	main_area_->setWidget(main_group_);

	/* main layout */
	auto *layout( new QVBoxLayout );
	layout->addWidget(main_area_);
	this->setLayout(layout);
}

/**
 * @brief Retrieve the main grouping element of a scroll panel (there is one per tab).
 * @return The main Group of this scroll panel holding all widgets.
 */
Group * ScrollPanel::getGroup() const
{
	return this->main_group_;
}

/**
 * @class SectionTab
 * @brief A subclassed tab bar that the main GUI building recursion can be started on.
 * @param[in] parent The parent widget.
 */
SectionTab::SectionTab(QWidget *parent) : QTabWidget(parent)
{
	//do nothing
}

/**
 * @brief Retrieve the ScrollPanel of a section.
 * @details Sections are grouped in tab bar elements. For this, they are put in that specific
 * tab panel's ScrollPanel which can be retrieved by this function (to build top level panels into).
 * If the section/tab does not exist yet it is created.
 * @param[in] section The INI section for which to get the corresponding ScrollPanel.
 * @param[in] background_color Background color to give to a whole section that is being created.
 * @param[in] color Font color to give a new section.
 * @param[in] no_create Set to true when parsing an INI to "force" INI sections matching the XML.
 * @param[in] dynamic Is this a dynamic section that is being created?
 * @return The found or newly created ScrollPanel.
 */
ScrollPanel * SectionTab::getSectionScrollArea(const QString &section, const QString &background_color,
	const QString &color, const bool&no_create, const int &insert_idx)
{ //find or create section tab

	for (int ii = 0; ii < this->count(); ++ii) {
		if (QString::compare(os::cleanKDETabStr(this->tabBar()->tabText(ii)), section, Qt::CaseInsensitive) == 0)
			return qobject_cast<ScrollPanel *>(this->widget(ii)); //cast to access members
	}
	if (!no_create) {
		int new_index = insert_idx;
		if (insert_idx == -1)
			new_index = this->count();
		auto *new_scroll( new ScrollPanel(background_color) );
		this->insertTab(new_index, new_scroll, section);
		this->tabBar()->setTabTextColor(new_index, colors::getQColor(color));

		if (insert_idx != -1) {
			auto button = new SectionButton(SectionButton::minus, section);
			this->tabBar()->setTabButton(new_index, QTabBar::RightSide, button);
		} else if (isDynamicParent(section)) {
			auto button = new SectionButton(SectionButton::plus, section);
			this->tabBar()->setTabButton(new_index, QTabBar::RightSide, button);
		}
		return new_scroll;
	} else { //don't add sections for keys found in ini files
		return nullptr;
	}
}

/**
 * @brief Retrieve a section's ScrollPanel with known section tab index.
 * @param[in] index The section's index in the tab bar.
 * @return ScrollPanel for the section.
 */
ScrollPanel * SectionTab::getSectionScrollArea(const int &index)
{
	return qobject_cast<ScrollPanel *>(this->widget(index));
}

/**
 * @brief Mark and remember a certain section to be dynamic.
 * @param[in] section The ini section to mark as dynamic.
 * @param[in] node The section's complete XML node. Note that this only works
 * in dedicated <section> tags!
 */
void SectionTab::setSectionDynamic(const QString &section, const QDomNode &node)
{
	dynamic_sections_.push_back(section);
	dynamic_nodes_.push_back(node);
	dynamic_running_indices_.push_back(1); //1st of its kind
}

/**
 * @brief Check if a section is marked as dynamic and can therefore
 * spawn child sections.
 * @param[in] section The ini section name to check.
 * @return True if this is a parent section.
 */
bool SectionTab::isDynamicParent(const QString &section) const
{
	const int dyn_idx = dynamic_sections_.indexOf(QRegExp(section, Qt::CaseInsensitive));
	return (dyn_idx != -1);
}

/**
 * @brief Check if a section was derived from any dynamic parent section.
 * @details This function checks for the syntax string + number.
 * @param[in] section The ini section name to check.
 * @return True if this is a child section that was spawned by a parent section.
 */
int SectionTab::isDynamicChild(const QString &section) const
{
	for (int ii = 0; ii < dynamic_sections_.length(); ++ii) {
		if (isDynamicChildOf(section, dynamic_sections_.at(ii)))
		if (section.startsWith(dynamic_sections_.at(ii), Qt::CaseInsensitive))
			return true;
		//else: the correct one may still come later
	} //endfor ii
	return false;
}

/**
 * @brief Check if a section was derived from a specific parent section.
 * @param section The section to check.
 * @param parent_section Parent section to check.
 * @return True if this is child of the given parent section.
 */
bool SectionTab::isDynamicChildOf(const QString &section, const QString &parent_section) const
{
	if (!section.startsWith(parent_section, Qt::CaseInsensitive))
		return false;
	if (QString::compare(section, parent_section, Qt::CaseInsensitive) == 0)
		return false; //a section is not a child of itself

	const QString postfix( section.mid(parent_section.length()) ); //the potential number
	bool success;
	(void) postfix.toInt(&success);
	return success;
}

/**
 * @brief Count how many children a dynamic section currently has.
 * @param parent_section The parent dynamic section to check.
 * @return Number of currently opened child sections.
 */
int SectionTab::countChildren(const QString &parent_section) const
{
	int counter = 0;
	for (int ii = 0; ii < this->tabBar()->count(); ++ii) {
		if (isDynamicChildOf(os::cleanKDETabStr(this->tabBar()->tabText(ii)), parent_section))
			counter++;
	}
	return counter;
}

/**
 * @brief Get the parent section to a dynamic child section.
 * @param section The ini section to get parent section for.
 * @return The section's parent section.
 */
QString SectionTab::getParentOf(const QString &section)
{
	for (auto &parsec : dynamic_sections_) {
		if (isDynamicChildOf(section, parsec))
			return parsec;
	}
	return QString(); //not found
}

/**
 * @brief Take a parent section and clone it to a new one.
 * @param[in] section The section to clone.
 * @param[in] new_tab_name The name to give to the new tab. Leave empty to pick one.
 */
void SectionTab::spawnDynamicSection(const QString &section, const QString &new_tab_name)
{
	int index = getIndex(section, Qt::CaseInsensitive);
	const int dyn_idx = dynamic_sections_.indexOf(QRegExp(section, Qt::CaseInsensitive));

	bool found = false;
	for (int ii = index + 1; ii < this->count(); ++ii) {
		const QString postfix( os::cleanKDETabStr(
			this->tabBar()->tabText(ii)).mid(section.length()) ); //the potential number
		bool success = true;
		if (!postfix.isEmpty()) //section is the start of a tab name --> check if remainder is a number:
			(void) postfix.toInt(&success); //(so that e. g. "InputEditing" is not seen as child of "Input")
		if (!os::cleanKDETabStr(this->tabBar()->tabText(ii)).startsWith(section, Qt::CaseInsensitive) || !success) {
			index = ii - 1; //order after all dynamic sections of this kind
			found = true;
			break;
		}
	}
	if (!found) //parent panel is the last one --> append
		index = this->count() - 1;

	QString new_name( new_tab_name );
	if (new_name.isNull()) { //from a + button click (i. e. not from an INI file) --> pick new name
		new_name = (section + "%1").arg(dynamic_running_indices_.at(dyn_idx));
		dynamic_running_indices_[dyn_idx]++;
	}
	auto dyn_scroll = getSectionScrollArea(new_name, QString(), "normal", false, index + 1);
	recursiveBuild(dynamic_nodes_.at(dyn_idx), dyn_scroll->getGroup(), new_name);
	if (new_tab_name.isNull()) //do not focus tab from ini read-in
		this->setCurrentIndex(index + 1);
}

/**
 * @brief Try for a currently unknown section if it could be a child section,
 * and if so, create it.
 * @param[in] section The ini section to check
 * @return A new tab for the new section, or nullptr if this is not a dynamic child.
 */
ScrollPanel * SectionTab::tryDynamicSection(const QString &section)
{
	for (int ii = 0; ii < dynamic_sections_.length(); ++ii) {
		if (section.startsWith(dynamic_sections_.at(ii), Qt::CaseInsensitive)) {
			const QString postfix( section.mid(dynamic_sections_.at(ii).length()) );
			bool success;
			const int sec_no = postfix.toInt(&success);
			if (success) { //we use the 1st one that fits
				spawnDynamicSection(dynamic_sections_.at(ii), section);
				if (sec_no > dynamic_running_indices_.at(ii))
					dynamic_running_indices_[ii] = sec_no + 1; //start at highest index on "+" click
				return getSectionScrollArea(section);
			}
		}
	} //endfor ii
	return nullptr;
}

/**
 * @brief Remove a dynamic child section.
 * @param[in] section The ini section to remove.
 */
void SectionTab::removeDynamicSection(const QString &section)
{
	if (getSetting("user::tabs::warn_unsaved_tab", "value") == "TRUE") {
		const QString msg( tr("INI settings will be lost") );
		const QString informative( tr("If you close this tab, all of its settings will be lost.") );
		const QString details( tr(
			"If you did not make any changes to the default values of this tab/section, you can safely ignore this message.") );
		const QFlags<QMessageBox::StandardButton> buttons( QMessageBox::Ok | QMessageBox::Cancel );
		RememberDialog msgNotSaved("user::tabs::warn_unsaved_tab", msg, informative, details, buttons, this);

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

	const QString parent_section( getParentOf(section) );
	if (countChildren(parent_section) == 1) {
		const int dyn_idx = dynamic_sections_.indexOf(parent_section, Qt::CaseInsensitive);
		dynamic_running_indices_[dyn_idx] = 1;
	}
	for (int ii = 0; ii < this->tabBar()->count(); ++ii) {
		if (os::cleanKDETabStr(this->tabBar()->tabText(ii)) == section) {
			this->widget(ii)->deleteLater();
			break;
		}
	} //endfor ii
}

/**
 * @brief Select tab specified by its caption.
 * @param[in] tab_name The tab/section to select.
 * @return True if the tab could be selected.
 */
bool SectionTab::showTab(const QString &tab_name)
{
	const int idx = getIndex(tab_name);
	if (idx == -1) //not found
		return false;
	this->setCurrentIndex(idx);
	return true;
}

/**
 * @brief Highlight a certain frame within a tab.
 * @details For this to work the frame must have a "key" property to reference
 * in the XML.
 * @param[in] tab_name The tab/section the frame is in.
 * @param[in] frame_name The frame's name to show and highlight.
 * @return
 */
bool SectionTab::showFrame(const QString &tab_name, const QString &frame_name)
{

#ifdef DEBUG
	const bool success = showTab(tab_name);
	if (!success)
		qDebug() << "Help section does not exist:" << tab_name;
#else
	(void) showTab(tab_name);
#endif //def DEBUG

	bool found = false;
	const auto parent = this->getSectionScrollArea(tab_name);
	const QList<Group *> panel_list( parent->findChildren<Group *>() );
	for (auto &panel : panel_list) { //run through panels in section
		QString section, key;
		(void) panel->getIniValue(section, key);
		if (QString::compare(key, frame_name, Qt::CaseInsensitive) == 0) {
			const QString id( section + Cst::sep + key );
			//this ID must be set in the help XML:
			auto wid = getMainWindow()->findChild<QGroupBox *>("_primary_" + Atomic::getQtKey(id));
			parent->ensureWidgetVisible(wid); //scroll the ScrollBar until visible

			if (wid->property("stylesheet").toString().isEmpty()) //nothing replaced yet --> store original
				wid->setProperty("stylesheet", wid->styleSheet()); //(to reset with timer)

			QString stylesheet_copy( wid->styleSheet() );
			wid->setStyleSheet(stylesheet_copy.replace( //flash frame border color
				colors::getQColor("frameborder").name(QColor::HexRgb).toLower(),
				colors::getQColor("important").name()));
			QTimer::singleShot(Cst::msg_short_length, this,
				[=]{ wid->setStyleSheet(wid->property("stylesheet").toString()); } );
			found = true;
			break;
		}
	} //endfor panel_list
	this->raise();
	return found;
}

/**
 * @brief Get tab index from a section name.
 * @param[in] section Section name to retrieve the index for.
 * @param[in] sensitivity Case sensitive search?
 * @return Tab index of the section, or -1 if not found.
 */
int SectionTab::getIndex(const QString &section, const Qt::CaseSensitivity &sensitivity)
{
	for (int ii = 0; ii < this->count(); ++ii) {
		if (QString::compare(os::cleanKDETabStr(this->tabBar()->tabText(ii)), section, sensitivity) == 0)
			return ii;
	}
	return -1; //not found
}

/**
 * @brief Clear all tab pages.
 * @details Unlike the native QTabWidget::clear(), this here also deletes the pages.
 */
void SectionTab::clear()
{
	while (this->count() > 0) {
		this->widget(0)->deleteLater();
		this->removeTab(0);
	}
	dynamic_sections_.clear();
	dynamic_nodes_.clear();
	dynamic_running_indices_.clear();
}

/**
 * @brief Remove all dynamic child tabs.
 */
void SectionTab::clearDynamicTabs()
{
	for (int ii = this->count() - 1; ii > -1; --ii) {
		if (isDynamicChild(os::cleanKDETabStr(this->tabBar()->tabText(ii)))) {
			this->widget(ii)->deleteLater();
			this->removeTab(ii);
		}
	}
	for (int ii = 0; ii < dynamic_running_indices_.count(); ++ii)
		dynamic_running_indices_[ii] = 1;
}
