//SPDX-License-Identifier: GPL-3.0-or-later
/*****************************************************************************/
/*  Copyright 2020 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/main/SyntaxHighlighter.h>

#include <src/main/colors.h>
#include <src/main/constants.h>
#include <src/main/inishell.h>
#include <src/panels/Atomic.h>

/**
 * @class INISyntaxHighlighter
 * @brief Default constructor for a syntax highlighter used in the INI preview.
 * @param[in] textdoc Text document to handle syntax highlighting for.
 */
INISyntaxHighlighter::INISyntaxHighlighter(QTextDocument *textdoc) : QSyntaxHighlighter(textdoc)
{
	HighlightingRule rule;

	/*
	 * The SyntaxHighlighter parses a document for INI file syntax and checks the sections
	 * and keys against the curently loaded XML. Sections and keys that are not present
	 * in the XML are highlighted differently.
	 */

	/* unknown sections */
	QTextCharFormat format_section;
	format_section.setForeground(colors::getQColor("syntax_unknown_section"));
	format_section.setFontWeight(QFont::Bold);
	rule.pattern = QRegularExpression(R"(.*\)" + Cst::section_open + R"(.*\)" + Cst::section_close + R"(.*)");
	rule.format = format_section;
	rules_.append(rule);

	/* unknown keys */
	QTextCharFormat format_unknown_key;
	format_unknown_key.setForeground(colors::getQColor("syntax_unknown_key"));
	rule.pattern = QRegularExpression(R"(^\s*[\w|:|*]+(?=\s*=))"); //TODO: only :: but not :
	//TODO: collect and unify all regex handling (e. g. use the same here as in INIParser)
	rule.format = format_unknown_key;
	rules_.append(rule);

	/* INI values */
	QTextCharFormat format_value;
	format_value.setForeground(colors::getQColor("syntax_value"));
	rule.pattern = QRegularExpression(R"((?<=\=).*)");
	rule.format = format_value;
	rules_.append(rule);

	/* populate highlighter with known sections and keys */
	QTextCharFormat format_known_key;
	format_known_key.setForeground(colors::getQColor("syntax_known_key"));
	QTextCharFormat format_known_section;
	format_known_section.setForeground(colors::getQColor("syntax_known_section"));
	format_known_section.setFontWeight(QFont::Bold);

	QStringList added_sections_; //to avoid duplicated rules
	const QList<Atomic *> panel_list( getMainWindow()->findChildren<Atomic *>() );
	for (auto &panel : panel_list) {
		if (panel->property("no_ini").toBool()) //e. g. Groups / frames
			continue;
		QString section, key;
		(void) panel->getIniValue(section, key);
		key.replace("*", "\\*");
		rule.pattern = QRegularExpression("\\" + Cst::section_open + section + "\\" +
			Cst::section_close, QRegularExpression::CaseInsensitiveOption); //TODO: escape only if needed for the set char
		rule.format = format_known_section;
		if (added_sections_.indexOf(QRegExp(section, Qt::CaseInsensitive)) == -1) {
			rules_.append(rule);
			added_sections_.append(section);
		}
		rule.pattern = QRegularExpression(R"(^\s*)" + key + R"((=|\s))", QRegularExpression::CaseInsensitiveOption);
		rule.format = format_known_key;
		rules_.append(rule);
	}
	//for dynamic sections we do not need to iterate through the GUI because we have them in a list:
	QStringList *dynamic_sections( getMainWindow()->getControlPanel()->getSectionTab()->getDynamicSectionList() );
	for (auto &dyn_sec : *dynamic_sections) { //add all dynamic sections with numeric postfixes
		rule.pattern = QRegularExpression("\\" + Cst::section_open + dyn_sec + "[0-9]+" + "\\" +
			Cst::section_close, QRegularExpression::CaseInsensitiveOption);
		rule.format = format_known_section;
		rules_.append(rule);
	}

	/* comments */
	QTextCharFormat format_block_comment;
	format_block_comment.setForeground(colors::getQColor("syntax_comment"));
	rule.pattern = QRegularExpression(R"(^\s*[#;].*)");
	rule.format = format_block_comment;
	rules_.append(rule);
	rule.pattern = QRegularExpression(R"(([#;](?!$).*))");
	rules_.append(rule);

	/* coordinates */
	QTextCharFormat format_coordinate;
	format_coordinate.setForeground(colors::getQColor("coordinate"));
	rule.pattern = QRegularExpression(R"((latlon|xy)\s*\(([-\d\.]+)(?:,)\s*([-\d\.]+)((?:,)\s*([-\d\.]+))?\))");
	rule.format = format_coordinate;
	rules_.append(rule);

	/* equals sign */
	QTextCharFormat format_equal;
	format_equal.setForeground(Qt::black);
	rule.pattern = QRegularExpression(R"(=)");
	rule.format = format_equal;
	rules_.append(rule);
}

