//SPDX-License-Identifier: GPL-3.0-or-later
/*****************************************************************************/
/*  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 <src/panels/dynamic_panels.h>
#include <src/panels/Replicator.h>
#include <src/panels/Selector.h>
#include <src/main/constants.h>
#include <src/main/inishell.h>

#include <QStringList>

#ifdef DEBUG
	#include <iostream>
#endif //def DEBUG

/**
 * @brief Extract the character and numeric parts of a Replicator's key.
 * @details For example, they key "FILTER1::EXPRESSION" would be split up
 * into ("FILTER", 1, "EXPRESSION").
 * @param full_key They INI key to split up.
 * @param pre String before the number.
 * @param extracted_no Numeric part of the key.
 * @param post String after the number.
 * @param rightmost If true, search number from the right. If false, search from
 * the left (currently unused).
 * @return True if a number could be extracted and thus the key could be split up.
 */
bool splitRepKey(const QString &full_key, QString &pre, int &extracted_no, QString &post,
	const bool &rightmost)
{
	pre = QString();
	post = QString();
	int extracted = 0;
	bool found = false;

	if (rightmost) {
		for (int rpos = full_key.length() - 1; rpos > -1; --rpos) { //start anchor at last char and iterate to 1st
			for (int len = 1; len <= rpos + 1; ++len) { //1 to max length of candidate (start of word to current anchor)
				const QString candidate( full_key.mid(rpos - len + 1, len));
				bool success;
				const int no = candidate.toInt(&success);
				if (success) { //str --> int conversion successful for this substring
					extracted = no;
					pre = full_key.mid(0, rpos - len + 1);
					post = full_key.mid(rpos + 1);
					found = true; //don't exit yet - we try if it is a multi digit number
				} else {
					break;
				}
			}
			if (found)
				break;
		} //endfor rpos
	} else { //leftmost
		for (int lpos = 0; lpos < full_key.length(); ++lpos) { //start anchor at 1st char and iterate to last
			for (int len = 1; len <= full_key.length() - lpos; ++len) { //1 to max length (current anchor to end of word)
				const QString candidate( full_key.mid(lpos, len));
				bool success;
				const int no = candidate.toInt(&success);
				if (success) {
					extracted = no;
					pre = full_key.mid(0, lpos - 1);
					post = full_key.mid(lpos + len + 1);
					found = true;
				} else {
					break;
				}
			}
			if (found)
				break;
		} //endfor lpos
	} //endif rightmost

	extracted_no = extracted;
	return found;

	//Up until v2.0.6 a different implementation relying on regular expressions was used.
	//The Replicator's regex was:
	//"^" + section.getName() + Cst::sep + R"(([\w\.]+)" + Cst::sep + R"()*(\w*?)(?=\d)(\d*)$)");
}

/**
 * @brief From a real INI key, get the name/key a Replicator would have
 * to be able to create this INI key.
 * @param full_key The key to get the parent's name for.
 * @param multi Replace all numbers in the string with Replicator tag
 * (special case) or only the rightmost one (default)?
 * @return The parent Replicator's name if this is a "dynamic" INI key,
 * and the key unchanged if not.
 */
QString getRepName(const QString &full_key, const bool &multi)
{
	QString rep_key( full_key );
	while (true) {
		//split key into character, numeric, character parts:
		QString pre, post;
		int no;
		const bool converted = splitRepKey(rep_key, pre, no, post);
		if (!converted)
			break;
		//replace number with special Replicator tag:
		rep_key = pre + "#" + post;
		if (!multi)
			break; //replace only rightmost number
	}
	return rep_key;
}

/**
 * @brief Find a Replicator in our GUI that could handle a given INI key.
 * @details This function looks for Replicators handling keys like "STATION#". If one is found,
 * it requests the Replicator to create a new panel for the key which will then be available
 * to the parent function that is currently looking for a suitable panel for the INI key.
 * @param[in] parent The parent panel or window to search.
 * @param[in] section Section to find.
 * @param[in] keyval Key/value pair to find.
 * @return True if the Replicator was activated and created child panels.
 */
bool prepareReplicator(QWidget *parent, const Section &section, const KeyValue &keyval)
{
	const QString id( section.getName() + Cst::sep + keyval.getKey() );

	QString pre, post; //unused here
	int no; //extract number from a Replicator's key
	splitRepKey(id, pre, no, post); //e. g. "ARG1::CUTOFF" --> 1

	QList<Replicator *> replicator_list = parent->findChildren<Replicator *>();
	bool found = false;
	for (int ii = 0; ii < replicator_list.count(); ++ii) {
		if (replicator_list.at(ii)->canSpawnPanel(id)) {
			replicator_list.at(ii)->setProperty("ini_value", QString::number(no));
			found = true; //allow multiple Replicator' to spawn it
		}
	} //endfor ii
	return found;
}

/**
 * @brief Find a Selector in our GUI that could handle a given INI key.
 * @details This function looks for Selectors handling keys like "%::COPY". If one is found,
 * it requests the Selector to create a new panel for the key which will then be available
 * to the parent function that is currently looking for a suitable panel for the INI key.
 * @param[in] parent The parent panel or window to search.
 * @param[in] section Section to find.
 * @param[in] keyval Key/value pair to find.
 * @return A list of panels for the INI key including the possibly newly created ones.
 */
