/*
 * 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 <QDir>
#include <Qt> /* enum Qt::ToolButtonStyle */

#include "src/datovka_shared/compat_qt/random.h"
#include "src/datovka_shared/identifiers/account_id.h"
#include "src/datovka_shared/json/basic.h"
#include "src/datovka_shared/log/log.h"
#include "src/datovka_shared/settings/prefs.h"
#include "src/datovka_shared/settings/prefs_helper.h"
#include "src/settings/prefs_specific.h"
#include "src/settings/registry.h"

bool PrefsSpecific::messageDbOnDisk(const Prefs &prefs)
{
	bool val = false;
	return prefs.boolVal("storage.database.messages.on_disk.enabled",
	    val) && val;
}

bool PrefsSpecific::accountDbOnDisk(const Prefs &prefs)
{
	bool val = false;
	return prefs.boolVal("storage.database.accounts.on_disk.enabled",
	    val) && val;
}

bool PrefsSpecific::backgroundSync(const Prefs &prefs)
{
	bool val = false;
	return prefs.boolVal(
	    "window.main.action.sync_all_accounts.background.enabled", val) && val;
}

int PrefsSpecific::backgroundSyncTimerMinutes(const Prefs &prefs)
{
	qint64 val = 10;
	if (Q_UNLIKELY(!prefs.intVal(
	        "window.main.action.sync_all_accounts.background.timer.period.minutes",
	        val))) {
		Q_ASSERT(0);
		return 10;
	}
	return val;
}

void PrefsSpecific::setBackgroundSyncTimerMinutes(Prefs &prefs, int val)
{
	if (val < 5) {
		val = 5;
	}
	prefs.setIntVal(
	    "window.main.action.sync_all_accounts.background.timer.period.minutes",
	    val);
}

int PrefsSpecific::shadowSyncTimerMinutes(const Prefs &prefs)
{
	qint64 val = 10;
	if (Q_UNLIKELY(!prefs.intVal(
	        "window.main.action.shadow_sync_all_accounts.background.timer.period.minutes",
	        val))) {
		Q_ASSERT(0);
		return 10;
	}
	return val;
}

void PrefsSpecific::setShadowSyncTimerMinutes(Prefs &prefs, int val)
{
	if (val < 5) {
		val = 5;
	}
	prefs.setIntVal(
	    "window.main.action.shadow_sync_all_accounts.background.timer.period.minutes",
	    val);
}

bool PrefsSpecific::checkNewVersionsDefault(void)
{
#ifdef DISABLE_VERSION_CHECK_BY_DEFAULT
	return false;
#else /* !DISABLE_VERSION_CHECK_BY_DEFAULT */
	return true;
#endif /* DISABLE_VERSION_CHECK_BY_DEFAULT */
}

bool PrefsSpecific::canConfigureCheckNewVersions(void)
{
#if defined(DISABLE_VERSION_NOTIFICATION)
	return false;
#elif defined(Q_OS_WIN) /* !DISABLE_VERSION_NOTIFICATION */
	/* Registry settings can override the default behaviour. */
	return !(
	    RegPreferences::haveEntry(RegPreferences::LOC_POL,
	        RegPreferences::ENTR_DISABLE_VER_NOTIF) ||
	    RegPreferences::haveEntry(RegPreferences::LOC_SYS,
	        RegPreferences::ENTR_DISABLE_VER_NOTIF) ||
	    RegPreferences::haveEntry(RegPreferences::LOC_USR,
	        RegPreferences::ENTR_DISABLE_VER_NOTIF));
#else /* !Q_OS_WIN */
	return true;
#endif /* Q_OS_WIN */
}