/**
 * @brief Send a chunk of text through the syntax highlighter.
 * @param[in] text The text to syntax-highlight.
 */
void INISyntaxHighlighter::highlightBlock(const QString &text)
{
	for (const HighlightingRule &rule : qAsConst(rules_)) {
		QRegularExpressionMatchIterator mit( rule.pattern.globalMatch(text) );
		while (mit.hasNext()) { //run trough regex matches and set the stored formats
			QRegularExpressionMatch match = mit.next();
			setFormat(match.capturedStart(), match.capturedLength(), rule.format);
		}
	}
}

/**
 * @class XMLSyntaxHighlighter
 * @brief Default constructor for a syntax highlighter used in Copytexts.
 * @param[in] textdoc Text document to handle syntax highlighting for.
 */
XMLSyntaxHighlighter::XMLSyntaxHighlighter(QTextDocument *textdoc) : QSyntaxHighlighter(textdoc)
{
	HighlightingRule rule;

	/* XML syntax highlighting */
	QTextCharFormat format_element; //XML elements
	format_element.setForeground(colors::getQColor("syntax_xml_element"));
	format_element.setFontWeight(QFont::Bold);
	rule.pattern = QRegularExpression("<[?\\s]*[/]?[\\s]*([^\\n][^>]*)(?=[\\s/>])");
	rule.format = format_element;
	rules_.append(rule);

	QTextCharFormat format_attribute; //XML attributes
	format_attribute.setForeground(colors::getQColor("syntax_xml_attribute"));
	format_attribute.setFontWeight(QFont::Bold);
	format_attribute.setFontItalic(true);
	rule.pattern = QRegularExpression("\\w+(?=\\=)");
	rule.format = format_attribute;
	rules_.append(rule);

	QTextCharFormat format_value; //XML values
	format_value.setForeground(colors::getQColor("syntax_xml_value"));
	rule.pattern = QRegularExpression("\"[^\\n\"]+\"(?=[?\\s/>])");
	rule.format = format_value;
	rules_.append(rule);

	QTextCharFormat format_comment; //XML comments
	format_comment.setForeground(colors::getQColor("syntax_xml_comment"));
	rule.pattern = QRegularExpression("<!--[^\\n]*-->");
	rule.format = format_comment;
	rules_.append(rule);

	QTextCharFormat format_keyword; //XML keywords
	format_keyword.setForeground(colors::getQColor("syntax_xml_keyword"));
	format_keyword.setFontWeight(QFont::Bold);
	rule.format = format_keyword;
	QStringList keyword_patterns;
	keyword_patterns << "<\\?" << "/>" << ">" << "<" << "</" << "\\?>";
	for (auto &pat : keyword_patterns) {
		rule.pattern = QRegularExpression( pat );
		rules_.append(rule);
	}
}

/**
 * @brief Send a chunk of text through the XML syntax highlighter.
 * @param[in] text The text to syntax-highlight.
 */
void XMLSyntaxHighlighter::highlightBlock(const QString &text)
{
	for (const HighlightingRule &rule : qAsConst(rules_)) {
		QRegularExpressionMatchIterator mit( rule.pattern.globalMatch(text) );
		while (mit.hasNext()) { //run trough regex matches and set the stored formats
			QRegularExpressionMatch match = mit.next();
			setFormat(match.capturedStart(), match.capturedLength(), rule.format);
		}
	}
}
