/*****************************************************************************/
/*  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 "Textfield.h"
#include "Label.h"
#include "src/main/constants.h"
#include "src/main/expressions.h"
#include "src/main/inishell.h"

#include <QDesktopServices>
#include <QFontMetrics>
#include <QHBoxLayout>

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

/**
 * @class Textfield
 * @brief Default constructor for a Textfield.
 * @details A Textfield is used to enter plain text.
 * @param[in] key INI key corresponding to the value that is being controlled by this Textfield.
 * @param[in] options XML node responsible for this panel with all options and children.
 * @param[in] no_spacers Keep a tight layout for this panel.
 * @param[in] parent The parent widget.
 */
Textfield::Textfield(const QString &section, const QString &key, const QDomNode &options, const bool &no_spacers,
    QWidget *parent) : Atomic(section, key, parent)
{
	/* label and text box */
	auto *key_label( new Label(QString(), QString(), options, no_spacers, key_, this) );
	setEmphasisWidget(key_label->label_);
	textfield_ = new QLineEdit; //single line text box
	connect(textfield_, &QLineEdit::textEdited, this, &Textfield::checkValue);

	/* action button */
	nav_button_ = new QToolButton; //a button that can pop up if the text has a certain format
	nav_button_->setStyleSheet("* {border: none}");
	QSize sz_label;
	sz_label.setWidth(static_cast<int>(fontMetrics().boundingRect(Cst::u_globe).width()) * 1.2);
	sz_label.setHeight(static_cast<int>(fontMetrics().boundingRect(Cst::u_globe).height()));
	nav_button_->setFixedSize(sz_label);
	connect(nav_button_, &QToolButton::clicked, this, &Textfield::navButtonClicked);

	/* layout of textbox plus button and the main layout */
	auto *nav_button_layout( new QHBoxLayout );
	nav_button_layout->addWidget(textfield_);
	nav_button_layout->addWidget(nav_button_);
	auto *textfield_layout( new QHBoxLayout );
	setLayoutMargins(textfield_layout);
	textfield_layout->addWidget(key_label, 0, Qt::AlignLeft);
	textfield_layout->addLayout(nav_button_layout);

	/* choose size of text box */
	const QString size( options.toElement().attribute("size") );
	if (size.toLower() == "small")
		textfield_->setMinimumWidth(Cst::tiny);
	else
		textfield_->setMinimumWidth(Cst::width_textbox_medium);
	if (!no_spacers && size.toLower() != "large") //"large": text box extends to window width
		textfield_layout->addSpacerItem(buildSpacer());

	addHelp(textfield_layout, options, no_spacers);
	this->setLayout(textfield_layout);
	setOptions(options);
}

/**
 * @brief Parse options for a Textfield from XML.
 * @param[in] options XML node holding the Textfield.
 */
void Textfield::setOptions(const QDomNode &options)
{
	validation_regex_ = options.toElement().attribute("validate");
	if (options.toElement().attribute("lenient").toLower() == "true")
		needs_prefix_for_evaluation_ = false; //always evaluate arithmetic expressions
	if (!options.toElement().attribute("placeholder").isEmpty())
		textfield_->setPlaceholderText(options.toElement().attribute("placeholder"));

	//user-set substitutions in expressions to style custom keys correctly:
	substitutions_ = expr::parseSubstitutions(options);
}

/**
 * @brief Event listener for changed INI values.
 * @details The "ini_value" property is set when parsing default values and potentially again
 * when setting INI keys while parsing a file.
 */
void Textfield::onPropertySet()
{
	const QString text_to_set( this->property("ini_value").toString() );
	if (ini_value_ == text_to_set)
		return;
	textfield_->setText(text_to_set);
	checkValue(text_to_set);
}

/**
 * @brief Perform checks on the entered text.
 * @details This function checks if the entered text stands for a recognized format such
 * as expressions (like the Number panel does), and if yes, styles the text box.
 * @param[in] text The current text to check.
 */
