// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2011 Konrad Twardowski

#include "udialog.h"

#include "commandline.h"
#include "config.h"
#include "utils.h"
#include "uwidgets.h"
#include "version.h"

#include <QMessageBox>
#include <QPushButton>
#include <QScreen>
#include <QStyle>
#include <QTabWidget>
#include <QTimeZone>

// public:

UDialog::UDialog(QWidget *parent, const QString &windowTitle, const bool simple) :
	QDialog(parent) {

	setWindowFlag(Qt::WindowContextHelpButtonHint, false); // hide unused titlebar "?" button
	setWindowTitle(windowTitle);

	if (simple) {
		m_buttonBox = new QDialogButtonBox(QDialogButtonBox::Close);
		m_acceptButton = m_buttonBox->button(QDialogButtonBox::Close);
	}
	else {
		m_buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
		m_acceptButton = m_buttonBox->button(QDialogButtonBox::Ok);
	}

	connect(m_buttonBox, SIGNAL(accepted()), SLOT(accept()));
	connect(m_buttonBox, SIGNAL(rejected()), SLOT(reject()));

	m_mainLayout = UWidgets::newVBoxLayout({ }, 0_px);

	auto *buttonBoxLayout = dynamic_cast<QHBoxLayout *>(m_buttonBox->layout());
	if (buttonBoxLayout != nullptr) {
		buttonBoxLayout->setSpacing(10_px);
		Utils::setMargin(buttonBoxLayout, 0_px);
	}

	m_bottomWidget = new QWidget();
	m_bottomWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);

	QPalette p = m_bottomWidget->palette();
	QColor bg = p.color(QPalette::Window);
	p.setColor(QPalette::Window, Utils::isDark(bg) ? bg.lighter(114) : bg.darker(104));
	m_bottomWidget->setAutoFillBackground(true);
	m_bottomWidget->setPalette(p);

	m_bottomLayout = UWidgets::newHBoxLayout(m_bottomWidget, { m_buttonBox }, 10_px, 10_px);
	m_bottomLayout->insertStretch(0);

	m_infoWidget = new InfoWidget();

	// root layout
	UWidgets::newVBoxLayout(this, { m_mainLayout, m_infoWidget, m_bottomWidget }, 0_px);

	if (simple)
		m_acceptButton->setDefault(true);
}

void UDialog::moveToCenterOfScreen() {
	QSize dialogSize = sizeHint();

// TODO: #qt5.14 QScreen *screen = dialog->screen();
	QScreen *screen = QApplication::primaryScreen();

	QRect desktopRect = screen->availableGeometry();
	move(
		(desktopRect.width() / 2) - (dialogSize.width() / 2) + desktopRect.x(),
		(desktopRect.height() / 2) - (dialogSize.height() / 2) + desktopRect.y()
	);
}

void UDialog::setWindowSize(const WindowSize windowSize) {
	double f = 0;
	switch (windowSize) {
		case WindowSize::FIXED:
			layout()->setSizeConstraint(QLayout::SetFixedSize);
			//resize(sizeHint());

			return;

		case WindowSize::LARGE:
			f = 0.7;
			break;
	}

	auto *screen = QApplication::primaryScreen();
	auto size = screen->availableSize();

	int w = std::clamp(static_cast<int>(size.width() * f), 800_px, 1200_px);
	int h = std::clamp(static_cast<int>(size.height() * f), 600_px, 700_px);

	resize(w, h);
}

// messages

void UDialog::error(QWidget *parent, const QString &text, const Qt::TextFormat format) {
	UMessageBuilder message(UMessageBuilder::Type::Error);
	message.text(text, format);
	message.exec(parent);
}

void UDialog::info(QWidget *parent, const QString &text, const Qt::TextFormat format) {
	UMessageBuilder message(UMessageBuilder::Type::Info);
	message.text(text, format);
	message.exec(parent);
}

void UDialog::warning(QWidget *parent, const QString &text, const Qt::TextFormat format) {
	UMessageBuilder message(UMessageBuilder::Type::Warning);
	message.text(text, format);
	message.exec(parent);
}

// misc

