/*
 * Copyright (C) 2014-2024 CZ.NIC
 *
 * This program is free software: 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 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * In addition, as a special exception, the copyright holders give
 * permission to link the code of portions of this program with the
 * OpenSSL library under certain conditions as described in each
 * individual source file, and distribute linked combinations including
 * the two.
 */

#include <QReadLocker>
#include <QRegularExpression>
#include <QSet>
#include <QWriteLocker>

#include "src/datovka_shared/compat/compiler.h" /* macroStdMove */
#include "src/datovka_shared/graphics/colour.h"
#include "src/datovka_shared/log/log.h"
#include "src/datovka_shared/settings/prefs.h"

Prefs::Prefs(void)
    : m_lock(),
    m_defaults(),
    m_prefsDb(Q_NULLPTR)
{
}

bool Prefs::setDefault(const Entry &entry)
{
	if (Q_UNLIKELY(entry.m_name.isEmpty())) {
		Q_ASSERT(0);
		return false;
	}
	if (Q_UNLIKELY(entry.m_content.m_modified)) {
		/* Cannot insert value which declares to be already modified. */
		Q_ASSERT(0);
		return false;
	}
	switch (entry.m_content.m_type) {
	case PrefsDb::VAL_BOOLEAN:
	case PrefsDb::VAL_INTEGER:
	case PrefsDb::VAL_FLOAT:
		if (Q_UNLIKELY(entry.m_content.m_defaultValue.isNull())) {
			Q_ASSERT(0);
			return false;
		}
		break;
	default:
		break;
	}

	enum PrefsDb::ValueType type = PrefsDb::VAL_BOOLEAN;
	bool runEmit = false;

	{
		QWriteLocker locker(&m_lock);

		QMap<QString, EntryContent>::iterator it =
		    m_defaults.insert(entry.m_name, entry.m_content);

		/* Adjust modified value. */
		if ((m_prefsDb != Q_NULLPTR) && (m_prefsDb->contains(entry.m_name))) {
			QVariant value;
			if (Q_UNLIKELY((!m_prefsDb->type(entry.m_name, type, &value)) ||
			               (type != entry.m_content.m_type))) {
				logWarningNL(
				    "Deleting entry '%s' from database because of type mismatch.",
				    entry.m_name.toUtf8().constData());
				m_prefsDb->erase(entry.m_name);
				/* Modified an existing entry in database. */
				runEmit = true;
			} else if (value != entry.m_content.m_defaultValue) {
				it->m_modified = true;
			} else {
				/* Default matches database value. */
				m_prefsDb->erase(entry.m_name);
				it->m_modified = false;
			}
		}
	}

	/* Signal must not be emitted when write lock is active. */
	if (runEmit) {
		emit entrySet(type, entry.m_name, entry.m_content.m_defaultValue);
	}

	return true;
}

bool Prefs::setDatabase(PrefsDb *prefsDb)
{
	QList<Entry> setEntries;
	QList<Entry> createdEntries;

	{
		QWriteLocker locker(&m_lock);

		if (m_prefsDb != Q_NULLPTR) {
			/* Set defaults as unmodified and stop database from being used. */
			resetDefaults();
			/* Just stop using this database. */
			m_prefsDb = Q_NULLPTR;
		}

		if (prefsDb != Q_NULLPTR) {
			const QStringList nameList(prefsDb->names());
			for (const QString &name : nameList) {
				enum PrefsDb::ValueType type = PrefsDb::VAL_BOOLEAN;
				QVariant value;
				if (Q_UNLIKELY(!prefsDb->type(name, type, &value))) {
					/* Ignore faulty values. */
					continue;
				}
				QMap<QString, EntryContent>::iterator it = m_defaults.find(name);
				if (it != m_defaults.end()) {
					/* There is a default entry. */
					if (Q_UNLIKELY(type != it->m_type)) {
						logWarningNL(
						    "Deleting entry '%s' from database because of type mismatch.",
						    name.toUtf8().constData());
						prefsDb->erase(name);
					} else if (value != it->m_defaultValue) {
						it->m_modified = true;
						/* Default value is modified by value in database. */
						setEntries.append(Entry(name, type, value));
					} else {
						/* Default matches database value. */
						prefsDb->erase(name);
						it->m_modified = false;
					}
				} else {
					/* New values loaded from database. */
					createdEntries.append(Entry(name, type, value));
				}
			}
		}

		m_prefsDb = prefsDb;
	}

	/* Signals must not be emitted when write lock is active. */
	for (const Entry &entry : setEntries) {
		emit entrySet(entry.m_content.m_type, entry.m_name, entry.m_content.m_defaultValue);
	}
	for (const Entry &entry : createdEntries) {
		emit entryCreated(entry.m_content.m_type, entry.m_name, entry.m_content.m_defaultValue);
	}

	return true;
}

