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

#include "extras.h"

#include "../commandline.h"
#include "../config.h"
#include "../mainwindow.h"
#include "../password.h"
#include "../utils.h"
#include "../uwidgets.h"

#include <QDebug>
#include <QDesktopServices>
#include <QDir>
#include <QMessageBox>
#include <QSettings>
#include <QStandardPaths>
#include <QUrl>

// Extras

// public

QString Extras::getStringOption() {
	return (m_selectedCommandAction != nullptr) ? m_selectedCommandAction->m_command : "";
}

void Extras::setStringOption(const QString &option) {
	if (option.isEmpty()) {
		setCommandAction(nullptr);
	}
	else {
		QFileInfo fileInfo(option);
		setCommandAction(createCommandAction(fileInfo, false));
	}
}

void Extras::initContainerWidget() {
	auto *layout = makeVBoxLayout();
	layout->addWidget(m_menuButton);

	QString example = "kdialog --passivepopup \"Foo Bar\"";

	m_quickCommandEdit = new UCommandEdit();
	m_quickCommandEdit->setCommand(m_quickCommandAction->m_command);
	m_quickCommandEdit->setCompleter({ example });
	m_quickCommandEdit->setExample(example);
	m_quickCommandEdit->setLabel(i18n("Quick Command:"));

	m_quickCommandEdit->hide();
	layout->addWidget(m_quickCommandEdit);
}

bool Extras::onAction() {
	bool ok = false;

	if (m_selectedCommandAction == m_quickCommandAction) {
		m_quickCommandAction->m_command = m_quickCommandEdit->command();
		ok = Utils::runSplitCommand(m_quickCommandAction->m_command);

		if (! ok)
			m_error = i18n("Cannot execute \"Extras\" command");

		return ok;
	}

	QFileInfo fileInfo(m_selectedCommandAction->m_command);
	QString path = fileInfo.filePath();
	QString suffix = fileInfo.suffix();

	if (suffix == "desktop") {
		auto settings = Config::openDesktopEntry(UPath(path.toStdString()));
// FIXME: command arguments
		QString exec = settings->value("Exec", "").toString();

		//qDebug() << "Exec:" << exec;
		if (!exec.isEmpty()) {
			QString workingDirectory = settings->value("Path", "")
				.toString();

			ok = Utils::runSplitCommand(exec, workingDirectory);
		}
// TODO: "else" error message (?)
	}
	#ifdef Q_OS_WIN32
// FIXME: "exe", shortcut dragged from Start menu
	else if (suffix == "lnk") { // shortcut
		ok = QDesktopServices::openUrl(QUrl::fromLocalFile(path));
	}
	#endif // Q_OS_WIN32
	else if (fileInfo.isExecutable()) {
		ok = Utils::run(path, QStringList());
	}
	else {
		ok = QDesktopServices::openUrl(QUrl::fromLocalFile(path));
	}

	if (!ok)
		m_error = i18n("Cannot execute \"Extras\" command");

	return ok;
}

bool Extras::onCommandLineOption() {
	setStringOption(CLI::getOption("extra"));

	return true;
}

void Extras::readConfig() {
	QString group = configGroup();

	m_quickCommandAction->m_command = Config::readString(group, "Quick Command", "");

	// do not override command set via "e" command line option
	if (m_selectedCommandAction == nullptr) {
		if (Config::readBool(group, "Quick Command Selected", true))
			//setCommandAction(m_quickCommandAction);
			setStringOption("");
		else
			setStringOption(Config::readString(group, "Command", ""));
	}

	m_examplesCreated = Config::readBool(group, "Examples Created", false);
}

void Extras::writeConfig() {
	QString group = configGroup();
	Config::writeString(
		group,
		"Command",
		((m_selectedCommandAction != nullptr) && (m_selectedCommandAction != m_quickCommandAction))
			? m_selectedCommandAction->m_command
			: ""
	);
	Config::writeBool(group, "Examples Created", m_examplesCreated);
	Config::writeString(group, "Quick Command", (m_quickCommandEdit != nullptr) ? m_quickCommandEdit->command() : m_quickCommandAction->m_command);
	Config::writeBool(group, "Quick Command Selected", (m_selectedCommandAction == m_quickCommandAction));
}

// private

Extras::Extras() :
	Action(i18n("Extras"), "rating", "extras")
{
	setCanBookmark(false);

	setCommandLineOptionValue({
		{ "e", "extra" },
		i18n("Run executable file (example: Desktop shortcut or Shell script)"),
		"file"
	});

	uiAction()->setMenu(createMenu());
	setVisibleInMainMenu(false);
	setVisibleInSystemTrayMenu(false);

	m_quickCommandAction = new CommandAction(this);

// TODO: lazy Action init for faster app startup
	m_menuButton = new QPushButton();
	m_menuButton->setMenu(uiAction()->menu());
	
	//setCommandAction(0);
}