bool PrefsSpecific::checkNewVersions(const Prefs &prefs)
{
	if (canConfigureCheckNewVersions()) {
		bool val = false;
		return prefs.boolVal(
		    "application.version.check.enabled", val) && val;
	} else {
#if defined(DISABLE_VERSION_NOTIFICATION)
		logInfoNL("%s",
		    "Version notification disabled at compile time.");
		return false;
#elif defined(Q_OS_WIN) /* !DISABLE_VERSION_NOTIFICATION */
		if (RegPreferences::haveEntry(RegPreferences::LOC_POL,
		        RegPreferences::ENTR_DISABLE_VER_NOTIF)) {
			return !RegPreferences::disableVersionNotification(
			    RegPreferences::LOC_POL);
		} else if (RegPreferences::haveEntry(RegPreferences::LOC_SYS,
		        RegPreferences::ENTR_DISABLE_VER_NOTIF)) {
			return !RegPreferences::disableVersionNotification(
			    RegPreferences::LOC_SYS);
		} else if (RegPreferences::haveEntry(RegPreferences::LOC_USR,
		        RegPreferences::ENTR_DISABLE_VER_NOTIF)) {
			return !RegPreferences::disableVersionNotification(
			    RegPreferences::LOC_USR);
		} else {
			/*
			 * The value could be deleted manually in between
			 * the calls.
			 */
			return checkNewVersionsDefault();
		}
#else /* !Q_OS_WIN */
		return checkNewVersionsDefault();
#endif /* Q_OS_WIN */
	}
}

void PrefsSpecific::setCheckNewVersions(Prefs &prefs, bool val)
{
	prefs.setBoolVal("application.version.check.enabled", val);
}

int PrefsSpecific::checkNewVersionsCooldown(const Prefs &prefs)
{
	qint64 val = 14400;
	if (Q_UNLIKELY(!prefs.intVal(
	        "application.version.check.timeout.seconds",
	        val))) {
		Q_ASSERT(0);
		return 14400;
	}
	return val;
}

void PrefsSpecific::setCheckNewVersionsCooldown(Prefs &prefs, int val)
{
	if (val < 0) {
		val = 0;
	}
	prefs.setIntVal("application.version.check.timeout.seconds", val);
}

bool PrefsSpecific::canAutoDownloadUpdate(const Prefs &prefs)
{
	bool val = false;
	return prefs.boolVal(
	    "application.update.automatic_download.enabled", val) && val;
}

void PrefsSpecific::setCanAutoDownloadUpdate(Prefs &prefs, bool val)
{
	prefs.setBoolVal("application.update.automatic_download.enabled", val);
}

int PrefsSpecific::autoDownloadUpdateAttemptMax(const Prefs &prefs)
{
	qint64 val = 2;
	if (Q_UNLIKELY(!prefs.intVal(
	        "application.update.automatic_download.attempt.max",
	        val))) {
		Q_ASSERT(0);
		return 2;
	}
	return val;
}

void PrefsSpecific::setAutoDownloadUpdateAttemptMax(Prefs &prefs, int val)
{
	if (val <= 0) {
		val = 0;
	}
	prefs.setIntVal("application.update.automatic_download.attempt.max", val);
}

QString PrefsSpecific::downloadedPkgVersion(const Prefs &prefs)
{
	QString val;
	prefs.strVal("application.update.downloaded_package.version", val);
	/* Has default value set. */
	return val;
}

void PrefsSpecific::setDownloadedPkgVersion(Prefs &prefs, const QString &val)
{
	prefs.setStrVal("application.update.downloaded_package.version", val);
}

QString PrefsSpecific::downloadedPkgSha256Sum(const Prefs &prefs)
{
	QString val;
	prefs.strVal("application.update.downloaded_package.sha256sum", val);
	/* Has default value set. */
	return val;
}

void PrefsSpecific::setDownloadedPkgSha256Sum(Prefs &prefs, const QString &val)
{
	prefs.setStrVal("application.update.downloaded_package.sha256sum", val);
}

QString PrefsSpecific::downloadedPkgPath(const Prefs &prefs)
{
	QString val;
	prefs.strVal("application.update.downloaded_package.path", val);
	/* Has default value set. */
	return val;
}

void PrefsSpecific::setDownloadedPkgPath(Prefs &prefs, const QString &val)
{
	prefs.setStrVal("application.update.downloaded_package.path", val);
}

int PrefsSpecific::isdsDownloadTimeoutMs(const Prefs &prefs)
{
	qint64 val = 0;
	if (Q_UNLIKELY(!prefs.intVal("network.isds.communication.timeout.ms",
	        val))) {
		Q_ASSERT(0);
		return 600000;
	}
	return val;
}

enum PrefsSpecific::CertValDate PrefsSpecific::certValDate(const Prefs &prefs)
{
	qint64 val = DOWNLOAD_DATE;
	if (Q_UNLIKELY(!prefs.intVal("crypto.message.validation.date.type",
	        val))) {
		Q_ASSERT(0);
		return DOWNLOAD_DATE;
	}
	switch (val) {
	case DOWNLOAD_DATE:
		return DOWNLOAD_DATE;
		break;
	case CURRENT_DATE:
		return CURRENT_DATE;
		break;
	default:
		Q_ASSERT(0);
		return DOWNLOAD_DATE;
		break;
	}
}

