Pith - whittle
#include "core/ConfigStore.h"
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QSaveFile>
#include <QStandardPaths>
#include <algorithm>

namespace whittle {
	namespace {

		QJsonObject readJson(const QString& path) {
			QFile f(path);
			if (!f.open(QIODevice::ReadOnly)) return {};
			const QJsonDocument doc = QJsonDocument::fromJson(f.readAll());
			return doc.isObject() ? doc.object() : QJsonObject{};
		}

		bool writeJson(const QString& path, const QJsonObject& obj) {
			QDir().mkpath(QFileInfo(path).absolutePath());
			QSaveFile f(path);
			if (!f.open(QIODevice::WriteOnly)) return false;
			f.write(QJsonDocument(obj).toJson(QJsonDocument::Indented));
			return f.commit();
		}

		QString settingsPath() { return ConfigStore::configDir() + "/settings.json"; }
		QString schemesPath()  { return ConfigStore::configDir() + "/schemes.json";  }

		constexpr char kSchemes[] = "schemes";

} // namespace

QString ConfigStore::configDir() {
	QString base = QStandardPaths::writableLocation(
		QStandardPaths::GenericConfigLocation);
	if (base.isEmpty()) base = QDir::homePath() + "/.config";
	const QString dir = base + "/whittle";
	QDir().mkpath(dir);
	return dir;
}

FilterSettings ConfigStore::loadFilter() {
	FilterSettings f;
	const QJsonObject o = readJson(settingsPath());
	if (o.isEmpty()) return f;

	f.compareSize = o.value("compareSize").toBool(f.compareSize);
	f.compareTime = o.value("compareTime").toBool(f.compareTime);
	if (!f.compareSize && !f.compareTime) f.compareSize = f.compareTime = true;

	f.toleranceNs = qint64(o.value("toleranceNs").toDouble(double(f.toleranceNs)));
	f.display = (o.value("display").toString() == QLatin1String("all"))
	? FilterSettings::Display::All
	: FilterSettings::Display::OnlyDiff;
	f.recursive      = o.value("recursive").toBool(f.recursive);
	f.loadLastScheme = o.value("loadLastScheme").toBool(f.loadLastScheme);
	return f;
}

bool ConfigStore::saveFilter(const FilterSettings& f) {
	QJsonObject o = readJson(settingsPath());
	o["compareSize"] = f.compareSize;
	o["compareTime"] = f.compareTime;
	o["toleranceNs"] = double(f.toleranceNs);
	o["display"] = (f.display == FilterSettings::Display::All)
	? QStringLiteral("all") : QStringLiteral("diff");
	o["recursive"]      = f.recursive;
	o["loadLastScheme"] = f.loadLastScheme;
	return writeJson(settingsPath(), o);
}

QString ConfigStore::loadLastSchemeName() {
	return readJson(settingsPath()).value("lastScheme").toString();
}

bool ConfigStore::saveLastSchemeName(const QString& name) {
	QJsonObject o = readJson(settingsPath());
	o["lastScheme"] = name;
	return writeJson(settingsPath(), o);
}

QStringList ConfigStore::schemeNames() {
	const QJsonObject all = readJson(schemesPath())
	.value(kSchemes).toObject();
	QStringList names = all.keys();
	std::sort(names.begin(), names.end(), [](const QString& a, const QString& b) {
		return a.compare(b, Qt::CaseInsensitive) < 0;
	});
	return names;
}

bool ConfigStore::hasScheme(const QString& name) {
	return readJson(schemesPath()).value(kSchemes).toObject().contains(name);
}

bool ConfigStore::saveScheme(const QString& name, const Scheme& s) {
	if (name.trimmed().isEmpty()) return false;

	QJsonObject root = readJson(schemesPath());
	QJsonObject all  = root.value(kSchemes).toObject();

	QJsonArray roots;
	for (const QString& r : s.roots) roots.append(r);

		QJsonArray excluded;
	for (const QString& e : s.excluded) excluded.append(e);

		QJsonObject entry;
	entry["recursive"] = s.recursive;
	entry["roots"]     = roots;
	entry["excluded"]  = excluded;

	all[name] = entry;
	root[kSchemes] = all;
	return writeJson(schemesPath(), root);
}

ConfigStore::Scheme ConfigStore::loadScheme(const QString& name, bool* found) {
	Scheme s;
	const QJsonObject all = readJson(schemesPath()).value(kSchemes).toObject();
	const auto it = all.find(name);
	if (it == all.end()) { if (found) *found = false; return s; }

	const QJsonObject entry = it->toObject();
	s.recursive = entry.value("recursive").toBool(true);
	for (const QJsonValue& v : entry.value("roots").toArray()) {
		const QString p = v.toString();
		if (!p.isEmpty()) s.roots << p;
	}
	for (const QJsonValue& v : entry.value("excluded").toArray()) {
		const QString p = v.toString();
		if (!p.isEmpty()) s.excluded << p;
	}
	if (found) *found = true;
	return s;
}

bool ConfigStore::deleteScheme(const QString& name) {
	QJsonObject root = readJson(schemesPath());
	QJsonObject all  = root.value(kSchemes).toObject();
	if (!all.contains(name)) return false;

	all.remove(name);
	root[kSchemes] = all;
	if (!writeJson(schemesPath(), root)) return false;

	if (loadLastSchemeName() == name) saveLastSchemeName(QString());
	return true;
}

}