CommandAction *Extras::createCommandAction(const QFileInfo &fileInfo, const bool returnNull) {
	QString text = fileInfo.fileName();

	if (!fileInfo.exists() || !fileInfo.isFile())
		return returnNull ? nullptr : new CommandAction(QIcon::fromTheme("dialog-error"), i18n("File not found: %0").arg(text), this, fileInfo, "");

	QString description = "";

	if (fileInfo.suffix() == "desktop") {
		QIcon icon = readDesktopInfo(fileInfo, text, description);

		return new CommandAction(icon, text, this, fileInfo, description);
	}

	description = fileInfo.filePath();
	description = Utils::trim(description, 100);

	if (fileInfo.isExecutable()) {
		QString iconName =
			(fileInfo.suffix() == "sh")
			? "application-x-executable-script"
			: "application-x-executable";

		return new CommandAction(QIcon::fromTheme(iconName), text, this, fileInfo, description);
	}

	return new CommandAction(QIcon(), text, this, fileInfo, description);
}

QMenu *Extras::createMenu() {
	m_menu = new QMenu();
	Utils::showToolTips(m_menu);

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

	return m_menu;
}

void Extras::createMenu(QMenu *parentMenu, const QString &parentDir) {
	QDir dir(parentDir);
	QFileInfoList entries = dir.entryInfoList(
		QDir::Dirs | QDir::Files,
		QDir::DirsFirst | QDir::Name
	);

	for (const QFileInfo &i : entries) {
		QString fileName = i.fileName();
		if (i.isDir() && (fileName != ".") && (fileName != "..")) {
			QFileInfo dirProperties(i.filePath() + "/.directory");

			QMenu *dirMenu;

			QString text = fileName; // do not use "baseName" here
			if (dirProperties.exists()) {
				QString description = "";
				QIcon icon = readDesktopInfo(dirProperties, text, description);

				dirMenu = new QMenu(text); // use "text" from ".directory" file
				dirMenu->setIcon(icon);
			}
			else {
				dirMenu = new QMenu(text);
			}

			Utils::showToolTips(dirMenu);

			createMenu(dirMenu, i.filePath()); // recursive scan

			if (dirMenu->isEmpty()) {
				auto *emptyAction = new QAction(dirMenu);
				emptyAction->setEnabled(false);
				emptyAction->setText('(' + i18n("Empty") + ')');
				dirMenu->addAction(emptyAction);
			}

			parentMenu->addMenu(dirMenu);
		}
		else {
			CommandAction *action = createCommandAction(i, true);
			if (action)
				parentMenu->addAction(action);
		}
	}
}

QString Extras::getFilesDirectory() {
	QDir dir(Config::getDataDirectory("extras"));

	//qDebug() << "Extras dir: " << dir;
	// CREDITS: http://stackoverflow.com/questions/6232631/how-to-recursively-create-a-directory-in-qt ;-)
	if (!dir.mkpath(dir.path())) {
		qDebug() << "Could not create Config dir";
	}
	else if (!m_examplesCreated) {
		m_examplesCreated = true;

		#ifndef Q_OS_WIN32
		UPath exampleFile(dir.path().toStdString() + "/VLC.desktop");
		qDebug() << "Creating example file (1):" << QString::fromStdString(exampleFile);
		if (! std::filesystem::exists(exampleFile)) {
			qDebug() << "Creating example file (2):" << QString::fromStdString(exampleFile);

			auto settings = Config::createDesktopEntry(exampleFile);
			settings->setValue("Exec", "dbus-send --print-reply --dest=org.mpris.MediaPlayer2.vlc /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Stop");
			settings->setValue("Icon", "vlc");
			settings->setValue("Name", i18n("Stop VLC Media Player"));
		}
		#endif // !Q_OS_WIN32
	}

	return dir.path();
}

QIcon Extras::readDesktopInfo(const QFileInfo &fileInfo, QString &text, QString &description) {
	auto settings = Config::openDesktopEntry(UPath(fileInfo.filePath().toStdString()));
	QString desktopName = settings->value("Name", "").toString();
	QString desktopComment = settings->value("Comment", "").toString();
	QString exec = settings->value("Exec", "").toString();
	QString icon = settings->value("Icon", "").toString();

	if (!exec.isEmpty())
		description = Utils::trim(exec, 100);

	if (!desktopComment.isEmpty()) {
		if (!description.isEmpty())
			description += '\n';

		description += Utils::trim(desktopComment, 100);
	}

	if (!desktopName.isEmpty())
		text = desktopName;

	text = Utils::trim(text, 100);

	return QIcon::fromTheme(icon);
}