bool PrefsSpecific::checkCrl(const Prefs &prefs)
{
	bool val = false;
	return prefs.boolVal("crypto.crl.check.enabled", val) && val;
}

bool PrefsSpecific::useGlobalPaths(const Prefs &prefs)
{
	bool val = false;
	return prefs.boolVal("global.path.enabled", val) && val;
}

void PrefsSpecific::setUseGlobalPaths(Prefs &prefs, bool val)
{
	prefs.setBoolVal("global.path.enabled", val);
}

bool PrefsSpecific::recMgmtAutoUpload(const Prefs &prefs)
{
	bool val = false;
	return prefs.boolVal("records_management.action.automatic_upload.enabled", val) && val;
}

void PrefsSpecific::setRecMgmtAutoUpload(Prefs &prefs, bool val)
{
	prefs.setBoolVal("records_management.action.automatic_upload.enabled", val);
}

bool PrefsSpecific::recMgmtAutoAcntStatusUpload(const Prefs &prefs)
{
	bool val = true;
	return prefs.boolVal("records_management.action.automatic_account_status_upload.enabled", val) && val;
}

QString PrefsSpecific::saveAttachDir(const Prefs &prefs)
{
	QString val;
	prefs.strVal("global.path.save.attachment", val);
	/* Has default value set. */
	return val;
}

void PrefsSpecific::setSaveAttachDir(Prefs &prefs, const QString &val)
{
	prefs.setStrVal("global.path.save.attachment", val);
}

QString PrefsSpecific::loadAttachDir(const Prefs &prefs)
{
	QString val;
	prefs.strVal("global.path.load.attachment", val);
	/* Has default value set. */
	return val;
}

void PrefsSpecific::setLoadAttachDir(Prefs &prefs, const QString &val)
{
	prefs.setStrVal("global.path.load.attachment", val);
}

QString PrefsSpecific::viewZfoDir(const Prefs &prefs)
{
	QString val;
	prefs.strVal("global.path.load.zfo", val);
	if (val.isEmpty()) {
		val = QDir::homePath();
	}
	return val;
}

void PrefsSpecific::setViewZfoDir(Prefs &prefs, const QString &val)
{
	prefs.setStrVal("global.path.load.zfo", val);
}

QString PrefsSpecific::importDbDir(const Prefs &prefs)
{
	QString val;
	prefs.strVal("global.path.import.database", val);
	if (val.isEmpty()) {
		val = QDir::homePath();
	}
	return val;
}

void PrefsSpecific::setImportDbDir(Prefs &prefs, const QString &val)
{
	prefs.setStrVal("global.path.import.database", val);
}

QString PrefsSpecific::importDbZfo(const Prefs &prefs)
{
	QString val;
	prefs.strVal("global.path.import.zfo", val);
	if (val.isEmpty()) {
		val = QDir::homePath();
	}
	return val;
}

void PrefsSpecific::setImportDbZfo(Prefs &prefs, const QString &val)
{
	prefs.setStrVal("global.path.import.zfo", val);
}

/*!
 * @brief Get account-related string entry.
 *
 * @param[in] prefs Preferences.
 * @param[in] acntId Account identifier.
 * @param[in] suff Suffix of the key.
 * @param[in] dflt Default value if no entry found.
 * @return String entry, null string on error.
 */
static
QString acntStrEntry(const Prefs &prefs, const AcntId &acntId,
    const QString &suff, const QString &dflt)
{
	if (Q_UNLIKELY((!acntId.isValid()) || suff.isEmpty())) {
		Q_ASSERT(0);
		return QString();
	}

	QString val;
	const QString accountId = PrefsHelper::accountIdentifier(
	    acntId.username(), acntId.testing());
	/* key = "account." + accountId + suff */
	prefs.strVal("account." + accountId + suff, val);
	if (val.isEmpty()) {
		val = dflt;
	}
	return val;
}

/*!
 * @brief Set account-related string entry.
 *
 * @param[in,out] prefs Preferences.
 * @param[in] acntId Account identifier.
 * @param[in] suff Suffix of the key.
 * @param[in] val Value to be set.
 */
