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

#include "bootentry.h"

#include "../config.h"
#include "../kshutdown.h"
#include "../preferences.h"
#include "../udialog.h"
#include "../utils.h"

#include <QAbstractItemView>
#include <QDebug>

// BootEntry

// public:

void BootEntry::initFromConfig() {
	m_entryList.clear();
	m_problemList.clear();

// TEST: m_problemList << "<b>Problem 1</b>\nnew line" << "Problem 2";

	qDebug() << "BootEntry::initFromConfig()";

/* TODO: ?
	QFile grubEtcFile("/etc/default/grub");
	if (grubEtcFile.open(QFile::ReadOnly)) {
		QTextStream text(&grubEtcFile);
		QString line;
		while (!(line = text.readLine()).isNull()) {
			line = line.trimmed();

			if (line.isEmpty() || line.startsWith('#'))
				continue; // while

			auto pair = Utils::splitPair(line, '=', false);
			if (!pair.isEmpty()) {
				QString key = pair.first();
				QString value = pair.last();
				//qDebug() << "LINE: " << key << " = " << value;

				if ((key == "GRUB_DEFAULT") && (value == "0")) {
					m_problemList <<
						i18n("\"GRUB_DEFAULT\" must be set to \"saved\":") +
						"\n\n" +
						grubEtcFile.fileName().toHtmlEscaped();
				}
			}
		}
	}
*/

	#ifdef Q_OS_LINUX
	UPath grubEnvFile = "/boot/grub/grubenv";
	if (! Utils::isWritable(grubEnvFile)) {
// TODO: maybe suggest "sudo chmod o+w /boot/grub/grubenv" (with a security warning)
		m_problemList << i18n("No permissions to write \"%0\"")
			.arg(QString::fromStdString(grubEnvFile.string()).toHtmlEscaped());
	}
	#endif // Q_OS_LINUX

/* TODO: ?
	QFile grubConfigFile("/boot/grub/grub.cfg");
	if (!QFileInfo(grubConfigFile).isReadable()) {
		m_problemList <<
			i18n("No permissions to read:") +
			"\n\n" +
			grubConfigFile.fileName().toHtmlEscaped() +
			"\n\n" +
			i18n("Quick fix: %0").arg("sudo chmod 0604 \"" + grubConfigFile.fileName().toHtmlEscaped() + "\"") +
			"\n\n" +
			i18n("Warning: %0").arg(i18n("Making the file readable for non-root users may decrease the system security."));
	}

	if (grubConfigFile.open(QFile::ReadOnly)) {
		bool inSubmenu = false;
		QTextStream text(&grubConfigFile);
		QString menuEntryID = "menuentry ";
		QString line;
		while (!(line = text.readLine()).isNull()) {
			//qDebug() << "LINE: " << line;

// FIXME: the parser assumes that the grub.cfg formatting is sane
			if (inSubmenu && (line == "}")) { // use non-simplified line
				inSubmenu = false;

				continue; // while
			}

			line = line.simplified();

			if (!inSubmenu && line.startsWith("submenu ")) {
				inSubmenu = true;

				continue; // while
			}

			if (inSubmenu || !line.startsWith(menuEntryID))
				continue; // while
			
			line = line.mid(menuEntryID.length());
			
			QChar quoteChar;
			int quoteStart = -1;
			int quoteEnd = -1;
			for (int i = 0; i < line.length(); i++) {
				QChar c = line[i];
				if (quoteStart == -1) {
					quoteStart = i + 1;
					quoteChar = c;
				}
// FIXME: unescape, quotes (?)
				else if ((quoteEnd == -1) && (c == quoteChar)) {
					quoteEnd = i - 1;
					
					break; // for
				}
			}
			
			if ((quoteStart != -1) && (quoteEnd != -1) && (quoteEnd > quoteStart))
				line = line.mid(quoteStart, quoteEnd);
			else
				qCritical() << "Error parsing menuentry: " << line;

			if (line.contains("(memtest86+")) {
				qDebug() << "Skipping Boot Entry: " << line;
			}
			else {
				qDebug() << "Adding Boot Entry: " << line;
				m_entryList << line;
			}
		}
	}
	else {
		qCritical() << "Could not read GRUB menu entries: " << grubConfigFile.fileName();
	}
*/

	QString entryListValue = Config::bootEntries.getString();

	for (const QString &i : entryListValue.split('\n')) {
		QString entry = i.trimmed();

		if (entry.isEmpty())
			continue; // for

		if (entry.startsWith("'") && entry.endsWith("'")) {
			entry = entry.mid(1, entry.length() - 2);

			if (entry.isEmpty())
				continue; // for
		}

		//qDebug() << "ENTRY:" << entry;
		m_entryList << entry;
	}
}