void Textfield::checkValue(const QString &text)
{

	setDefaultPanelStyles(text);
	static const int idx_full = 0; //regex index

	if (validation_regex_.isNull()) { //check for (arithmetic) expressions

		/* first, check for coordinates */
		static const QString regex_coord(R"(\Alatlon\s*\(([-\d\.]+)(?:,)\s*([-\d\.]+)((?:,)\s*([-\d\.]+))?\))");
		static const QRegularExpression rex_coord(regex_coord);
		const QRegularExpressionMatch coord_match(rex_coord.match(text));
		if (coord_match.captured(idx_full) == text && !text.isEmpty()) {
			nav_button_->setText(Cst::u_globe);
			nav_button_->setCursor(Qt::PointingHandCursor);
			nav_button_->setToolTip(tr("Show online map"));
			return;
		}

		bool evaluation_success;
		const bool is_expression = expr::checkExpression(text, evaluation_success,
			substitutions_, needs_prefix_for_evaluation_);
		const bool is_invalid = is_expression && !evaluation_success;
		setInvalidStyle(is_invalid);
		nav_button_->setText(is_invalid? Cst::u_warning : (is_expression? Cst::u_valid : ""));
		nav_button_->setCursor(is_expression? Qt::PointingHandCursor : Qt::ArrowCursor);
		nav_button_->setToolTip(is_expression?
			(is_invalid? tr("Entered expression has wrong format") : tr("Valid expression")) : "");
		nav_button_->setProperty("invalid", is_invalid? "true" : "false");
		nav_button_->style()->unpolish(nav_button_);
		nav_button_->style()->polish(nav_button_);

	} else { //check against regex specified in XML

			const QRegularExpression rex_xml(validation_regex_);
			const QRegularExpressionMatch xml_match(rex_xml.match(text));
			const bool is_invalid = xml_match.captured(idx_full) != text && !text.isEmpty();
			setInvalidStyle(is_invalid);
			nav_button_->setText(is_invalid? Cst::u_warning : (text.isEmpty()? "" : Cst::u_valid));
			nav_button_->setCursor(text.isEmpty()? Qt::ArrowCursor : Qt::PointingHandCursor); //mark as link
			nav_button_->setToolTip(text.isEmpty()? "" :
				(is_invalid? tr("Entered expression has wrong format") : tr("Valid expression")));
			//set "invalid" style on navigation button also:
			nav_button_->setProperty("invalid", is_invalid? "true" : "false");
			nav_button_->style()->unpolish(nav_button_);
			nav_button_->style()->polish(nav_button_);

	}
	setIniValue(text); //checks are just a hint - set anyway
}

/**
 * @brief Event listener for the action button.
 * @details Currently, the button is only shown for coordinates and will open the Browser
 * with a map link.
 */
void Textfield::navButtonClicked() //for now this button only pops up for coordinates
{
	if (nav_button_->text().isEmpty())
		return;
	if (nav_button_->text() != Cst::u_globe) { //"valid" resp. "invalid" icons
		getMainWindow()->loadHelp("Input panels 1", "help-textfield");
		return;
	}

	static const QString regex_coord(R"(\Alatlon\s*\(([-\d\.]+)(?:,)\s*([-\d\.]+)((?:,)\s*([-\d\.]+))?\))");
	static const QRegularExpression rex_coord(regex_coord);
	const QRegularExpressionMatch coord_match(rex_coord.match(textfield_->text()));
	static const int idx_full = 0;
	static const int idx_lat = 1;
	static const int idx_lon = 2;

	if (coord_match.captured(idx_full) == textfield_->text()) {
		const QChar latchar = coord_match.captured(idx_lat).toDouble() < 0? 'S' : 'N';
		const QChar lonchar = coord_match.captured(idx_lon).toDouble() < 0? 'W' : 'E';
		QString url("https://tools.wmflabs.org/geohack/geohack.php?params=");
		url += coord_match.captured(idx_lat) + "_" + latchar + "_";
		url += coord_match.captured(idx_lon) + "_" + lonchar;
		QDesktopServices::openUrl(url);
	}
}