static
void setAcntStrEntry(Prefs &prefs, const AcntId &acntId,
    const QString &suff, const QString &val)
{
	if (Q_UNLIKELY((!acntId.isValid()) || suff.isEmpty())) {
		Q_ASSERT(0);
		return;
	}

	const QString accountId = PrefsHelper::accountIdentifier(
	    acntId.username(), acntId.testing());
	prefs.setStrVal("account." + accountId + suff, val);
}

QString PrefsSpecific::acntSaveAttachDir(const Prefs &prefs,
    const AcntId &acntId)
{
	return acntStrEntry(prefs, acntId, ".last.path.save.attachment",
	    QDir::homePath());
}

void PrefsSpecific::setAcntSaveAttachDir(Prefs &prefs, const AcntId &acntId,
    const QString &val)
{
	setAcntStrEntry(prefs, acntId, ".last.path.save.attachment", val);
}

QString PrefsSpecific::anctLoadAttachDir(const Prefs &prefs,
    const AcntId &acntId)
{
	return acntStrEntry(prefs, acntId, ".last.path.load.attachment",
	    QDir::homePath());
}

void PrefsSpecific::setAnctLoadAttachDir(Prefs &prefs, const AcntId &acntId,
    const QString &val)
{
	setAcntStrEntry(prefs, acntId, ".last.path.load.attachment", val);
}

QString PrefsSpecific::acntCorrespondenceDir(const Prefs &prefs,
    const AcntId &acntId)
{
	return acntStrEntry(prefs, acntId,
	    ".last.path.save.correspondence_overview", QDir::homePath());
}

void PrefsSpecific::setAcntCorrespondenceDir(Prefs &prefs, const AcntId &acntId,
    const QString &val)
{
	setAcntStrEntry(prefs, acntId,
	    ".last.path.save.correspondence_overview", val);
}

QString PrefsSpecific::acntZfoDir(const Prefs &prefs, const AcntId &acntId)
{
	return acntStrEntry(prefs, acntId, ".last.path.save.zfo",
	    QDir::homePath());
}

void PrefsSpecific::setAcntZfoDir(Prefs &prefs, const AcntId &acntId,
    const QString &val)
{
	setAcntStrEntry(prefs, acntId, ".last.path.save.zfo", val);
}

bool PrefsSpecific::showPdzNotification(const Prefs &prefs)
{
	bool val = true;
	return prefs.boolVal("window.create_message.notification.send_pdz.enabled", val) && val;
}

void PrefsSpecific::setShowPdzNotification(Prefs &prefs, bool val)
{
	prefs.setBoolVal("window.create_message.notification.send_pdz.enabled", val);
}

bool PrefsSpecific::showUpdateQuitAppNotification(const Prefs &prefs)
{
	bool val = true;
	return prefs.boolVal("window.about.notification.update.quit_app.enabled", val) && val;
}

void PrefsSpecific::setShowUpdateQuitAppNotification(Prefs &prefs, bool val)
{
	prefs.setBoolVal("window.about.notification.update.quit_app.enabled", val);
}

int PrefsSpecific::sentMsgDownloadWaitIntervalMs(const Prefs &prefs)
{
	qint64 val = 5000;
	if (Q_UNLIKELY(!prefs.intVal(
	        "action.send_message.tie.action.download_message.timeout.ms", val))) {
		Q_ASSERT(0);
		return 5000;
	}
	return val;
}

qint64 PrefsSpecific::bytesInMByte(const Prefs &prefs)
{
	qint64 val = 1000000; /* 1 M == 10^6 */
	if (Q_UNLIKELY(!prefs.intVal(
	        "message.limit.megabyte.bytes", val))) {
		return 1000000;
	}
	return val;
}

int PrefsSpecific::maxAttachmentMBytes(const Prefs &prefs)
{
	qint64 val = 20;
	if (Q_UNLIKELY(!prefs.intVal(
	        "message.basic.limit.attachments.megabytes.max", val))) {
		return 20;
	}
	return val;
}

int PrefsSpecific::maxOVMAttachmentMBytes(const Prefs &prefs)
{
	qint64 val = 50; /* Some OVM boxes allow to receive up to 50 MB. */
	if (Q_UNLIKELY(!prefs.intVal(
	        "message.some_ovm.limit.attachments.megabytes.max", val))) {
		return 50;
	}
	return val;
}