QStringList Prefs::names(const QRegularExpression &re) const
{
	QReadLocker locker(&m_lock);

#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
	QList<QString> keys = m_defaults.keys();
	QSet<QString> names(keys.begin(), keys.end());
	if (m_prefsDb != Q_NULLPTR) {
		QStringList dbNames = m_prefsDb->names();
		names += QSet<QString>(dbNames.begin(), dbNames.end());
	}
#else /* < Qt-5.14.0 */
	QSet<QString> names = m_defaults.keys().toSet();
	if (m_prefsDb != Q_NULLPTR) {
		names += m_prefsDb->names().toSet();
	}
#endif /* >= Qt-5.14.0 */

	QStringList matchingNames;
	for (const QString &name : names) {
		if (re.match(name).hasMatch()) {
			matchingNames.append(name);
		}
	}
	return matchingNames;
}

bool Prefs::type(const QString &name, enum PrefsDb::ValueType &type) const
{
	QReadLocker locker(&m_lock);

	QMap<QString, EntryContent>::const_iterator it = m_defaults.constFind(name);
	if (it != m_defaults.constEnd()) {
		type = it->m_type;
		return true;
	}
	if (m_prefsDb != Q_NULLPTR) {
		return m_prefsDb->type(name, type, Q_NULLPTR);
	}
	/* No default, no database. */
	return false;
}

bool Prefs::status(const QString &name, enum Status &status) const
{
	QReadLocker locker(&m_lock);

	QMap<QString, EntryContent>::const_iterator it = m_defaults.constFind(name);
	if (it != m_defaults.constEnd()) {
		if (it->m_modified) {
			if (Q_UNLIKELY(m_prefsDb == Q_NULLPTR)) {
				Q_ASSERT(0);
				return false;
			}
			status = STAT_MODIFIED;
			return true;
		} else {
			status = STAT_DEFAULT;
			return true;
		}
	}
	if (m_prefsDb != Q_NULLPTR) {
		enum PrefsDb::ValueType type = PrefsDb::VAL_BOOLEAN;
		if (m_prefsDb->type(name, type)) {
			return true;
		}
		/* Name not in database. */
	}
	/* No default, no database. */
	return false;
}

void Prefs::resetVal(const QString &name)
{
	QList<Entry> setEntries;
	QList<Entry> removedEntries;

	{
		QWriteLocker locker(&m_lock);

		if (m_prefsDb != Q_NULLPTR) {
			enum PrefsDb::ValueType type = PrefsDb::VAL_BOOLEAN;
			if (m_prefsDb->type(name, type)) {
				/* Remove entry from database. */
				m_prefsDb->erase(name);

				QMap<QString, EntryContent>::iterator it = m_defaults.find(name);
				if (it != m_defaults.end()) {
					/* Reset to defaults. */
					it->m_modified = false;
					setEntries.append(Entry(name, it->m_type, it->m_defaultValue));
				} else {
					/* Reset to nothing. */
					removedEntries.append(Entry(name, it->m_type, QVariant()));
				}
			}
		}
	}

	/* Signals must not be emitted when write lock is active. */
	for (const Entry &entry : setEntries) {
		emit entrySet(entry.m_content.m_type, entry.m_name, entry.m_content.m_defaultValue);
	}
	for (const Entry &entry : removedEntries) {
		emit entrySet(entry.m_content.m_type, entry.m_name, QVariant());
		emit entryRemoved(entry.m_content.m_type, entry.m_name);
	}
}