#ifdef KS_PURE_QT
void UDialog::about(QWidget *parent) {
	auto *topLabel = new ULabel();
	topLabel->setMarginAndSpacing(0_px, 20_px);
	topLabel->iconLabel()->setAlignment(Qt::AlignCenter);

	QIcon logo = QApplication::windowIcon();
	topLabel->setIcon(logo, 64_px);

	QString titleText = "KShutdown™ " + QApplication::applicationVersion();
	QString buildText = KS_RELEASE_DATE;
	if (Config::isPortable())
		buildText += " | Portable";
	topLabel->setText(Utils::makeHTML(
		"<h1 style=\"margin: 0px; white-space: nowrap\">" + titleText + "</h1>" +
		"<b style=\"white-space: nowrap\">" + buildText + "</b>"
	));
	topLabel->textLabel()->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
	topLabel->textLabel()->setTextInteractionFlags(Qt::TextBrowserInteraction);

	QUrl homePage = QUrl(KS_HOME_PAGE);

	auto *aboutLabel = new QLabel();
	aboutLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
	aboutLabel->setOpenExternalLinks(true);
	aboutLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
	aboutLabel->setText(Utils::makeHTML(
		CLI::getArgs()->applicationDescription() + "<br />" +
		Utils::makeLink(aboutLabel, homePage.toString(), homePage.host())
	));

	auto *aboutTab = new QWidget();
	auto *aboutLayout = UWidgets::newVBoxLayout(
		aboutTab,
		{ topLabel, aboutLabel },
		20_px, 20_px
	);
	aboutLayout->addStretch();

	auto *licenseLabel = new QLabel();

QString licenseText =
	KS_COPYRIGHT + "<br>" +
	"<br>";

licenseText += QString(
R"(This program is <b>free software</b>; 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 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but <b>WITHOUT ANY WARRANTY</b>; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
%1 for more details. (%2))")
	.arg(Utils::makeLink(licenseLabel, "https://www.gnu.org/licenses/old-licenses/gpl-2.0.html", "GNU General Public License"))
	.arg(Utils::makeLink(licenseLabel, "https://www.tldrlegal.com/license/gnu-general-public-license-v2", "tl;dr"));

	licenseText = licenseText.replace("\n", "<br />");

	licenseLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop);
	licenseLabel->setOpenExternalLinks(true);
	licenseLabel->setText(Utils::makeHTML(licenseText));
	licenseLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);

	auto *licenseTab = new QWidget();
	UWidgets::newVBoxLayout(
		licenseTab,
		{ licenseLabel },
		20_px, 20_px
	);
// TODO: https://sourceforge.net/p/kshutdown/wiki/Credits/

	auto dialog = std::make_unique<UDialog>(parent, i18n("About"), true);

	auto *aboutQtButton = new QPushButton(i18n("About Qt"));
	dialog->m_bottomLayout->insertWidget(0, aboutQtButton);
	connect(aboutQtButton, SIGNAL(clicked()), qApp, SLOT(aboutQt()));

	auto *tabs = new QTabWidget();
	tabs->addTab(aboutTab, i18n("About"));
	tabs->addTab(licenseTab, i18n("License"));
	dialog->mainLayout()->addWidget(tabs);
	Utils::setMargin(dialog->mainLayout(), 10_px);
	tabs->setFocus();

	if (Utils::isAntique())
		dialog->infoWidget()->setText(Utils::getAntiqueMessage(), InfoWidget::Type::Info);

	dialog->setWindowSize(WindowSize::FIXED);
	dialog->exec();
}
#endif // KS_PURE_QT