int PrefsSpecific::maxVoDZAttachmentMBytes(const Prefs &prefs)
{
	qint64 val = 100; /* VoDZ max size is 100MB. */
	if (Q_UNLIKELY(!prefs.intVal(
	        "message.vodz.limit.attachments.megabytes.max", val))) {
		return 100;
	}
	return val;
}

int PrefsSpecific::maxAttachmentNum(const Prefs &prefs)
{
	qint64 val = 50; /* Max 50 attachment files. */
	if (Q_UNLIKELY(!prefs.intVal(
	        "message.limit.attachment.count.max", val))) {
		return 50;
	}
	return val;
}

bool PrefsSpecific::enableVodzSending(const Prefs &prefs)
{
	bool val = false;
	return prefs.boolVal("window.send_message.vodz.enable",
	    val) && val;
}

QSize PrefsSpecific::dlgSize(const Prefs &prefs, const QString &name)
{
	qint64 w = 0;
	if (Q_UNLIKELY(!prefs.intVal(
	        QStringLiteral("window.dialogue.%1.position.width").arg(name), w))) {
		w = -1;
	}
	qint64 h = 0;
	if (Q_UNLIKELY(!prefs.intVal(
	        QStringLiteral("window.dialogue.%1.position.height").arg(name), h))) {
		h = -1;
	}
	if ((w > 0) && (h > 0)) {
		return QSize(w, h);
	} else {
		/* Return invalid value. */
		return QSize();
	}
}

void PrefsSpecific::setDlgSize(Prefs &prefs, const QString &name,
    const QSize &val, const QSize &dfltVal)
{
	/* Store valid and modified size only. */
	if (Q_UNLIKELY((!val.isValid()) || (val == dfltVal))) {
		/*
		 * Fallback to defaults if attempting to store invalid or
		 * default values.
		 */
		prefs.resetVal(
		    QStringLiteral("window.dialogue.%1.position.width").arg(name));
		prefs.resetVal(
		    QStringLiteral("window.dialogue.%1.position.height").arg(name));
	} else {
		prefs.setIntVal(
		    QStringLiteral("window.dialogue.%1.position.width").arg(name),
		    val.width());
		prefs.setIntVal(
		    QStringLiteral("window.dialogue.%1.position.height").arg(name),
		    val.height());
	}
}

int PrefsSpecific::exportedNamesParamMaxLength(const Prefs &prefs)
{
	qint64 val = 50;
	if (Q_UNLIKELY(!prefs.intVal(
	        "file_system.format.filename.export.truncate.max_chars", val))) {
		return 50;
	}
	return val;
}

QVector<double> PrefsSpecific::dlgTblRelColWidths(const Prefs &prefs,
    const QString &dlgName, const QString &tblName)
{
	QString strVal;
	if (Q_UNLIKELY(!prefs.strVal(
	        QStringLiteral("window.dialogue.%1.%2.column.relative_widths")
	            .arg(dlgName).arg(tblName), strVal))) {
		return QVector<double>();
	}

	bool iOk = false;
	QVector<double> val = Json::DoubleStringVector::fromJsonStr(
	    strVal.toUtf8(), &iOk);
	if (Q_UNLIKELY(!iOk)) {
		QVector<double>();
	}
	return val;
}

void PrefsSpecific::setDlgTblRelColWidths(Prefs &prefs, const QString &dlgName,
    const QString &tblName, const QVector<double> &val)
{
	if (Q_UNLIKELY(val.size() == 0)) {
		prefs.resetVal(
		    QStringLiteral("window.dialogue.%1.%2.column.relative_widths")
		        .arg(dlgName).arg(tblName));
	} else {
		prefs.setStrVal(
		    QStringLiteral("window.dialogue.%1.%2.column.relative_widths")
		        .arg(dlgName).arg(tblName),
		    Json::DoubleStringVector(val).toJsonData());
	}
}

QVector<qint64> PrefsSpecific::mainWinColWidths(const Prefs &prefs,
    const QString &tblName)
{
	QString strVal;
	if (Q_UNLIKELY(!prefs.strVal(
	        QStringLiteral("window.main.%1.column.widths")
	            .arg(tblName), strVal))) {
		return QVector<qint64>();
	}

	bool iOk = false;
	QVector<qint64> val = Json::Int64StringVector::fromJsonStr(
	    strVal.toUtf8(), &iOk);
	if (Q_UNLIKELY(!iOk)) {
		QVector<qint64>();
	}
	return val;
}