bool Prefs::setBoolVal(const QString &name, bool val)
{
	const enum PrefsDb::ValueType expectedType = PrefsDb::VAL_BOOLEAN;
	QList<Entry> setEntries;
	QList<Entry> createdEntries;
	bool ret = false; /* No default, no database. */

	{
		QWriteLocker locker(&m_lock);

		QMap<QString, EntryContent>::iterator it = m_defaults.find(name);
		if (it != m_defaults.end()) {
			if (Q_UNLIKELY(it->m_type != expectedType)) {
				return false; /* Cannot override default type. */
			}
			if (it->m_defaultValue == QVariant(val)) {
				/* New value matches default value. */
				if (m_prefsDb != Q_NULLPTR) {
					m_prefsDb->erase(name);
				}
				it->m_modified = false;
				setEntries.append(Entry(name, expectedType, val));
				ret = true;
				goto leave;
			}
		}
		if (m_prefsDb != Q_NULLPTR) {
			const bool alreadyExistent =
			   (it != m_defaults.end()) || m_prefsDb->contains(name);
			/* New value is different or default is not set. */
			if (Q_UNLIKELY(!m_prefsDb->setBoolVal(name, val))) {
				/* Cannot modify database. */
				return false;
			}
			if (it != m_defaults.end()) {
				/* Default has been changed. */
				it->m_modified = true;
			}
			if (!alreadyExistent) {
				createdEntries.append(Entry(name, expectedType, val));
			}
			setEntries.append(Entry(name, expectedType, val));
			ret = true;
			goto leave;
		}
	}

leave:
	/* Signals must not be emitted when write lock is active. */
	for (const Entry &entry : setEntries) {
		emit entrySet(entry.m_content.m_type, entry.m_name, entry.m_content.m_defaultValue);
	}
	for (const Entry &entry : createdEntries) {
		emit entryCreated(entry.m_content.m_type, entry.m_name, entry.m_content.m_defaultValue);
	}

	return ret;
}

bool Prefs::boolVal(const QString &name, bool &val) const
{
	QReadLocker locker(&m_lock);

	QMap<QString, EntryContent>::const_iterator it = m_defaults.constFind(name);
	if (it != m_defaults.constEnd()) {
		if (it->m_modified) {
			if (Q_UNLIKELY(m_prefsDb == Q_NULLPTR)) {
				Q_ASSERT(0);
				return false;
			}
			return m_prefsDb->boolVal(name, val);
		} else {
			if (Q_UNLIKELY(it->m_type != PrefsDb::VAL_BOOLEAN)) {
				Q_ASSERT(0);
				return false;
			}
			if (Q_UNLIKELY(it->m_defaultValue.isNull())) {
				Q_ASSERT(0);
				return false;
			}
			val = it->m_defaultValue.toBool();
			return true;
		}
	}
	if (m_prefsDb != Q_NULLPTR) {
		return m_prefsDb->boolVal(name, val);
	}
	/* No defaults, no database. */
	return false;
}

bool Prefs::setIntVal(const QString &name, qint64 val)
{
	const enum PrefsDb::ValueType expectedType = PrefsDb::VAL_INTEGER;
	QList<Entry> setEntries;
	QList<Entry> createdEntries;
	bool ret = false; /* No default, no database. */

	{
		QWriteLocker locker(&m_lock);

		QMap<QString, EntryContent>::iterator it = m_defaults.find(name);
		if (it != m_defaults.end()) {
			if (Q_UNLIKELY(it->m_type != expectedType)) {
				return false; /* Cannot override default type. */
			}
			if (it->m_defaultValue == QVariant(val)) {
				/* New value matches default value. */
				if (m_prefsDb != Q_NULLPTR) {
					m_prefsDb->erase(name);
				}
				it->m_modified = false;
				setEntries.append(Entry(name, expectedType, val));
				ret = true;
				goto leave;
			}
		}
		if (m_prefsDb != Q_NULLPTR) {
			const bool alreadyExistent =
			   (it != m_defaults.end()) || m_prefsDb->contains(name);
			/* New value is different or default is not set. */
			if (Q_UNLIKELY(!m_prefsDb->setIntVal(name, val))) {
				/* Cannot modify database. */
				return false;
			}
			if (it != m_defaults.end()) {
				/* Default has been changed. */
				it->m_modified = true;
			}
			if (!alreadyExistent) {
				createdEntries.append(Entry(name, expectedType, val));
			}
			setEntries.append(Entry(name, expectedType, val));
			ret = true;
			goto leave;
		}
	}

leave:
	/* Signals must not be emitted when write lock is active. */
	for (const Entry &entry : setEntries) {
		emit entrySet(entry.m_content.m_type, entry.m_name, entry.m_content.m_defaultValue);
	}
	for (const Entry &entry : createdEntries) {
		emit entryCreated(entry.m_content.m_type, entry.m_name, entry.m_content.m_defaultValue);
	}

	return ret;
}