bool BootEntry::setDefault(const QString &name) {
	qDebug() << "BootEntry::setDefault" << name;

	QString command = Config::bootSetEntryCommand
		.getString()
// TODO: common code - verifing user commands
		.trimmed();

	if (!command.isEmpty()) {
		qDebug() << "Using custom set boot entry command: " << command;

		command = command.replace("%entry", name);

		return Utils::runSplitCommand(command, QString(), true/* waitFor */);
	}

// TODO: support pkexec
	bool result = Utils::run("grub-reboot", { name }, QString(), true/* waitFor */);
/* TEST:
	QFile f("/boot/grub/grubenv");
	f.open(QFile::ReadOnly | QFile::Text);
	QTextStream env(&f);
	qDebug() << result << "/boot/grub/grubenv:" << env.readAll();
*/
	return result;
}

void BootEntry::showProblemList(QWidget *parent) {
	QString html = "";

	for (const QString &i : getProblemList()) {
		html += i;
		html += "<hr />";
	}

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

	UDialog::warning(parent, Utils::makeHTML(html));
}

// BootEntryAction

// public:

BootEntryAction::BootEntryAction(const QString &entry) :
	Action(entry, ""/* icon name */, "reboot"),
	m_entry(entry) {
}

bool BootEntryAction::onAction() {
	auto *action = RebootAction::self();

	if (!action->authorize(nullptr))//!!!!?
		return false;

	bool ok = BootEntry::setDefault(m_entry);

	action->activate();

	return ok;
}

// BootEntryComboBox

// public:

BootEntryComboBox::BootEntryComboBox() {
	setToolTip(i18n("Select an Operating System you want to use after restart"));
	view()->setAlternatingRowColors(true);

	updateBootEntryList();
}

void BootEntryComboBox::updateBootEntryList() {
	qDebug() << "BootEntryComboBox::updateBootEntryList()";//!!!!cleanup

	QString oldItem = currentText();

	clear();

	addItem('<' + i18n("Default") + '>');

	for (const QString &i : BootEntry::getEntryList()) {
		if (i.startsWith(BootEntry::SEPARATOR))
			insertSeparator(count());
		else
			addItem(i);
	}

	if (!oldItem.isEmpty())
		setCurrentText(oldItem);

	setEnabled(count() > 1);
}

// BootEntryMenu

// public:

BootEntryMenu::BootEntryMenu(QWidget *parent) :
	QMenu(parent) {

	connect(this, &QMenu::aboutToShow, [this] { onUpdate(); });
}

// private:

void BootEntryMenu::onUpdate() {
	clear();

// TODO: Utils::addTitle(this, QIcon(), i18n("System"));

	for (const QString &i : BootEntry::getEntryList()) {
		if (i.startsWith(BootEntry::SEPARATOR)) {
			addSeparator();
		}
		else {
			auto *a = new BootEntryAction(i);
			addAction(a->createConfirmAction(false));//!!!!test confirm
		}
	}

	addSeparator();

	auto *action = addAction(
		QIcon::fromTheme("configure"), i18n("Settings"),
		[] { Preferences::showDialog(Preferences::RESTART_PAGE_INDEX); }
	);
	action->setEnabled(!Utils::isRestricted("action/options_configure"));

	if (! BootEntry::getProblemList().isEmpty()) {
		addSeparator();

		addAction(
			QIcon::fromTheme("dialog-warning"), i18n("Problems Found"),
			[] { BootEntry::showProblemList(nullptr); }
		);
	}
}