void PrefsSpecific::setMainWinTblColWidths(Prefs &prefs, const QString &tblName,
    const QVector<qint64> &val)
{
	if (Q_UNLIKELY(val.size() == 0)) {
		prefs.resetVal(
		    QStringLiteral("window.main.%1.column.widths").arg(tblName));
	} else {
		prefs.setStrVal(
		    QStringLiteral("window.main.%1.column.widths").arg(tblName),
		    Json::Int64StringVector(val).toJsonData());
	}
}

int PrefsSpecific::dlgSplitterSize(const Prefs &prefs, const QString &dlgName,
    const QString &splitterName)
{
	qint64 val = -1;
	if (Q_UNLIKELY(!prefs.intVal(
	        QStringLiteral("window.dialogue.%1.pane.%2.size")
	            .arg(dlgName).arg(splitterName), val))) {
		return -1;
	}
	return val;
}

void PrefsSpecific::setDlgSplitterSize(Prefs &prefs, const QString &dlgName,
    const QString &splitterName, int val)
{
	if (Q_UNLIKELY(val < 0)) {
		prefs.resetVal(
		    QStringLiteral("window.dialogue.%1.pane.%2.size")
		        .arg(dlgName).arg(splitterName));
	} else {
		prefs.setIntVal(
		    QStringLiteral("window.dialogue.%1.pane.%2.size")
		        .arg(dlgName).arg(splitterName), val);
	}
}

QVector<int> PrefsSpecific::dlgTblColSortOrder(const Prefs &prefs,
    const QString &dlgName, const QString &tblName)
{
	QString strVal;
	if (Q_UNLIKELY(!prefs.strVal(
	        QStringLiteral("window.dialogue.%1.%2.sort.column_and_order")
	            .arg(dlgName).arg(tblName), strVal))) {
		return QVector<int>();
	}

	bool iOk = false;
	QVector<int> val = Json::IntVector::fromJsonStr(
	    strVal.toUtf8(), &iOk);
	if (Q_UNLIKELY(!iOk)) {
		return QVector<int>();
	}
	return val;
}

void PrefsSpecific::setDlgTblColSortOrder(Prefs &prefs, const QString &dlgName,
    const QString &tblName, const QVector<int> &val)
{
	if (Q_UNLIKELY(val.size() == 0)) {
		prefs.resetVal(
		    QStringLiteral("window.dialogue.%1.%2.sort.column_and_order")
		        .arg(dlgName).arg(tblName));
	} else {
		prefs.setStrVal(
		    QStringLiteral("window.dialogue.%1.%2.sort.column_and_order")
		        .arg(dlgName).arg(tblName),
		    Json::IntVector(val).toJsonData());
	}
}

QVector<int> PrefsSpecific::mainWinTblColSortOrder(const Prefs &prefs,
    const QString &tblName)
{
	QString strVal;
	if (Q_UNLIKELY(!prefs.strVal(
	        QStringLiteral("window.main.%1.sort.column_and_order")
	            .arg(tblName), strVal))) {
		return QVector<int>();
	}

	bool iOk = false;
	QVector<int> val = Json::IntVector::fromJsonStr(strVal.toUtf8(), &iOk);
	if (Q_UNLIKELY(!iOk)) {
		QVector<int>();
	}
	return val;
}

void PrefsSpecific::setMainWinTblColSortOrder(Prefs &prefs,
    const QString &tblName, const QVector<int> &val)
{
	if (Q_UNLIKELY(val.size() == 0)) {
		prefs.resetVal(
		    QStringLiteral("window.main.%1.sort.column_and_order").arg(tblName));
	} else {
		prefs.setStrVal(
		    QStringLiteral("window.main.%1.sort.column_and_order").arg(tblName),
		    Json::IntVector(val).toJsonData());
	}
}

QDateTime PrefsSpecific::appFirstLaunchTime(const Prefs &prefs)
{
	QDateTime val;
	if (Q_UNLIKELY(!prefs.dateTimeVal("stats.app.launch.first.datetime", val))) {
		return QDateTime();
	}
	if (Q_UNLIKELY(!val.isValid())) {
		return QDateTime();
	}

	return val;
}