bool Prefs::intVal(const QString &name, qint64 &val) const
{
	QReadLocker locker(&m_lock);

	QMap<QString, EntryContent>::const_iterator it = m_defaults.constFind(name);
	if (it != m_defaults.constEnd()) {
		if (it->m_modified) {
			if (Q_UNLIKELY(m_prefsDb == Q_NULLPTR)) {
				Q_ASSERT(0);
				return false;
			}
			return m_prefsDb->intVal(name, val);
		} else {
			if (Q_UNLIKELY(it->m_type != PrefsDb::VAL_INTEGER)) {
				Q_ASSERT(0);
				return false;
			}
			if (Q_UNLIKELY(it->m_defaultValue.isNull())) {
				Q_ASSERT(0);
				return false;
			}
			val = it->m_defaultValue.toLongLong();
			return true;
		}
	}
	if (m_prefsDb != Q_NULLPTR) {
		return m_prefsDb->intVal(name, val);
	}
	/* No defaults, no database. */
	return false;
}

bool Prefs::setFloatVal(const QString &name, double val)
{
	const enum PrefsDb::ValueType expectedType = PrefsDb::VAL_FLOAT;
	QList<Entry> setEntries;
	QList<Entry> createdEntries;
	bool ret = false; /* No default, no database. */

	{
		QWriteLocker locker(&m_lock);

		QMap<QString, EntryContent>::iterator it = m_defaults.find(name);
		if (it != m_defaults.end()) {
			if (Q_UNLIKELY(it->m_type != expectedType)) {
				return false; /* Cannot override default type. */
			}
			if (it->m_defaultValue == QVariant(val)) {
				/* New value matches default value. */
				if (m_prefsDb != Q_NULLPTR) {
					m_prefsDb->erase(name);
				}
				it->m_modified = false;
				setEntries.append(Entry(name, expectedType, val));
				ret = true;
				goto leave;
			}
		}
		if (m_prefsDb != Q_NULLPTR) {
			const bool alreadyExistent =
			   (it != m_defaults.end()) || m_prefsDb->contains(name);
			/* New value is different or default is not set. */
			if (Q_UNLIKELY(!m_prefsDb->setFloatVal(name, val))) {
				/* Cannot modify database. */
				return false;
			}
			if (it != m_defaults.end()) {
				/* Default has been changed. */
				it->m_modified = true;
			}
			if (!alreadyExistent) {
				createdEntries.append(Entry(name, expectedType, val));
			}
			setEntries.append(Entry(name, expectedType, val));
			ret = true;
			goto leave;
		}
	}

leave:
	/* Signals must not be emitted when write lock is active. */
	for (const Entry &entry : setEntries) {
		emit entrySet(entry.m_content.m_type, entry.m_name, entry.m_content.m_defaultValue);
	}
	for (const Entry &entry : createdEntries) {
		emit entryCreated(entry.m_content.m_type, entry.m_name, entry.m_content.m_defaultValue);
	}

	return ret;
}

bool Prefs::floatVal(const QString &name, double &val) const
{
	QReadLocker locker(&m_lock);

	QMap<QString, EntryContent>::const_iterator it = m_defaults.constFind(name);
	if (it != m_defaults.constEnd()) {
		if (it->m_modified) {
			if (Q_UNLIKELY(m_prefsDb == Q_NULLPTR)) {
				Q_ASSERT(0);
				return false;
			}
			return m_prefsDb->floatVal(name, val);
		} else {
			if (Q_UNLIKELY(it->m_type != PrefsDb::VAL_FLOAT)) {
				Q_ASSERT(0);
				return false;
			}
			if (Q_UNLIKELY(it->m_defaultValue.isNull())) {
				Q_ASSERT(0);
				return false;
			}
			val = it->m_defaultValue.toDouble();
			return true;
		}
	}
	if (m_prefsDb != Q_NULLPTR) {
		return m_prefsDb->floatVal(name, val);
	}
	/* No defaults, no database. */
	return false;
}

