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

#include "config.h"

#include "commandline.h"
#include "utils.h"

#include <QDebug>
#include <QDir>
#include <QLoggingCategory>
#include <QStandardPaths>

#ifdef KS_PURE_QT
	#include <QApplication>
#else
	#include <KConfig>
	#include <KSharedConfig>
#endif // KS_PURE_QT

// Config

// public

Config::~Config() {
	//qDebug() << "Config::~Config()";
#ifdef KS_PURE_QT
	if (m_engine != nullptr) {
		delete m_engine;
		m_engine = nullptr;
	}
#endif // KS_PURE_QT
}

QString Config::getDataDirectory(const QString &subDirectory) {
	QString result;

	if (isPortable()) {
		result = Utils::getAppDir().path();
	}
	else {
		result =
		#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
		QStandardPaths::writableLocation(QStandardPaths::DataLocation);
		#else
		QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
		#endif // QT_VERSION
	}

	if (!subDirectory.isEmpty())
		result += QDir::separator() + subDirectory;

	return result;
}

void Config::init() {
	#ifdef KS_PORTABLE
	m_portable = true;
	#else
		#ifdef KS_PURE_QT
		// HACK: QCommandLineParser is not initialized at this stage yet
		//m_portable = CLI::isArg("portable");
		const QStringList &args = QApplication::arguments();
		m_portable = args.contains("-portable") || args.contains("--portable");
		#else
		m_portable = false;
		#endif // KS_PURE_QT
	#endif // KS_PORTABLE

	// DOC: https://specifications.freedesktop.org/desktop-entry-spec/latest/
	m_desktopEntryFormat = QSettings::registerFormat(
		"desktop",

		// reader

		[](QIODevice &device, QSettings::SettingsMap &map) {
			QLoggingCategory log("ks.desktopentry");
			//qCDebug(log) << "READ";

			QString group = "";

			while (!device.atEnd()) {
				QString line = QString(device.readLine());

				//qCDebug(log) << line;
				
				line = line.trimmed();
				
				if (line.isEmpty())
					continue; // for
				
				QChar firstChar = line[0];
				
				if (firstChar == '#') // comment
					continue; // for

				if ((firstChar == '[') && line[line.length() - 1] == ']') { // group
					group = line.mid(1, line.length() - 2);
					//qCDebug(log) << "GROUP:" << group;
				}
				else {
					auto pair = Utils::splitPair(line, '=', true);
					if (!pair.isEmpty()) {
						QString name = pair.first();
						QString key = group.isEmpty() ? name : (group + "/" + name);
						QString unescaped = pair.last();

						// unescape
						const QChar temp(0xFFFC/* OBJECT REPLACEMENT CHARACTER */);
						unescaped.replace("\\\\", temp); // handle "\\\\n" case, etc.
						unescaped.replace("\\n", "\n");
						unescaped.replace("\\r", "\r");
						unescaped.replace("\\t", "\t");
						unescaped.replace("\\s", " ");
						//unescaped.replace("\\\\", "\\");
						unescaped.replace(temp, "\\");

						//qCDebug(log) << "\tREAD ENTRY:" << name << " = " << unescaped;

						map[key] = unescaped;
					}
					else {
						qCWarning(log, "Group or key-value pair expected: %s", KS_S(line));
					}
				}

				//qCDebug(log) << line;
			}

			return true;
		},

		// writer

		[](QIODevice &device, const QSettings::SettingsMap &map) {
			QLoggingCategory log("ks.desktopentry");
			QString lastGroup = "";
			QString name = "";

			QMapIterator<QString, QVariant> it(map);
			while (it.hasNext()) {
				it.next();
				QString key = it.key();
				const QString value = it.value().toString();

				auto pair = Utils::splitPair(key, '/', true);
				if (!pair.isEmpty()) {
					QString group = pair.first();
					name = pair.last();

					if (group != lastGroup) {
						device.write(((lastGroup.isEmpty() ? "[" : "\n[") + group + "]\n").toUtf8());
						lastGroup = group;
					}
				}
				else {
					name = key.trimmed();
				}

				// escape
				QString escaped = "";
				for (int i = 0; i < value.size(); i++) {
					QChar c = value.at(i);
					if (c == '\n')
						escaped.append("\\n");
					else if (c == '\r')
						escaped.append("\\r");
					//else if (c == ' ')
					//	escaped.append("\\s");
					else if (c == '\t')
						escaped.append("\\t");
					else if (c == '\\')
						escaped.append("\\\\");
					else
						escaped.append(c);
				}

				//qCDebug(log) << "WRITE ENTRY:" << key << "=" << escaped;

				device.write((name + '=' + escaped + '\n').toUtf8());
			}

			return true;
		}
	);
}