void UDialog::largeWidget(QWidget *parent, QWidget *widget, const QString &windowTitle, const QList<QPushButton *> &actionButtonList) {
	auto dialog = std::make_unique<UDialog>(parent, windowTitle, true);
	auto *closeButton = dialog->m_acceptButton;

	if (actionButtonList.count() > 0) {
		for (auto *actionButton : actionButtonList)
			dialog->m_bottomLayout->insertWidget(0, actionButton);

		dialog->m_bottomLayout->insertSpacing(0, 10_px);
	}

	class SearchLineEdit: public QLineEdit {
	public:
		QColor oldHightlight;
		QColor oldHightlightedText;
		QPalette widgetPalette;
		QWidget *widget;

		SearchLineEdit(QWidget *widget) : widget(widget) {
			this->widgetPalette = widget->palette();
			this->oldHightlight = this->widgetPalette.color(QPalette::Highlight);
			this->oldHightlightedText = this->widgetPalette.color(QPalette::HighlightedText);

			this->setClearButtonEnabled(true);
			this->setPlaceholderText(i18n("Search"));
		}
		void changePalette(const bool old) {
			this->widgetPalette.setColor(QPalette::Highlight,       old ? this->oldHightlight       : QColorConstants::Svg::fuchsia);
			this->widgetPalette.setColor(QPalette::HighlightedText, old ? this->oldHightlightedText : Qt::white);
			this->widget->setPalette(this->widgetPalette);
		}
	protected:
		virtual void focusOutEvent(QFocusEvent *event) override {
			QLineEdit::focusOutEvent(event);

			this->changePalette(true);
		}
	};

	auto *searchLineEdit = new SearchLineEdit(widget);

	connect(searchLineEdit, &QLineEdit::returnPressed, [closeButton, searchLineEdit, widget]() {
		closeButton->setDefault(false); // HACK: do not close window on Enter press

		bool found = false;
		QString texToFind = searchLineEdit->text();
		auto *plainTextEdit = dynamic_cast<QPlainTextEdit *>(widget);
		if (plainTextEdit != nullptr) {
			if (! plainTextEdit->find(texToFind)) {
				plainTextEdit->moveCursor(QTextCursor::Start); // wrap around
				found = plainTextEdit->find(texToFind);
			}
			else {
				found = true;
			}
		}
		else {
			auto *textEdit = dynamic_cast<QTextEdit *>(widget);
			if (textEdit != nullptr) {
				if (! textEdit->find(texToFind)) {
					textEdit->moveCursor(QTextCursor::Start); // wrap around
					found = textEdit->find(texToFind);
				}
				else {
					found = true;
				}
			}
		}

		searchLineEdit->changePalette(! found);
	});

	class DialogEventFilter: public QObject {
	public:
		SearchLineEdit *searchLineEdit;
		DialogEventFilter(QObject *parent) : QObject(parent) { }
	protected:
		bool eventFilter(QObject *object, QEvent *event) override {
			if (event->type() == QEvent::KeyPress) {
				auto *ke = static_cast<QKeyEvent *>(event);
				if (
					ke->matches(QKeySequence::Find) || // Ctrl+F
					((ke->key() == Qt::Key_Slash) && (ke->modifiers() == Qt::NoModifier))
				) {
					this->searchLineEdit->setFocus();
					this->searchLineEdit->selectAll();

					return true;
				}
			}

			return QObject::eventFilter(object, event);
		}
	};

	auto *dialogFilter = new DialogEventFilter(dialog.get());
	dialogFilter->searchLineEdit = searchLineEdit;
	dialog->installEventFilter(dialogFilter);

	dialog->m_bottomLayout->insertWidget(0, searchLineEdit);
	dialog->mainLayout()->addWidget(widget);

	widget->setFocus();

	dialog->setSizeGripEnabled(true);
	dialog->setWindowSize(UDialog::WindowSize::LARGE);
	dialog->exec();
}

void UDialog::plainText(QWidget *parent, const QString &text, const QString &windowTitle) {
	auto *textEdit = Utils::newTextEdit(text);
	textEdit->setReadOnly(true);

	largeWidget(parent, textEdit, windowTitle, { });
}