bool Prefs::setStrVal(const QString &name, const QString &val)
{
	const enum PrefsDb::ValueType expectedType = PrefsDb::VAL_STRING;
	QList<Entry> setEntries;
	QList<Entry> createdEntries;
	bool ret = false; /* No default, no database. */

	{
		QWriteLocker locker(&m_lock);

		QMap<QString, EntryContent>::iterator it = m_defaults.find(name);
		if (it != m_defaults.end()) {
			if (Q_UNLIKELY(it->m_type != expectedType)) {
				return false; /* Cannot override default type. */
			}
			if (it->m_defaultValue == QVariant(val)) {
				/* New value matches default value. */
				if (m_prefsDb != Q_NULLPTR) {
					m_prefsDb->erase(name);
				}
				it->m_modified = false;
				setEntries.append(Entry(name, expectedType, val));
				ret = true;
				goto leave;
			}
		}
		if (m_prefsDb != Q_NULLPTR) {
			const bool alreadyExistent =
			   (it != m_defaults.end()) || m_prefsDb->contains(name);
			/* New value is different or default is not set. */
			if (Q_UNLIKELY(!m_prefsDb->setStrVal(name, val))) {
				/* Cannot modify database. */
				return false;
			}
			if (it != m_defaults.end()) {
				/* Default has been changed. */
				it->m_modified = true;
			}
			if (!alreadyExistent) {
				createdEntries.append(Entry(name, expectedType, val));
			}
			setEntries.append(Entry(name, expectedType, val));
			ret = true;
			goto leave;
		}
	}

leave:
	/* Signals must not be emitted when write lock is active. */
	for (const Entry &entry : setEntries) {
		emit entrySet(entry.m_content.m_type, entry.m_name, entry.m_content.m_defaultValue);
	}
	for (const Entry &entry : createdEntries) {
		emit entryCreated(entry.m_content.m_type, entry.m_name, entry.m_content.m_defaultValue);
	}

	return ret;
}

bool Prefs::strVal(const QString &name, QString &val) const
{
	QReadLocker locker(&m_lock);

	QMap<QString, EntryContent>::const_iterator it = m_defaults.constFind(name);
	if (it != m_defaults.constEnd()) {
		if (it->m_modified) {
			if (Q_UNLIKELY(m_prefsDb == Q_NULLPTR)) {
				Q_ASSERT(0);
				return false;
			}
			return m_prefsDb->strVal(name, val);
		} else {
			if (Q_UNLIKELY(it->m_type != PrefsDb::VAL_STRING)) {
				Q_ASSERT(0);
				return false;
			}
			/* Allow null values here. */
			val = it->m_defaultValue.toString();
			return true;
		}
	}
	if (m_prefsDb != Q_NULLPTR) {
		return m_prefsDb->strVal(name, val);
	}
	/* No defaults, no database. */
	return false;
}