std::shared_ptr<QSettings> Config::createDesktopEntry(const UPath &file) {
	auto settings = openDesktopEntry(file);
	settings->setValue("Version", "1.0");
	settings->setValue("Type", "Application");

	return settings;
}

std::shared_ptr<QSettings> Config::openDesktopEntry(const UPath &file) {
	auto settings = std::make_shared<QSettings>(QString::fromStdString(file.string()), m_desktopEntryFormat);
	settings->beginGroup("Desktop Entry");

	return settings;
}

bool Config::readBool(const QString &group, const QString &key, const bool defaultValue) {
	return readVariant(group, key, defaultValue)
		.toBool();
}

int Config::readInt(const QString &group, const QString &key, const int defaultValue, const int min, const int max) {
	int result = readVariant(group, key, defaultValue)
		.toInt();

	return std::clamp(result, min, max);
}

QString Config::readString(const QString &group, const QString &key, const QString &defaultValue) {
	return readVariant(group, key, defaultValue)
		.toString();
}

QVariant Config::readVariant(const QString &group, const QString &key, const QVariant &defaultValue) {
	Config *config = user();
	config->beginGroup(group);
	QVariant result = config->get(key, defaultValue);
	config->endGroup();

	return result;
}

void Config::writeBool(const QString &group, const QString &key, const bool value) {
	writeVariant(group, key, value);
}

void Config::writeInt(const QString &group, const QString &key, const int value) {
	writeVariant(group, key, value);
}

void Config::writeString(const QString &group, const QString &key, const QString &value) {
	writeVariant(group, key, value);
}

void Config::writeVariant(const QString &group, const QString &key, const QVariant &value) {
	Config *config = user();
	config->beginGroup(group);
	config->set(key, value);
	config->endGroup();
}

void Config::removeAllKeys(const QString &group) {
	m_user->beginGroup(group);
#ifdef KS_KF5
	m_user->m_group.deleteGroup();
#else
	m_user->m_engine->remove("");
#endif // KS_KF5
	m_user->endGroup();
}

void Config::sync() {
	if ((m_user != nullptr) && (m_user->m_engine != nullptr))
		m_user->m_engine->sync();
}

// private

Config::Config() {
	//qDebug() << "Config::Config()";
	
#ifdef KS_KF5
	m_engine = KSharedConfig::openConfig().data();
	m_path = QStandardPaths::writableLocation(m_engine->locationType()) + "/" + m_engine->name();
#else
	qDebug() << "Config::isPortable(): " << isPortable();
	if (isPortable())
		m_engine = new QSettings(getDataDirectory("kshutdown.ini"), QSettings::IniFormat);
	else
		m_engine = new QSettings();
	m_path = m_engine->fileName();
#endif // KS_KF5
}

void Config::beginGroup(const QString &name) {
#ifdef KS_KF5
	m_group = m_engine->group(name);
#else
	m_engine->beginGroup(name);
#endif // KS_KF5
}

void Config::endGroup() {
#ifdef KS_PURE_QT
	m_engine->endGroup();
#endif // KS_PURE_QT
}

QVariant Config::get(const QString &key, const QVariant &defaultValue) const {
#ifdef KS_KF5
	return m_group.readEntry(key, defaultValue);
#else
	return m_engine->value(key, defaultValue);
#endif // KS_KF5
}

void Config::set(const QString &key, const QVariant &value) {
#ifdef KS_KF5
	m_group.writeEntry(key, value);
#else
	m_engine->setValue(key, value);
#endif // KS_KF5
}

// Var

// public

QCheckBox *Var::newCheckBox(const QString &text) {
	auto *result = new QCheckBox(text);
	result->setChecked(getBool());

	return result;
}

void Var::write(const bool sync) {
	QVariant value = variant();
	//qDebug() << "WRITE VAR: [" << m_group << "] " << m_key << " = " << value;

	Config::writeVariant(m_group, m_key, value);

	if (sync)
		Config::sync();
}

void Var::write(QCheckBox *checkBox) {
	setBool(checkBox->isChecked());
	write();
}

// private

QVariant Var::variant() {
	if (!m_lazyVariant.isValid()) {
		m_lazyVariant = Config::readVariant(m_group, m_key, m_defaultVariant);
		//qDebug() << "Read var: " << m_group << " / " << m_key << " = " << m_lazyVariant;
	}
	
	return m_lazyVariant;
}