void PrefsSpecific::storeAppFirstLaunchTime(Prefs &prefs)
{
	prefs.setDateTimeVal(
	    "stats.app.launch.first.datetime", QDateTime::currentDateTime());
}

QDateTime PrefsSpecific::appLastLaunchTime(const Prefs &prefs)
{
	QDateTime val;
	if (Q_UNLIKELY(!prefs.dateTimeVal("stats.app.launch.last.datetime", val))) {
		return QDateTime();
	}
	if (Q_UNLIKELY(!val.isValid())) {
		return QDateTime();
	}

	return val;
}

void PrefsSpecific::storeAppLastLaunchTime(Prefs &prefs)
{
	prefs.setDateTimeVal(
	    "stats.app.launch.last.datetime", QDateTime::currentDateTime());
}

QDateTime PrefsSpecific::appPlannedDonationViewTime(const Prefs &prefs)
{
	QDateTime val;
	if (Q_UNLIKELY(!prefs.dateTimeVal("stats.app.action.planned_donation_view.datetime", val))) {
		return QDateTime();
	}
	if (Q_UNLIKELY(!val.isValid())) {
		return QDateTime();
	}

	return val;
}

void PrefsSpecific::setAppPlannedDonationViewTime(Prefs &prefs,
    const QDateTime &nextViewTime)
{
	prefs.setDateTimeVal(
	    "stats.app.action.planned_donation_view.datetime", nextViewTime);
}

void PrefsSpecific::planNextDonationViewTime(Prefs &prefs, bool shortTime)
{
	int intervalDays = 0;

	if (shortTime) {
		/* Short Time Interval: 3 months, tolerance +- 15 days */
		intervalDays = Compat::randBoundedInterval(75, 105);
	} else {
		/* Long Time Interval: 6 months, tolerance +- 30 days */
		intervalDays = Compat::randBoundedInterval(150, 210);
	}

	setAppPlannedDonationViewTime(prefs,
	    QDateTime::currentDateTimeUtc().addDays(intervalDays));
}

bool PrefsSpecific::isTimeToShowDonationDlg(Prefs &prefs)
{
	bool showDlg = false;
	const QDateTime nextViewTime = appPlannedDonationViewTime(prefs);

	if (Q_UNLIKELY(!nextViewTime.isValid())) {
		planNextDonationViewTime(prefs, true);
		return false;
	}

	showDlg = (nextViewTime < QDateTime::currentDateTimeUtc());
	if (Q_UNLIKELY(showDlg)) {
		planNextDonationViewTime(prefs, false);
	}

	return showDlg;
}

qint64 PrefsSpecific::appLaunchCount(const Prefs &prefs)
{
	qint64 val = -1;
	if (Q_UNLIKELY(!prefs.intVal("stats.app.launch.count", val))) {
		return -1;
	}

	return val;
}

void PrefsSpecific::incrementLaunchCount(Prefs &prefs)
{
	prefs.setIntVal("stats.app.launch.count", appLaunchCount(prefs) + 1);
}

QString PrefsSpecific::statsReportUniqueId(const Prefs &prefs)
{
	QString val;
	prefs.strVal("stats.app.report.unique_id", val);
	return val;
}

void PrefsSpecific::setStatsReportUniqueId(Prefs &prefs, const QString &val)
{
	prefs.setStrVal("stats.app.report.unique_id", val);
}

QDateTime PrefsSpecific::statsReportLastConsentRequest(const Prefs &prefs)
{
	QDateTime val;
	if (Q_UNLIKELY(!prefs.dateTimeVal("stats.app.report.action.ask_consent.datetime", val))) {
		return QDateTime();
	}
	if (Q_UNLIKELY(!val.isValid())) {
		return QDateTime();
	}

	return val;
}

void PrefsSpecific::setStatsReportLastConsentRequest(Prefs &prefs,
    const QDateTime &lastConsentDateTime)
{
	prefs.setDateTimeVal(
	    "stats.app.report.action.ask_consent.datetime", lastConsentDateTime);
}

bool PrefsSpecific::statsReportUsage(const Prefs &prefs)
{
	bool val = false;
	return prefs.boolVal(
	    "stats.app.report.usage.send.enabled", val) && val;
}

void PrefsSpecific::setStatsReportUsage(Prefs &prefs, bool enabled)
{
	prefs.setBoolVal("stats.app.report.usage.send.enabled", enabled);
}