bool Prefs::setColourVal(const QString &name, const QString &val)
{
	const enum PrefsDb::ValueType expectedType = PrefsDb::VAL_COLOUR;
	QList<Entry> setEntries;
	QList<Entry> createdEntries;
	bool ret = false; /* No default, no database. */

	{
		QWriteLocker locker(&m_lock);

		QMap<QString, EntryContent>::iterator it = m_defaults.find(name);
		if (it != m_defaults.end()) {
			if (Q_UNLIKELY(it->m_type != expectedType)) {
				return false; /* Cannot override default type. */
			}
			if (it->m_defaultValue == QVariant(val)) {
				/* New value matches default value. */
				if (m_prefsDb != Q_NULLPTR) {
					m_prefsDb->erase(name);
				}
				it->m_modified = false;
				setEntries.append(Entry(name, expectedType, val));
				ret = true;
				goto leave;
			}
		}
		if (m_prefsDb != Q_NULLPTR) {
			const bool alreadyExistent =
			   (it != m_defaults.end()) || m_prefsDb->contains(name);
			/* New value is different or default is not set. */
			if (Q_UNLIKELY(!m_prefsDb->setColourVal(name, val))) {
				/* Cannot modify database. */
				return false;
			}
			if (it != m_defaults.end()) {
				/* Default has been changed. */
				it->m_modified = true;
			}
			if (!alreadyExistent) {
				createdEntries.append(Entry(name, expectedType, val));
			}
			setEntries.append(Entry(name, expectedType, val));
			ret = true;
			goto leave;
		}
	}

leave:
	/* Signals must not be emitted when write lock is active. */
	for (const Entry &entry : setEntries) {
		emit entrySet(entry.m_content.m_type, entry.m_name, entry.m_content.m_defaultValue);
	}
	for (const Entry &entry : createdEntries) {
		emit entryCreated(entry.m_content.m_type, entry.m_name, entry.m_content.m_defaultValue);
	}

	return ret;
}

bool Prefs::colourVal(const QString &name, QString &val) const
{
	QReadLocker locker(&m_lock);

	QMap<QString, EntryContent>::const_iterator it = m_defaults.constFind(name);
	if (it != m_defaults.constEnd()) {
		if (it->m_modified) {
			if (Q_UNLIKELY(m_prefsDb == Q_NULLPTR)) {
				Q_ASSERT(0);
				return false;
			}
			return m_prefsDb->colourVal(name, val);
		} else {
			if (Q_UNLIKELY(it->m_type != PrefsDb::VAL_COLOUR)) {
				Q_ASSERT(0);
				return false;
			}
			/* Allow null values here. */
			QString value = it->m_defaultValue.toString();
			if (Q_UNLIKELY((!value.isNull()) &&
			        (!Colour::isValidColourStr(value)))) {
				logErrorNL("Bad default value '%s' of type '%d'.",
				    value.toUtf8().constData(), PrefsDb::VAL_COLOUR);
				Q_ASSERT(0);
				return false;
			}
			val = macroStdMove(value);
			return true;
		}
	}
	if (m_prefsDb != Q_NULLPTR) {
		return m_prefsDb->colourVal(name, val);
	}
	/* No defaults, no database. */
	return false;
}

bool Prefs::setDateTimeVal(const QString &name, const QDateTime &val)
{
	const enum PrefsDb::ValueType expectedType = PrefsDb::VAL_DATETIME;
	QList<Entry> setEntries;
	QList<Entry> createdEntries;
	bool ret = false; /* No default, no database. */

	{
		QWriteLocker locker(&m_lock);

		QMap<QString, EntryContent>::iterator it = m_defaults.find(name);
		if (it != m_defaults.end()) {
			if (Q_UNLIKELY(it->m_type != expectedType)) {
				return false; /* Cannot override default type. */
			}
			if (it->m_defaultValue == QVariant(val)) {
				/* New value matches default value. */
				if (m_prefsDb != Q_NULLPTR) {
					m_prefsDb->erase(name);
				}
				it->m_modified = false;
				setEntries.append(Entry(name, expectedType, val));
				ret = true;
				goto leave;
			}
		}
		if (m_prefsDb != Q_NULLPTR) {
			const bool alreadyExistent =
			   (it != m_defaults.end()) || m_prefsDb->contains(name);
			/* New value is different or default is not set. */
			if (Q_UNLIKELY(!m_prefsDb->setDateTimeVal(name, val))) {
				/* Cannot modify database. */
				return false;
			}
			if (it != m_defaults.end()) {
				/* Default has been changed. */
				it->m_modified = true;
			}
			if (!alreadyExistent) {
				createdEntries.append(Entry(name, expectedType, val));
			}
			setEntries.append(Entry(name, expectedType, val));
			ret = true;
			goto leave;
		}
	}

leave:
	/* Signals must not be emitted when write lock is active. */
	for (const Entry &entry : setEntries) {
		emit entrySet(entry.m_content.m_type, entry.m_name, entry.m_content.m_defaultValue);
	}
	for (const Entry &entry : createdEntries) {
		emit entryCreated(entry.m_content.m_type, entry.m_name, entry.m_content.m_defaultValue);
	}

	return ret;
}