void UDialog::systemInfo(QWidget *parent) {
	auto *primaryScreen = QApplication::primaryScreen();

	QString configValue = Config::getPath();
	if (configValue.startsWith("\\HKEY_CURRENT_USER\\"))
		configValue = "Registry: " + configValue.toHtmlEscaped();
	else
		configValue = makeHTMLLink(configValue);

	QList<QStringList> rows {
		{ Utils::getApplicationVersionInfo() },
		{ Utils::getQtVersionInfo() },

		{ },

		{ "Build Type", Utils::getBuildTypeInfo() },

		{ },

		{ "Desktop", Utils::getDesktopInfo() },
		{
			"Style",
			#if QT_VERSION < QT_VERSION_CHECK(6, 1, 0)
			QApplication::style()->objectName()
			#else
			QApplication::style()->name()
			#endif // QT_VERSION
		},
		{ "Icon Theme", QIcon::themeName() },
		{ "Platform", QApplication::platformName() },
		{ "Screen", QString("%1 x %2 | Scale=%3x")
			.arg(primaryScreen->size().width())
			.arg(primaryScreen->size().height())
			.arg(primaryScreen->devicePixelRatio()) },

		{ },

		{ "System", Utils::getSystemInfo() },
		{ "Locale", QString("%1 | %2 | %3")
			.arg(QLocale::system().name())
			.arg(QLocale::system().bcp47Name())
			.arg(QString(QTimeZone::systemTimeZoneId())) },

		{ },

		{ "Config", UWidgets::CELL_HTML + configValue },
		{ "Data", UWidgets::CELL_HTML + makeHTMLLink(Config::getDataDirectory()) },
		{ "Program", UWidgets::CELL_HTML + makeHTMLLink(QApplication::applicationFilePath()) },
	};

	#ifdef Q_OS_LINUX
	QFileInfo appImage = Utils::getAppImageInfo();

	if (appImage.isFile())
		rows += { "AppImage", UWidgets::CELL_HTML + makeHTMLLink(appImage.filePath()) };
	#endif // Q_OS_LINUX

	auto *htmlWidget = UWidgets::newHTMLTableView(rows);

	largeWidget(parent, htmlWidget, i18n("System Information"), { });
}

// private:

QString UDialog::makeHTMLLink(const QString &pathString) {
	UPath path = pathString.toStdString();
	bool isDir = std::filesystem::is_directory(path);

	QString dir = isDir
		? QString::fromStdString(path.string())
		: QString::fromStdString(path.parent_path().string());

	QUrl url = QUrl::fromLocalFile(dir);

	//QString html = Utils::makeLink(widget, url.toString(), dir); - link styled by UWidgets::newHTMLTable
	QString html = QString("<a href=\"%1\">%2</a>")
		.arg(url.toString().toHtmlEscaped())
		.arg(dir.toHtmlEscaped());

	if (! isDir)
		html += "/" + QString::fromStdString(path.filename().string()).toHtmlEscaped();

	return html;
}

// UMessageBuilder

// public:

bool UMessageBuilder::exec(QWidget *parent) {
	if (m_showVar && ! m_showVar->getBool())
		return true;

	auto mb = std::make_unique<QMessageBox>(parent);

	switch (m_type) {
		case Type::Error:
			mb->setIcon(QMessageBox::Critical);
			mb->setStandardButtons(QMessageBox::Ok);
			mb->setWindowTitle(i18n("Error"));
			break;
		case Type::Info:
			mb->setIcon(QMessageBox::Information);
			mb->setStandardButtons(QMessageBox::Ok);
			mb->setWindowTitle(i18n("Information"));
			break;
		case Type::Warning:
			mb->setIcon(QMessageBox::Warning);
			mb->setStandardButtons(QMessageBox::Ok);
			mb->setWindowTitle(i18n("Warning"));
			break;
		case Type::Question:
			mb->setIcon(QMessageBox::Question);
			mb->setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
			mb->setWindowTitle(i18n("Confirm"));
			break;
	}

	auto *okButton = mb->button(QMessageBox::Ok);

	if (! m_okText.isEmpty()) {
		okButton->setText(m_okText);
	}

	if (! m_icon.isNull()) {
		int size = qApp->style()->pixelMetric(QStyle::PM_MessageBoxIconSize);
		mb->setIconPixmap(m_icon.pixmap(size));

		okButton->setIcon(m_icon);
	}

	mb->setDefaultButton(m_cancelDefault ? QMessageBox::Cancel : QMessageBox::Ok);

	mb->setTextFormat(m_textFormat);
	mb->setText(m_text);

	if (! m_title.isEmpty())
		mb->setWindowTitle(m_title);

	QCheckBox *doNotShowCheckBox;
	if (m_showVar) {
		doNotShowCheckBox = new QCheckBox(i18n("Do not show this message again"));
		mb->setCheckBox(doNotShowCheckBox);
	}
	else {
		doNotShowCheckBox = nullptr;
	}

	bool result = mb->exec() == QMessageBox::Ok;

	if ((doNotShowCheckBox != nullptr) && doNotShowCheckBox->isChecked()) {
		m_showVar->setBool(false);
		m_showVar->write();
	}

	return result;
}