QWidgetList prepareSelector(QWidget *parent, const Section &section, const KeyValue &keyval)
{
	const QString id( section.getName() + Cst::sep + keyval.getKey() );
	//INI key as it would be given in the file, i. e. with the parameters in place instead of the Selector's %:
	const QString regex_selector("^" + section.getName() + Cst::sep + R"(([\w\*\-\.]+)()" + Cst::sep + R"()(\w+?)([0-9]*$))");
	const QRegularExpression rex(regex_selector);
	const QRegularExpressionMatch matches( rex.match(id) );

	if (matches.captured(0) == id) { //it could be from a selector with template children
		static const size_t idx_parameter = 1;
		static const size_t idx_keyname = 3;
		static const size_t idx_optional_number = 4;
		const QString parameter( matches.captured(idx_parameter) );
		const QString key_name( matches.captured(idx_keyname) );
		const QString number( matches.captured(idx_optional_number) );

		QString gui_id( section.getName() + Cst::sep + "%" + Cst::sep + key_name + number );

		//try to find Selector:
		QList<Selector *> selector_list = parent->findChildren<Selector *>(Atomic::getQtKey(gui_id));
		if (selector_list.isEmpty()) //this would be the ID given in an XML, i. e. with a "%" standing for a parameter:
			gui_id = getRepName(gui_id); //a rep has already replaced it
		selector_list = parent->findChildren<Selector *>(Atomic::getQtKey(gui_id));

		for (int ii = 0; ii < selector_list.size(); ++ii)
			selector_list.at(ii)->setProperty("ini_value", parameter); //cf. notes in prepareReplicator
		//now all the right child widgets should exist and we can try to find again:
		const QString key_id( section.getName() + Cst::sep + keyval.getKey() ); //the one we were originally looking for

		return parent->findChildren<QWidget *>(Atomic::getQtKey(key_id));
	}
	return QWidgetList();
}

/**
 * @brief Retrieve all key names that are found in (a dynamic panel's) XML child nodes.
 * @details A Replicator's child panels are built on request, so when a new INI key
 * is encountered they may not exist yet. This is why we keep a list of keys a Replicator
 * is able to spawn panels for.
 * @param[in] section The parent panel's INI section.
 * @param[in] node The (child) node to recursively parse for "key" attributes in all panels.
 * @return A List of INI keys the Replicator can create on the fly.
 */
QStringList getAllKeys(const QString &section, const QDomNode &node)
{
	QStringList current_level;
	for (QDomElement el = node.firstChildElement(); !el.isNull(); el = el.nextSiblingElement()) {
		const QString current_key( el.attribute("key") );
		if (!current_key.isNull())
			current_level.append(section + Cst::sep + current_key);
		current_level.append( getAllKeys(section, el) ); //recursion on all child nodes
	}
	return current_level;

	/*
	 * In this function the complete XML node is parsed for "key" attributes.
	 * Running on a Replicator's child panel node for e. g. the collection of keys
	 * then represents the child panels this Replicator can spawn.
	 * This mode of operation to check if a certain Replicator should be triggered for
	 * some encountered INI key is for e. g. used for Replicators without a key, i. e.
	 * the special case of Replicators that are solemnly a GUI element to group settings.
	 * It is also used for another special case where multiple occurrences of '#' should
	 * be replaced at once, i. e. a single-level Replicator with the key "A#::B#" --> "A1::B1".
	 *
	 * This is enough for current SLF software but it is not generic enough to handle
	 * arbitrarily complex XML files. For this, instead of a list of keys we would need
	 * a tree with logic to check which level of Replicator/Selector in a nested array can produce
	 * exactly the right enumeration in a key like "%::A#::B#::C#::%", i. e. an inheritance
	 * system parallel to QWidget's.
	 * It is left as a TODO: when the need arises and should fit the current structure.
	 */
}

/**
 * @brief Check if an XML node contains child panels that are Replicators.
 * @param node The node to search.
 * @return True if the node contains child panels of type Replicator.
 */
bool hasReplicatorChildren(const QDomNode &node)
{
	bool has_rep = false;
	for (QDomElement el = node.firstChildElement(); !el.isNull(); el = el.nextSiblingElement()) {
		if (el.attribute("replicate").toLower() == "true")
			has_rep = true;
		else
			has_rep = hasReplicatorChildren(el); //recursion on all child nodes
		if (has_rep)
			break;
	}
	return has_rep;
}

/**
 * @brief Substitute values in keys and texts.
 * @details This is for child elements that inherit a property from its parent which should be
 * displayed (e. g. "TA" in "TA::FILTER1 = ..."). The function traverses through the whole child
 * tree recursively.
 * @param[in] parent_element Parenting XML node holding the desired values.
 * @param[in] replace String to replace.
 * @param[in] replace_with Text to replace the string with.
 */
void substituteKeys(QDomElement &parent_element, const QString &replace, const QString &replace_with, const bool &once)
{
	for (QDomElement element = parent_element.firstChildElement(); !element.isNull(); element = element.nextSiblingElement()) {
		QString key(element.attribute("key"));
		QString text(element.attribute("caption"));
		QString label(element.attribute("label"));

		if (element.tagName() == "parameter") {
			if (once) {	//only subsitutute first occurrence, leave the rest to subsequent panels (e. g. TA::FILTER#::ARG#):
				element.setAttribute("key", key.replace(key.indexOf(replace), 1, replace_with));
				element.setAttribute("caption", text.replace(key.indexOf(replace), 1, replace_with)); //for labels
				element.setAttribute("label", label.replace(label.indexOf(replace), 1, replace_with));
			} else {
				element.setAttribute("key", key.replace(replace, replace_with));
				element.setAttribute("caption", text.replace(replace, replace_with));
				element.setAttribute("label", label.replace(replace, replace_with));
			}
		}
		substituteKeys(element, replace, replace_with, once);
	} //endfor element
}


/**
 * @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 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( getMainWindow()->getControlPanel()->getSectionTab()->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;
		} //look for all "key" attributes - no matter where they are found
	}
}
//this is a list of panels that can produce and delete an aribtrary number of child panels:
template void clearDynamicPanels<Selector>();
template void clearDynamicPanels<Replicator>();