bool Prefs::dateTimeVal(const QString &name, QDateTime &val) const
{
	QReadLocker locker(&m_lock);

	QMap<QString, EntryContent>::const_iterator it = m_defaults.constFind(name);
	if (it != m_defaults.constEnd()) {
		if (it->m_modified) {
			if (Q_UNLIKELY(m_prefsDb == Q_NULLPTR)) {
				Q_ASSERT(0);
				return false;
			}
			return m_prefsDb->dateTimeVal(name, val);
		} else {
			if (Q_UNLIKELY(it->m_type != PrefsDb::VAL_DATETIME)) {
				Q_ASSERT(0);
				return false;
			}
			/* Allow null values here. */
			val = it->m_defaultValue.toDateTime();
			return true;
		}
	}
	if (m_prefsDb != Q_NULLPTR) {
		return m_prefsDb->dateTimeVal(name, val);
	}
	/* No defaults, no database. */
	return false;
}

bool Prefs::setDateVal(const QString &name, const QDate &val)
{
	const enum PrefsDb::ValueType expectedType = PrefsDb::VAL_DATE;
	QList<Entry> setEntries;
	QList<Entry> createdEntries;
	bool ret = false; /* No default, no database. */

	{
		QWriteLocker locker(&m_lock);

		QMap<QString, EntryContent>::iterator it = m_defaults.find(name);
		if (it != m_defaults.end()) {
			if (Q_UNLIKELY(it->m_type != expectedType)) {
				return false; /* Cannot override default type. */
			}
			if (it->m_defaultValue == QVariant(val)) {
				/* New value matches default value. */
				if (m_prefsDb != Q_NULLPTR) {
					m_prefsDb->erase(name);
				}
				it->m_modified = false;
				setEntries.append(Entry(name, expectedType, val));
				ret = true;
				goto leave;
			}
		}
		if (m_prefsDb != Q_NULLPTR) {
			const bool alreadyExistent =
			   (it != m_defaults.end()) || m_prefsDb->contains(name);
			/* New value is different or default is not set. */
			if (Q_UNLIKELY(!m_prefsDb->setDateVal(name, val))) {
				/* Cannot modify database. */
				return false;
			}
			if (it != m_defaults.end()) {
				/* Default has been changed. */
				it->m_modified = true;
			}
			if (!alreadyExistent) {
				createdEntries.append(Entry(name, expectedType, val));
			}
			setEntries.append(Entry(name, expectedType, val));
			ret = true;
			goto leave;
		}
	}

leave:
	/* Signals must not be emitted when write lock is active. */
	for (const Entry &entry : setEntries) {
		emit entrySet(entry.m_content.m_type, entry.m_name, entry.m_content.m_defaultValue);
	}
	for (const Entry &entry : createdEntries) {
		emit entryCreated(entry.m_content.m_type, entry.m_name, entry.m_content.m_defaultValue);
	}

	return ret;
}

bool Prefs::dateVal(const QString &name, QDate &val) const
{
	QReadLocker locker(&m_lock);

	QMap<QString, EntryContent>::const_iterator it = m_defaults.constFind(name);
	if (it != m_defaults.constEnd()) {
		if (it->m_modified) {
			if (Q_UNLIKELY(m_prefsDb == Q_NULLPTR)) {
				Q_ASSERT(0);
				return false;
			}
			return m_prefsDb->dateVal(name, val);
		} else {
			if (Q_UNLIKELY(it->m_type != PrefsDb::VAL_DATE)) {
				Q_ASSERT(0);
				return false;
			}
			/* Allow null values here. */
			val = it->m_defaultValue.toDate();
			return true;
		}
	}
	if (m_prefsDb != Q_NULLPTR) {
		return m_prefsDb->dateVal(name, val);
	}
	/* No defaults, no database. */
	return false;
}

void Prefs::resetDefaults(void)
{
	/* No locker because not part of public interface. */

	QMap<QString, EntryContent>::iterator it = m_defaults.begin();
	while (it != m_defaults.end()) {
		if (it->hasDefault()) {
			it->m_modified = false;
			++it;
		} else {
			/* Completely erase content with no default values. */
			it = m_defaults.erase(it);
		}
	}
}