void Extras::setCommandAction(CommandAction *commandAction) {
	m_selectedCommandAction = commandAction;

	if (commandAction != nullptr) {
		if (commandAction == m_quickCommandAction) {
			setCanBookmark(false);

			if (m_quickCommandEdit != nullptr) {
				m_quickCommandAction->m_command = m_quickCommandEdit->command();
				m_quickCommandEdit->show();
				m_quickCommandEdit->setCommandFocus();
			}
		}
		else {
			setCanBookmark(true);

			if (m_quickCommandEdit != nullptr)
				m_quickCommandEdit->hide();
		}

		//qDebug() << "Extras::setCommandAction: " << m_command;
		m_menuButton->setIcon(QIcon(commandAction->icon()));
		m_menuButton->setText(commandAction->text());
		//m_status = (originalText() + " - " + command->text());

		setEnabled(true, "");
	}
	else {
		setCanBookmark(false);

		if (m_quickCommandEdit != nullptr)
			m_quickCommandEdit->hide();

		//qDebug() << "Extras::setCommandAction: NULL";
		m_menuButton->setIcon(QIcon::fromTheme("arrow-down"));
		m_menuButton->setText(i18n("Select a command..."));
		//m_status = QString::null;

/* TODO: disable OK button on setErrorStatus
-void Extras::updateMainWindow(MainWindow *mainWindow) {
-	if (isEnabled() && getStringOption().isEmpty()) {
-		mainWindow->okCancelButton()->setEnabled(false);
-		mainWindow->actionInfoWidget()->setText(i18n("No command selected."), InfoWidget::Type::Warning);
-	}
-	else {
-		Action::updateMainWindow(mainWindow);
-	}
-}
*/

		setEnabled(false, i18n("No command selected."), InfoWidget::Type::Warning);
	}
}

void Extras::showHelp(const bool always) {
	UMessageBuilder message(UMessageBuilder::Type::Info);

	if (! always)
		message.showKey("KShutdown Action extras Help");

	message.markdown(
		i18n("Use context menu to add/edit/remove actions.") + "\n" +
		"\n" +
		"* " + i18n("Use <b>Context Menu</b> to create a new link to application (action)") + "\n" +
// TODO: built-in "New Folder" menu item
		"* " + i18n("Use <b>Create New|Folder...</b> to create a new submenu") + "\n" +
		"* " + i18n("Use <b>Properties</b> to change icon, name, or command")
	);

	message.title(originalText());
	message.exec(MainWindow::self());
}

// event handlers:

void Extras::onModify() {
	if (!PasswordDialog::authorizeSettings(MainWindow::self()))
		return;

	showHelp(false);

	QUrl url = QUrl::fromLocalFile(getFilesDirectory());
	QDesktopServices::openUrl(url);
}

void Extras::onUpdateMenu() {
	m_menu->clear();

	m_menu->addAction(m_quickCommandAction);
	m_menu->addSeparator();

		#ifdef KS_KF5
		// HACK: init Examples
		if (!m_examplesCreated)
			getFilesDirectory();

		QStringList dirs = QStandardPaths::locateAll(
			QStandardPaths::GenericDataLocation,
			"kshutdown/extras",
			QStandardPaths::LocateDirectory
		);
		for (const QString &i : dirs) {
			qDebug() << "Found Extras Directory: " << i;
			createMenu(m_menu, i);
		}
		#else
// TODO: unify with KS_KF5
		createMenu(m_menu, getFilesDirectory());
		#endif // KS_KF5
	
	if (!m_menu->isEmpty())
		m_menu->addSeparator();

	auto *modifyAction = m_menu->addAction(i18n("Add or Remove Commands"), [this] { onModify(); });
	modifyAction->setIcon(QIcon::fromTheme("configure"));

	m_menu->addSeparator();

	m_menu->addAction(
		i18n("Help"),
		[this] { showHelp(true); }
	);

	m_menu->addAction(UWidgets::newLinkAction(
		"Wiki",
		"https://sourceforge.net/p/kshutdown/wiki/Extras/"
	));
}

// CommandAction

// private

CommandAction::CommandAction(QObject *parent)
	: QAction(QIcon::fromTheme("system-run"), i18n("Quick Command"), parent) {

	init("");
}

CommandAction::CommandAction(
	const QIcon &icon,
	const QString &text,
	QObject *parent,
	const QFileInfo &fileInfo,
	const QString &description
)
	: QAction(icon, text, parent) {

	init(fileInfo.filePath());
	setToolTip(description);
}

void CommandAction::init(const QString &command) {
	m_command = command;
	setIconVisibleInMenu(true);
	connect(this, &QAction::triggered, [this] { Extras::self()->setCommandAction(this); });
}
