/*
 * 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 <QCryptographicHash>
#include <QDateTime>
#include <QJsonArray>
#include <QJsonObject>
#include <utility> /* ::std::swap */

#include "src/datovka_shared/compat/compiler.h" /* macroStdMove */
#include "src/datovka_shared/json/helper.h"
#include "src/json/backup.h"

/* Null objects - for convenience. */
static const Json::Backup::AppInfo nullAppInfo;
static const Json::Backup::Checksum nullChecksum;
static const Json::Backup::File nullFile;
static const QList<Json::Backup::File> nullFileList;
static const QList<Json::Backup::AssociatedDirectory> nullAssocDirList;
static const Json::Backup::MessageDb nullMessageDb;
static const QList<Json::Backup::MessageDb> nullMessageDbList;
static const QByteArray nullByteArray;
static const QDateTime nullDateTime;
static const QString nullString;

static const QString keyTime("time");
static const QString keyBackupType("backup_type");
static const QString keyAppInfo("application_info");
static const QString keyAppName("application_name");
static const QString keyAppVariant("application_variant");
static const QString keyAppVersion("application_version");
static const QString keyChecksum("checksum");
static const QString keyAlgorithm("algorithm");
static const QString keyValue("value");
static const QString keyMsgDbs("message_databases");
static const QString keyTesting("testing_environment");
static const QString keyAcntName("account_name");
static const QString keyBoxId("data_box_id");
static const QString keyUsername("username");
static const QString keySubdir("subdirectory");
static const QString keyFileNames("file_names"); /* Obsolete, converted to files. Used for backward compatibility. */
static const QString keyAcntDb("account_database");
static const QString keyTagDb("tag_database");
static const QString keyFileName("file_name");
static const QString keySize("size");
static const QString keyFiles("files");
static const QString keyDirName("directory_name");
static const QString keyAssociatedDirectories("associated_directories");

static const QString typeBackup("backup");
static const QString typeTransfer("transfer");

static const QString appName("datovka");

static const QString variantDesktop("desktop");
static const QString variantMobile("mobile");

static const QString sha512Str("SHA512");

class Json::Backup::AppInfoPrivate {
public:
	AppInfoPrivate(void)
	    : m_appName(), m_appVariant(), m_appVersion()
	{ }

	AppInfoPrivate &operator=(const AppInfoPrivate &other) Q_DECL_NOTHROW
	{
		m_appName = other.m_appName;
		m_appVariant = other.m_appVariant;
		m_appVersion = other.m_appVersion;

		return *this;
	}

	bool operator==(const AppInfoPrivate &other) const
	{
		return (m_appName == other.m_appName) &&
		    (m_appVariant == other.m_appVariant) &&
		    (m_appVersion == other.m_appVersion);
	}

	QString m_appName; /*!< Name of the application. */
	QString m_appVariant; /*!< Variant of the application. */
	QString m_appVersion; /*!< Application version string. */
};

Json::Backup::AppInfo::AppInfo(void)
    : Object(),
    d_ptr(Q_NULLPTR)
{
}

Json::Backup::AppInfo::AppInfo(const AppInfo &other)
    : Object(),
    d_ptr((other.d_func() != Q_NULLPTR) ? (new (::std::nothrow) AppInfoPrivate) : Q_NULLPTR)
{
	Q_D(AppInfo);
	if (d == Q_NULLPTR) {
		return;
	}

	*d = *other.d_func();
}

#ifdef Q_COMPILER_RVALUE_REFS
Json::Backup::AppInfo::AppInfo(AppInfo &&other) Q_DECL_NOEXCEPT
    : Object(),
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
    d_ptr(other.d_ptr.release()) //d_ptr(::std::move(other.d_ptr))
#else /* < Qt-5.12 */
    d_ptr(other.d_ptr.take())
#endif /* >= Qt-5.12 */
{
}
#endif /* Q_COMPILER_RVALUE_REFS */

Json::Backup::AppInfo::~AppInfo(void)
{
}

Json::Backup::AppInfo::AppInfo(const QString &n, const QString &var,
    const QString &ver)
    : Object(),
    d_ptr(new (::std::nothrow) AppInfoPrivate)
{
	Q_D(AppInfo);
	d->m_appName = n;
	d->m_appVariant = var;
	d->m_appVersion = ver;
}

/*!
 * @brief Ensures AppInfoPrivate presence.
 *
 * @note Returns if AppInfoPrivate could not be allocated.
 */
#define ensureAppInfoPrivate(_x_) \
	do { \
		if (Q_UNLIKELY(d_ptr == Q_NULLPTR)) { \
			AppInfoPrivate *p = new (::std::nothrow) AppInfoPrivate; \
			if (Q_UNLIKELY(p == Q_NULLPTR)) { \
				Q_ASSERT(0); \
				return _x_; \
			} \
			d_ptr.reset(p); \
		} \
	} while (0)

Json::Backup::AppInfo &Json::Backup::AppInfo::operator=(const AppInfo &other) Q_DECL_NOTHROW
{
	if (other.d_func() == Q_NULLPTR) {
		d_ptr.reset(Q_NULLPTR);
		return *this;
	}
	ensureAppInfoPrivate(*this);
	Q_D(AppInfo);

	*d = *other.d_func();

	return *this;
}

#ifdef Q_COMPILER_RVALUE_REFS
Json::Backup::AppInfo &Json::Backup::AppInfo::operator=(AppInfo &&other) Q_DECL_NOTHROW
{
	swap(*this, other);
	return *this;
}
#endif /* Q_COMPILER_RVALUE_REFS */

bool Json::Backup::AppInfo::operator==(const AppInfo &other) const
{
	Q_D(const AppInfo);
	if ((d == Q_NULLPTR) && ((other.d_func() == Q_NULLPTR))) {
		return true;
	} else if ((d == Q_NULLPTR) || ((other.d_func() == Q_NULLPTR))) {
		return false;
	}

	return *d == *other.d_func();
}

bool Json::Backup::AppInfo::operator!=(const AppInfo &other) const
{
	return !operator==(other);
}

void Json::Backup::AppInfo::swap(AppInfo &first, AppInfo &second) Q_DECL_NOTHROW
{
	using ::std::swap;
	swap(first.d_ptr, second.d_ptr);
}

bool Json::Backup::AppInfo::isNull(void) const
{
	Q_D(const AppInfo);
	return d == Q_NULLPTR;
}

bool Json::Backup::AppInfo::isValid(void) const
{
	Q_D(const AppInfo);
	return (!isNull()) && (!d->m_appName.isEmpty()) &&
	    (!d->m_appVariant.isEmpty()) && (!d->m_appVersion.isEmpty());
}

const QString &Json::Backup::AppInfo::appName(void) const
{
	Q_D(const AppInfo);
	if (Q_UNLIKELY(d == Q_NULLPTR)) {
		return nullString;
	}

	return d->m_appName;
}

void Json::Backup::AppInfo::setAppName(const QString &n)
{
	ensureAppInfoPrivate();
	Q_D(AppInfo);
	d->m_appName = n;
}

#ifdef Q_COMPILER_RVALUE_REFS
void Json::Backup::AppInfo::setAppName(QString &&n)
{
	ensureAppInfoPrivate();
	Q_D(AppInfo);
	d->m_appName = ::std::move(n);
}
#endif /* Q_COMPILER_RVALUE_REFS */

const QString &Json::Backup::AppInfo::appVariant(void) const
{
	Q_D(const AppInfo);
	if (Q_UNLIKELY(d == Q_NULLPTR)) {
		return nullString;
	}

	return d->m_appVariant;
}

void Json::Backup::AppInfo::setAppVariant(const QString &va)
{
	ensureAppInfoPrivate();
	Q_D(AppInfo);
	d->m_appVariant = va;
}

#ifdef Q_COMPILER_RVALUE_REFS
void Json::Backup::AppInfo::setAppVariant(QString &&va)
{
	ensureAppInfoPrivate();
	Q_D(AppInfo);
	d->m_appVariant = ::std::move(va);
}
#endif /* Q_COMPILER_RVALUE_REFS */

const QString &Json::Backup::AppInfo::appVersion(void) const
{
	Q_D(const AppInfo);
	if (Q_UNLIKELY(d == Q_NULLPTR)) {
		return nullString;
	}

	return d->m_appVersion;
}

void Json::Backup::AppInfo::setAppVersion(const QString &ve)
{
	ensureAppInfoPrivate();
	Q_D(AppInfo);
	d->m_appVersion = ve;
}

#ifdef Q_COMPILER_RVALUE_REFS
void Json::Backup::AppInfo::setAppVersion(QString &&ve)
{
	ensureAppInfoPrivate();
	Q_D(AppInfo);
	d->m_appVersion = ::std::move(ve);
}
#endif /* Q_COMPILER_RVALUE_REFS */

Json::Backup::AppInfo Json::Backup::AppInfo::fromJsonVal(
    const QJsonValue &jsonVal, bool *ok)
{
	AppInfo aBu;
	QString valStr;

	if (Q_UNLIKELY(!jsonVal.isObject())) {
		goto fail;
	}

	{
		const QJsonObject jsonObj = jsonVal.toObject();

		if (Helper::readString(jsonObj, keyAppName, valStr,
		        Helper::ACCEPT_NULL)) {
			aBu.setAppName(macroStdMove(valStr));
		} else {
			goto fail;
		}

		if (Helper::readString(jsonObj, keyAppVariant, valStr,
		        Helper::ACCEPT_NULL)) {
			aBu.setAppVariant(macroStdMove(valStr));
		} else {
			goto fail;
		}

		if (Helper::readString(jsonObj, keyAppVersion, valStr,
		        Helper::ACCEPT_NULL)) {
			aBu.setAppVersion(macroStdMove(valStr));
		} else {
			goto fail;
		}

		if (ok != Q_NULLPTR) {
			*ok = true;
		}
		return aBu;
	}

fail:
	if (ok != Q_NULLPTR) {
		*ok = false;
	}
	return AppInfo();
}

bool Json::Backup::AppInfo::toJsonVal(QJsonValue &jsonVal) const
{
	QJsonObject jsonObj;

	if (!appName().isEmpty()) {
		jsonObj.insert(keyAppName, appName());
	}
	if (!appVariant().isEmpty()) {
		jsonObj.insert(keyAppVariant, appVariant());
	}
	if (!appVersion().isEmpty()) {
		jsonObj.insert(keyAppVersion, appVersion());
	}

	jsonVal = jsonObj;
	return true;
}

class Json::Backup::ChecksumPrivate {
public:
	ChecksumPrivate(void)
	    : m_algorithm(Backup::Checksum::ALG_UNKNOWN), m_value()
	{ }

	ChecksumPrivate &operator=(const ChecksumPrivate &other) Q_DECL_NOTHROW
	{
		m_algorithm = other.m_algorithm;
		m_value = other.m_value;

		return *this;
	}

	bool operator==(const ChecksumPrivate &other) const
	{
		return (m_algorithm == other.m_algorithm) &&
		    (m_value == other.m_value);
	}

	enum Backup::Checksum::Algorithm m_algorithm; /*!< Hash algorithm. */
	QByteArray m_value; /*!< Hash value. */
};

Json::Backup::Checksum::Checksum(void)
    : Object(),
    d_ptr(Q_NULLPTR)
{
}

Json::Backup::Checksum::Checksum(const Checksum &other)
    : Object(),
    d_ptr((other.d_func() != Q_NULLPTR) ? (new (::std::nothrow) ChecksumPrivate) : Q_NULLPTR)
{
	Q_D(Checksum);
	if (d == Q_NULLPTR) {
		return;
	}

	*d = *other.d_func();
}

#ifdef Q_COMPILER_RVALUE_REFS
Json::Backup::Checksum::Checksum(Checksum &&other) Q_DECL_NOEXCEPT
    : Object(),
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
    d_ptr(other.d_ptr.release()) //d_ptr(::std::move(other.d_ptr))
#else /* < Qt-5.12 */
    d_ptr(other.d_ptr.take())
#endif /* >= Qt-5.12 */
{
}
#endif /* Q_COMPILER_RVALUE_REFS */

Json::Backup::Checksum::~Checksum(void)
{
}

Json::Backup::Checksum::Checksum(enum Algorithm a, const QByteArray &v)
    : Object(),
    d_ptr(new (::std::nothrow) ChecksumPrivate)
{
	Q_D(Checksum);
	d->m_algorithm = a;
	d->m_value = v;
}

#ifdef Q_COMPILER_RVALUE_REFS
Json::Backup::Checksum::Checksum(enum Algorithm a, QByteArray &&v)
    : Object(),
    d_ptr(new (::std::nothrow) ChecksumPrivate)
{
	Q_D(Checksum);
	d->m_algorithm = a;
	d->m_value = ::std::move(v);
}
#endif /* Q_COMPILER_RVALUE_REFS */

/*!
 * @brief Ensures ChecksumPrivate presence.
 *
 * @note Returns if ChecksumPrivate could not be allocated.
 */
#define ensureChecksumPrivate(_x_) \
	do { \
		if (Q_UNLIKELY(d_ptr == Q_NULLPTR)) { \
			ChecksumPrivate *p = new (::std::nothrow) ChecksumPrivate; \
			if (Q_UNLIKELY(p == Q_NULLPTR)) { \
				Q_ASSERT(0); \
				return _x_; \
			} \
			d_ptr.reset(p); \
		} \
	} while (0)

Json::Backup::Checksum &Json::Backup::Checksum::operator=(const Checksum &other) Q_DECL_NOTHROW
{
	if (other.d_func() == Q_NULLPTR) {
		d_ptr.reset(Q_NULLPTR);
		return *this;
	}
	ensureChecksumPrivate(*this);
	Q_D(Checksum);

	*d = *other.d_func();

	return *this;
}

#ifdef Q_COMPILER_RVALUE_REFS
Json::Backup::Checksum &Json::Backup::Checksum::operator=(Checksum &&other) Q_DECL_NOTHROW
{
	swap(*this, other);
	return *this;
}
#endif /* Q_COMPILER_RVALUE_REFS */

bool Json::Backup::Checksum::operator==(const Checksum &other) const
{
	Q_D(const Checksum);
	if ((d == Q_NULLPTR) && ((other.d_func() == Q_NULLPTR))) {
		return true;
	} else if ((d == Q_NULLPTR) || ((other.d_func() == Q_NULLPTR))) {
		return false;
	}

	return *d == *other.d_func();
}

bool Json::Backup::Checksum::operator!=(const Checksum &other) const
{
	return !operator==(other);
}

void Json::Backup::Checksum::swap(Checksum &first, Checksum &second) Q_DECL_NOTHROW
{
	using ::std::swap;
	swap(first.d_ptr, second.d_ptr);
}

bool Json::Backup::Checksum::isNull(void) const
{
	Q_D(const Checksum);
	return d == Q_NULLPTR;
}

bool Json::Backup::Checksum::isValid(void) const
{
	Q_D(const Checksum);
	return (!isNull()) && (d->m_algorithm != ALG_UNKNOWN) &&
	    (!d->m_value.isEmpty());
}

enum Json::Backup::Checksum::Algorithm Json::Backup::Checksum::algorithm(void) const
{
	Q_D(const Checksum);
	if (Q_UNLIKELY(d == Q_NULLPTR)) {
		return ALG_UNKNOWN;
	}

	return d->m_algorithm;
}

void Json::Backup::Checksum::setAlgorithm(enum Algorithm a)
{
	ensureChecksumPrivate();
	Q_D(Checksum);
	d->m_algorithm = a;
}

const QByteArray &Json::Backup::Checksum::value(void) const
{
	Q_D(const Checksum);
	if (Q_UNLIKELY(d == Q_NULLPTR)) {
		return nullByteArray;
	}

	return d->m_value;
}

void Json::Backup::Checksum::setValue(const QByteArray &v)
{
	ensureChecksumPrivate();
	Q_D(Checksum);
	d->m_value = v;
}

#ifdef Q_COMPILER_RVALUE_REFS
void Json::Backup::Checksum::setValue(QByteArray &&v)
{
	ensureChecksumPrivate();
	Q_D(Checksum);
	d->m_value = ::std::move(v);
}
#endif /* Q_COMPILER_RVALUE_REFS */

Json::Backup::Checksum Json::Backup::Checksum::computeChecksum(
    const QByteArray &data, enum Algorithm algorithm)
{
	enum QCryptographicHash::Algorithm alg = QCryptographicHash::Md4;

	switch (algorithm) {
	case ALG_SHA512:
		alg = QCryptographicHash::Sha512;
		break;
	default:
		Q_ASSERT(0);
		return Checksum();
		break;
	}

	QCryptographicHash hash(alg);
	hash.addData(data);
	return Checksum(algorithm, hash.result());
}

Json::Backup::Checksum Json::Backup::Checksum::computeChecksum(
    QIODevice *device, enum Algorithm algorithm)
{
	if (Q_UNLIKELY(device == Q_NULLPTR)) {
		Q_ASSERT(0);
		return Checksum();
	}

	enum QCryptographicHash::Algorithm alg = QCryptographicHash::Md4;

	switch (algorithm) {
	case ALG_SHA512:
		alg = QCryptographicHash::Sha512;
		break;
	default:
		Q_ASSERT(0);
		return Checksum();
		break;
	}

	QCryptographicHash hash(alg);
	hash.addData(device);
	return Checksum(algorithm, hash.result());
}

static
const QString &algorithm2str(enum Json::Backup::Checksum::Algorithm algorithm)
{
	switch (algorithm) {
	case Json::Backup::Checksum::ALG_SHA512:
		return sha512Str;
		break;
	default:
		return nullString;
		break;
	}
}

static
enum Json::Backup::Checksum::Algorithm str2algorithm(const QString &str)
{
	if (str == sha512Str) {
		return Json::Backup::Checksum::ALG_SHA512;
	} else {
		return Json::Backup::Checksum::ALG_UNKNOWN;
	}
}

Json::Backup::Checksum Json::Backup::Checksum::fromJsonVal(
    const QJsonValue &jsonVal, bool *ok)
{
	Checksum cBu;
	QString valStr;

	if (Q_UNLIKELY(!jsonVal.isObject())) {
		goto fail;
	}

	{
		const QJsonObject jsonObj = jsonVal.toObject();

		if (Helper::readString(jsonObj, keyAlgorithm, valStr,
		        Helper::ACCEPT_NULL)) {
			Algorithm algorithm = str2algorithm(valStr);
			if (algorithm != ALG_UNKNOWN) {
				cBu.setAlgorithm(algorithm);
			} else {
				goto fail;
			}
		} else {
			goto fail;
		}

		if (Helper::readString(jsonObj, keyValue, valStr,
		        Helper::ACCEPT_NULL)) {
			QByteArray value = QByteArray::fromHex(valStr.toUtf8());
			if (!value.isEmpty()) {
				cBu.setValue(macroStdMove(value));
			} else {
			}
		} else {
			goto fail;
		}

		if (ok != Q_NULLPTR) {
			*ok = true;
		}
		return cBu;
	}

fail:
	if (ok != Q_NULLPTR) {
		*ok = false;
	}
	return Checksum();
}

bool Json::Backup::Checksum::toJsonVal(QJsonValue &jsonVal) const
{
	QJsonObject jsonObj;

	if (algorithm() != ALG_UNKNOWN) {
		jsonObj.insert(keyAlgorithm, algorithm2str(algorithm()));
	}
	if (!value().isEmpty()) {
		jsonObj.insert(keyValue, QString::fromUtf8(value().toHex()));
	}

	jsonVal = jsonObj;
	return true;
}

class Json::Backup::FilePrivate {
public:
	FilePrivate(void)
	    : m_fileName(), m_checksum(), m_size(-1)
	{ }

	FilePrivate &operator=(const FilePrivate &other) Q_DECL_NOTHROW
	{
		m_fileName = other.m_fileName;
		m_checksum = other.m_checksum;
		m_size = other.m_size;

		return *this;
	}

	bool operator==(const FilePrivate &other) const
	{
		return (m_fileName == other.m_fileName) &&
		    (m_checksum == other.m_checksum) &&
		    (m_size == other.m_size);
	}

	QString m_fileName; /*!< Name of backed-up file. */
	Checksum m_checksum; /*!< File checksum. */
	qint64 m_size; /*!< File size, -1 means unknown. */
};

Json::Backup::File::File(void)
    : Object(),
    d_ptr(Q_NULLPTR)
{
}

Json::Backup::File::File(const File &other)
    : Object(),
    d_ptr((other.d_func() != Q_NULLPTR) ? (new (::std::nothrow) FilePrivate) : Q_NULLPTR)
{
	Q_D(File);
	if (d == Q_NULLPTR) {
		return;
	}

	*d = *other.d_func();
}

#ifdef Q_COMPILER_RVALUE_REFS
Json::Backup::File::File(File &&other) Q_DECL_NOEXCEPT
    : Object(),
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
    d_ptr(other.d_ptr.release()) //d_ptr(::std::move(other.d_ptr))
#else /* < Qt-5.12 */
    d_ptr(other.d_ptr.take())
#endif /* >= Qt-5.12 */
{
}
#endif /* Q_COMPILER_RVALUE_REFS */

Json::Backup::File::~File(void)
{
}

Json::Backup::File::File(const QString &f, const Checksum &c, qint64 s)
    : Object(),
    d_ptr(new (::std::nothrow) FilePrivate)
{
	Q_D(File);
	d->m_fileName = f;
	d->m_checksum = c;
	d->m_size = s;
}

#ifdef Q_COMPILER_RVALUE_REFS
Json::Backup::File::File(QString &&f, Checksum &&c, qint64 s)
    : Object(),
    d_ptr(new (::std::nothrow) FilePrivate)
{
	Q_D(File);
	d->m_fileName = ::std::move(f);
	d->m_checksum = ::std::move(c);
	d->m_size = s;
}
#endif /* Q_COMPILER_RVALUE_REFS */

/*!
 * @brief Ensures FilePrivate presence.
 *
 * @note Returns if FilePrivate could not be allocated.
 */
#define ensureFilePrivate(_x_) \
	do { \
		if (Q_UNLIKELY(d_ptr == Q_NULLPTR)) { \
			FilePrivate *p = new (::std::nothrow) FilePrivate; \
			if (Q_UNLIKELY(p == Q_NULLPTR)) { \
				Q_ASSERT(0); \
				return _x_; \
			} \
			d_ptr.reset(p); \
		} \
	} while (0)

Json::Backup::File &Json::Backup::File::operator=(const File &other) Q_DECL_NOTHROW
{
	if (other.d_func() == Q_NULLPTR) {
		d_ptr.reset(Q_NULLPTR);
		return *this;
	}
	ensureFilePrivate(*this);
	Q_D(File);

	*d = *other.d_func();

	return *this;
}

#ifdef Q_COMPILER_RVALUE_REFS
Json::Backup::File &Json::Backup::File::operator=(File &&other) Q_DECL_NOTHROW
{
	swap(*this, other);
	return *this;
}
#endif /* Q_COMPILER_RVALUE_REFS */

bool Json::Backup::File::operator==(const File &other) const
{
	Q_D(const File);
	if ((d == Q_NULLPTR) && ((other.d_func() == Q_NULLPTR))) {
		return true;
	} else if ((d == Q_NULLPTR) || ((other.d_func() == Q_NULLPTR))) {
		return false;
	}

	return *d == *other.d_func();
}

bool Json::Backup::File::operator!=(const File &other) const
{
	return !operator==(other);
}

void Json::Backup::File::swap(File &first, File &second) Q_DECL_NOTHROW
{
	using ::std::swap;
	swap(first.d_ptr, second.d_ptr);
}

bool Json::Backup::File::isNull(void) const
{
	Q_D(const File);
	return d == Q_NULLPTR;
}

bool Json::Backup::File::isValid(void) const
{
	Q_D(const File);
	return (!isNull()) && (!d->m_fileName.isEmpty());
}

const QString &Json::Backup::File::fileName(void) const
{
	Q_D(const File);
	if (Q_UNLIKELY(d == Q_NULLPTR)) {
		return nullString;
	}

	return d->m_fileName;
}

void Json::Backup::File::setFileName(const QString &f)
{
	ensureFilePrivate();
	Q_D(File);
	d->m_fileName = f;
}

#ifdef Q_COMPILER_RVALUE_REFS
void Json::Backup::File::setFileName(QString &&f)
{
	ensureFilePrivate();
	Q_D(File);
	d->m_fileName = ::std::move(f);
}
#endif /* Q_COMPILER_RVALUE_REFS */

const Json::Backup::Checksum &Json::Backup::File::checksum(void) const
{
	Q_D(const File);
	if (Q_UNLIKELY(d == Q_NULLPTR)) {
		return nullChecksum;
	}

	return d->m_checksum;
}

void Json::Backup::File::setChecksum(const Checksum &c)
{
	ensureFilePrivate();
	Q_D(File);
	d->m_checksum = c;
}

#ifdef Q_COMPILER_RVALUE_REFS
void Json::Backup::File::setChecksum(Checksum &&c)
{
	ensureFilePrivate();
	Q_D(File);
	d->m_checksum = ::std::move(c);
}
#endif /* Q_COMPILER_RVALUE_REFS */

qint64 Json::Backup::File::size(void) const
{
	Q_D(const File);
	if (Q_UNLIKELY(d == Q_NULLPTR)) {
		return -1;
	}

	return d->m_size;
}

void Json::Backup::File::setSize(qint64 s)
{
	ensureFilePrivate();
	Q_D(File);
	d->m_size = s;
}

static
Json::Backup::Checksum checksumBackupExtract(const QJsonObject &jsonObj,
    const QString &key, bool *ok = Q_NULLPTR)
{
	Json::Backup::Checksum cb;

	QJsonValue val;
	if (Json::Helper::readValue(jsonObj, key, val,
	        Json::Helper::ACCEPT_VALID)) {
		bool iOk = false;
		cb = Json::Backup::Checksum::fromJsonVal(val, &iOk);
		if (iOk) {
			if (ok != Q_NULLPTR) {
				*ok = true;
			}
			return cb;
		} else {
			if (ok != Q_NULLPTR) {
				*ok = false;
			}
			return Json::Backup::Checksum();
		}
	}
	/* Accept missing value. */
	if (ok != Q_NULLPTR) {
		*ok = true;
	}
	return cb;
}

Json::Backup::File Json::Backup::File::fromJsonVal(
    const QJsonValue &jsonVal, bool *ok)
{
	File fBu;
	QString valStr;
	bool iOk = false;
	qint64 valQint64;

	if (Q_UNLIKELY(!jsonVal.isObject())) {
		goto fail;
	}

	{
		const QJsonObject jsonObj = jsonVal.toObject();

		if (Helper::readString(jsonObj, keyFileName, valStr,
		        Helper::ACCEPT_NULL)) {
			fBu.setFileName(macroStdMove(valStr));
		} else {
			goto fail;
		}

		iOk = false;
		fBu.setChecksum(checksumBackupExtract(jsonObj, keyChecksum, &iOk));
		if (Q_UNLIKELY(!iOk)) {
			goto fail;
		}

		if (Helper::readQint64String(jsonObj, keySize, valQint64,
		        Helper::ACCEPT_MISSING)) {
			fBu.setSize(valQint64);
		} else {
			goto fail;
		}

		if (ok != Q_NULLPTR) {
			*ok = true;
		}
		return fBu;
	}

fail:
	if (ok != Q_NULLPTR) {
		*ok = false;
	}
	return File();
}

bool Json::Backup::File::toJsonVal(QJsonValue &jsonVal) const
{
	QJsonObject jsonObj;

	if (!fileName().isEmpty()) {
		jsonObj.insert(keyFileName, fileName());
	}
	if (checksum().isValid()) {
		QJsonValue val;
		if (checksum().toJsonVal(val)) {
			jsonObj.insert(keyChecksum, val);
		}
	}
	if (size() >= 0) {
		jsonObj.insert(keySize, QString::number(size()));
	}

	jsonVal = jsonObj;
	return true;
}

class Json::Backup::AssociatedDirectoryPrivate {
public:
	AssociatedDirectoryPrivate(void)
	    : m_dirName()
	{ }

	AssociatedDirectoryPrivate &operator=(const AssociatedDirectoryPrivate &other) Q_DECL_NOTHROW
	{
		m_dirName = other.m_dirName;

		return *this;
	}

	bool operator==(const AssociatedDirectoryPrivate &other) const
	{
		return (m_dirName == other.m_dirName);
	}

	QString m_dirName; /*!< Name of backed-up directory. */
};

Json::Backup::AssociatedDirectory::AssociatedDirectory(void)
    : Object(),
    d_ptr(Q_NULLPTR)
{
}

Json::Backup::AssociatedDirectory::AssociatedDirectory(const AssociatedDirectory &other)
    : Object(),
    d_ptr((other.d_func() != Q_NULLPTR) ? (new (::std::nothrow) AssociatedDirectoryPrivate) : Q_NULLPTR)
{
	Q_D(AssociatedDirectory);
	if (d == Q_NULLPTR) {
		return;
	}

	*d = *other.d_func();
}

#ifdef Q_COMPILER_RVALUE_REFS
Json::Backup::AssociatedDirectory::AssociatedDirectory(AssociatedDirectory &&other) Q_DECL_NOEXCEPT
    : Object(),
#  if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
    d_ptr(other.d_ptr.release()) //d_ptr(::std::move(other.d_ptr))
#  else /* < Qt-5.12 */
    d_ptr(other.d_ptr.take())
#  endif /* >= Qt-5.12 */
{
}
#endif /* Q_COMPILER_RVALUE_REFS */

Json::Backup::AssociatedDirectory::~AssociatedDirectory(void)
{
}

Json::Backup::AssociatedDirectory::AssociatedDirectory(const QString &dn)
    : Object(),
    d_ptr(new (::std::nothrow) AssociatedDirectoryPrivate)
{
	Q_D(AssociatedDirectory);
	d->m_dirName = dn;
}

#ifdef Q_COMPILER_RVALUE_REFS
Json::Backup::AssociatedDirectory::AssociatedDirectory(QString &&dn)
    : Object(),
    d_ptr(new (::std::nothrow) AssociatedDirectoryPrivate)
{
	Q_D(AssociatedDirectory);
	d->m_dirName = ::std::move(dn);
}
#endif /* Q_COMPILER_RVALUE_REFS */

/*!
 * @brief Ensures AssociatedDirectoryPrivate presence.
 *
 * @note Returns if AssociatedDirectoryPrivate could not be allocated.
 */
#define ensureAssociatedDirectoryPrivate(_x_) \
	do { \
		if (Q_UNLIKELY(d_ptr == Q_NULLPTR)) { \
			AssociatedDirectoryPrivate *p = new (::std::nothrow) AssociatedDirectoryPrivate; \
			if (Q_UNLIKELY(p == Q_NULLPTR)) { \
				Q_ASSERT(0); \
				return _x_; \
			} \
			d_ptr.reset(p); \
		} \
	} while (0)

Json::Backup::AssociatedDirectory &Json::Backup::AssociatedDirectory::operator=(const AssociatedDirectory &other) Q_DECL_NOTHROW
{
	if (other.d_func() == Q_NULLPTR) {
		d_ptr.reset(Q_NULLPTR);
		return *this;
	}
	ensureAssociatedDirectoryPrivate(*this);
	Q_D(AssociatedDirectory);

	*d = *other.d_func();

	return *this;
}

#ifdef Q_COMPILER_RVALUE_REFS
Json::Backup::AssociatedDirectory &Json::Backup::AssociatedDirectory::operator=(AssociatedDirectory &&other) Q_DECL_NOTHROW
{
	swap(*this, other);
	return *this;
}
#endif /* Q_COMPILER_RVALUE_REFS */

bool Json::Backup::AssociatedDirectory::operator==(const AssociatedDirectory &other) const
{
	Q_D(const AssociatedDirectory);
	if ((d == Q_NULLPTR) && ((other.d_func() == Q_NULLPTR))) {
		return true;
	} else if ((d == Q_NULLPTR) || ((other.d_func() == Q_NULLPTR))) {
		return false;
	}

	return *d == *other.d_func();
}

bool Json::Backup::AssociatedDirectory::operator!=(const AssociatedDirectory &other) const
{
	return !operator==(other);
}

void Json::Backup::AssociatedDirectory::swap(AssociatedDirectory &first, AssociatedDirectory &second) Q_DECL_NOTHROW
{
	using ::std::swap;
	swap(first.d_ptr, second.d_ptr);
}

bool Json::Backup::AssociatedDirectory::isNull(void) const
{
	Q_D(const AssociatedDirectory);
	return d == Q_NULLPTR;
}

bool Json::Backup::AssociatedDirectory::isValid(void) const
{
	Q_D(const AssociatedDirectory);
	return (!isNull()) && (!d->m_dirName.isEmpty());
}

const QString &Json::Backup::AssociatedDirectory::dirName(void) const
{
	Q_D(const AssociatedDirectory);
	if (Q_UNLIKELY(d == Q_NULLPTR)) {
		return nullString;
	}

	return d->m_dirName;
}

void Json::Backup::AssociatedDirectory::setDirName(const QString &dn)
{
	ensureAssociatedDirectoryPrivate();
	Q_D(AssociatedDirectory);
	d->m_dirName = dn;
}

#ifdef Q_COMPILER_RVALUE_REFS
void Json::Backup::AssociatedDirectory::setDirName(QString &&dn)
{
	ensureAssociatedDirectoryPrivate();
	Q_D(AssociatedDirectory);
	d->m_dirName = ::std::move(dn);
}
#endif /* Q_COMPILER_RVALUE_REFS */

Json::Backup::AssociatedDirectory Json::Backup::AssociatedDirectory::fromJsonVal(
    const QJsonValue &jsonVal, bool *ok)
{
	AssociatedDirectory dBu;
	QString valStr;

	if (Q_UNLIKELY(!jsonVal.isObject())) {
		goto fail;
	}

	{
		const QJsonObject jsonObj = jsonVal.toObject();

		if (Helper::readString(jsonObj, keyDirName, valStr,
		        Helper::ACCEPT_NULL)) {
			dBu.setDirName(macroStdMove(valStr));
		} else {
			goto fail;
		}

		if (ok != Q_NULLPTR) {
			*ok = true;
		}
		return dBu;
	}

fail:
	if (ok != Q_NULLPTR) {
		*ok = false;
	}
	return AssociatedDirectory();
}

bool Json::Backup::AssociatedDirectory::toJsonVal(QJsonValue &jsonVal) const
{
	QJsonObject jsonObj;

	if (!dirName().isEmpty()) {
		jsonObj.insert(keyDirName, dirName());
	}

	jsonVal = jsonObj;
	return true;
}

class Json::Backup::MessageDbPrivate {
public:
	MessageDbPrivate(void)
	    : m_testing(false), m_accountName(), m_boxId(), m_username(),
	    m_subdir(), m_files(), m_assocDirs()
	{ }

	MessageDbPrivate &operator=(const MessageDbPrivate &other) Q_DECL_NOTHROW
	{
		m_testing = other.m_testing;
		m_accountName = other.m_accountName;
		m_boxId = other.m_boxId;
		m_username = other.m_username;
		m_subdir = other.m_subdir;
		m_files = other.m_files;
		m_assocDirs = other.m_assocDirs;

		return *this;
	}

	bool operator==(const MessageDbPrivate &other) const
	{
		return (m_testing == other.m_testing) &&
		    (m_accountName == other.m_accountName) &&
		    (m_boxId == other.m_boxId) &&
		    (m_username == other.m_username) &&
		    (m_subdir == other.m_subdir) &&
		    (m_files == other.m_files) &&
		    (m_assocDirs == other.m_assocDirs);
	}

	bool m_testing; /*!< True if account is used in the ISDS testing environment. */
	QString m_accountName; /*!< Account identification as used in this application. */
	QString m_boxId; /*!< Data box identifier. */
	QString m_username; /*!< Account username. */
	QString m_subdir; /*!< Subdirectory where the backup message databases are located. */
	QList<File> m_files; /*!< Files of the database. */
	QList<AssociatedDirectory> m_assocDirs; /*!< Associated directories. */
};

Json::Backup::MessageDb::MessageDb(void)
    : Object(),
    d_ptr(Q_NULLPTR)
{
}

Json::Backup::MessageDb::MessageDb(const MessageDb &other)
    : Object(),
    d_ptr((other.d_func() != Q_NULLPTR) ? (new (::std::nothrow) MessageDbPrivate) : Q_NULLPTR)
{
	Q_D(MessageDb);
	if (d == Q_NULLPTR) {
		return;
	}

	*d = *other.d_func();
}

#ifdef Q_COMPILER_RVALUE_REFS
Json::Backup::MessageDb::MessageDb(MessageDb &&other) Q_DECL_NOEXCEPT
    : Object(),
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
    d_ptr(other.d_ptr.release()) //d_ptr(::std::move(other.d_ptr))
#else /* < Qt-5.12 */
    d_ptr(other.d_ptr.take())
#endif /* >= Qt-5.12 */
{
}
#endif /* Q_COMPILER_RVALUE_REFS */

Json::Backup::MessageDb::~MessageDb(void)
{
}

Json::Backup::MessageDb::MessageDb(bool t, const QString &a, const QString &b,
    const QString &u, const QString &s, const QList<File> &f,
    const QList<AssociatedDirectory> &ad)
    : Object(),
    d_ptr(new (::std::nothrow) MessageDbPrivate)
{
	Q_D(MessageDb);
	d->m_testing = t;
	d->m_accountName = a;
	d->m_boxId = b;
	d->m_username = u;
	d->m_subdir = s;
	d->m_files = f;
	d->m_assocDirs = ad;
}

/*!
 * @brief Ensures MessageDbPrivate presence.
 *
 * @note Returns if MessageDbPrivate could not be allocated.
 */
#define ensureMessageDbPrivate(_x_) \
	do { \
		if (Q_UNLIKELY(d_ptr == Q_NULLPTR)) { \
			MessageDbPrivate *p = new (::std::nothrow) MessageDbPrivate; \
			if (Q_UNLIKELY(p == Q_NULLPTR)) { \
				Q_ASSERT(0); \
				return _x_; \
			} \
			d_ptr.reset(p); \
		} \
	} while (0)

Json::Backup::MessageDb &Json::Backup::MessageDb::operator=(
    const MessageDb &other) Q_DECL_NOTHROW
{
	if (other.d_func() == Q_NULLPTR) {
		d_ptr.reset(Q_NULLPTR);
		return *this;
	}
	ensureMessageDbPrivate(*this);
	Q_D(MessageDb);

	*d = *other.d_func();

	return *this;
}

#ifdef Q_COMPILER_RVALUE_REFS
Json::Backup::MessageDb &Json::Backup::MessageDb::operator=(
    MessageDb &&other) Q_DECL_NOTHROW
{
	swap(*this, other);
	return *this;
}
#endif /* Q_COMPILER_RVALUE_REFS */

bool Json::Backup::MessageDb::operator==(const MessageDb &other) const
{
	Q_D(const MessageDb);
	if ((d == Q_NULLPTR) && ((other.d_func() == Q_NULLPTR))) {
		return true;
	} else if ((d == Q_NULLPTR) || ((other.d_func() == Q_NULLPTR))) {
		return false;
	}

	return *d == *other.d_func();
}

bool Json::Backup::MessageDb::operator!=(const MessageDb &other) const
{
	return !operator==(other);
}

void Json::Backup::MessageDb::swap(MessageDb &first, MessageDb &second) Q_DECL_NOTHROW
{
	using ::std::swap;
	swap(first.d_ptr, second.d_ptr);
}

bool Json::Backup::MessageDb::isNull(void) const
{
	Q_D(const MessageDb);
	return d == Q_NULLPTR;
}

bool Json::Backup::MessageDb::isValid(void) const
{
	Q_D(const MessageDb);
	return (!isNull()) && (!d->m_files.isEmpty());
}

bool Json::Backup::MessageDb::testing(void) const
{
	Q_D(const MessageDb);
	if (Q_UNLIKELY(d == Q_NULLPTR)) {
		return false;
	}

	return d->m_testing;
}

void Json::Backup::MessageDb::setTesting(bool t)
{
	ensureMessageDbPrivate();
	Q_D(MessageDb);
	d->m_testing = t;
}

const QString &Json::Backup::MessageDb::accountName(void) const
{
	Q_D(const MessageDb);
	if (Q_UNLIKELY(d == Q_NULLPTR)) {
		return nullString;
	}

	return d->m_accountName;
}

void Json::Backup::MessageDb::setAccountName(const QString &a)
{
	ensureMessageDbPrivate();
	Q_D(MessageDb);
	d->m_accountName = a;
}

#ifdef Q_COMPILER_RVALUE_REFS
void Json::Backup::MessageDb::setAccountName(QString &&a)
{
	ensureMessageDbPrivate();
	Q_D(MessageDb);
	d->m_accountName = ::std::move(a);
}
#endif /* Q_COMPILER_RVALUE_REFS */

const QString &Json::Backup::MessageDb::boxId(void) const
{
	Q_D(const MessageDb);
	if (Q_UNLIKELY(d == Q_NULLPTR)) {
		return nullString;
	}

	return d->m_boxId;
}

void Json::Backup::MessageDb::setBoxId(const QString &b)
{
	ensureMessageDbPrivate();
	Q_D(MessageDb);
	d->m_boxId = b;
}

#ifdef Q_COMPILER_RVALUE_REFS
void Json::Backup::MessageDb::setBoxId(QString &&b)
{
	ensureMessageDbPrivate();
	Q_D(MessageDb);
	d->m_boxId = ::std::move(b);
}
#endif /* Q_COMPILER_RVALUE_REFS */

const QString &Json::Backup::MessageDb::username(void) const
{
	Q_D(const MessageDb);
	if (Q_UNLIKELY(d == Q_NULLPTR)) {
		return nullString;
	}

	return d->m_username;
}

void Json::Backup::MessageDb::setUsername(const QString &u)
{
	ensureMessageDbPrivate();
	Q_D(MessageDb);
	d->m_username = u;
}

#ifdef Q_COMPILER_RVALUE_REFS
void Json::Backup::MessageDb::setUsername(QString &&u)
{
	ensureMessageDbPrivate();
	Q_D(MessageDb);
	d->m_username = ::std::move(u);
}
#endif /* Q_COMPILER_RVALUE_REFS */

const QString &Json::Backup::MessageDb::subdir(void) const
{
	Q_D(const MessageDb);
	if (Q_UNLIKELY(d == Q_NULLPTR)) {
		return nullString;
	}

	return d->m_subdir;
}

void Json::Backup::MessageDb::setSubdir(const QString &s)
{
	ensureMessageDbPrivate();
	Q_D(MessageDb);
	d->m_subdir = s;
}

#ifdef Q_COMPILER_RVALUE_REFS
void Json::Backup::MessageDb::setSubdir(QString &&s)
{
	ensureMessageDbPrivate();
	Q_D(MessageDb);
	d->m_subdir = ::std::move(s);
}
#endif /* Q_COMPILER_RVALUE_REFS */

const QList<Json::Backup::File> &Json::Backup::MessageDb::files(void) const
{
	Q_D(const MessageDb);
	if (Q_UNLIKELY(d == Q_NULLPTR)) {
		return nullFileList;
	}

	return d->m_files;
}

void Json::Backup::MessageDb::setFiles(const QList<File> &f)
{
	ensureMessageDbPrivate();
	Q_D(MessageDb);
	d->m_files = f;
}

#ifdef Q_COMPILER_RVALUE_REFS
void Json::Backup::MessageDb::setFiles(QList<File> &&f)
{
	ensureMessageDbPrivate();
	Q_D(MessageDb);
	d->m_files = ::std::move(f);
}
#endif /* Q_COMPILER_RVALUE_REFS */

const QList<Json::Backup::AssociatedDirectory> &Json::Backup::MessageDb::assocDirs(void) const
{
	Q_D(const MessageDb);
	if (Q_UNLIKELY(d == Q_NULLPTR)) {
		return nullAssocDirList;
	}

	return d->m_assocDirs;
}

void Json::Backup::MessageDb::setAssocDirs(const QList<AssociatedDirectory> &ad)
{
	ensureMessageDbPrivate();
	Q_D(MessageDb);
	d->m_assocDirs = ad;
}

#ifdef Q_COMPILER_RVALUE_REFS
void Json::Backup::MessageDb::setAssocDirs(QList<AssociatedDirectory> &&ad)
{
	ensureMessageDbPrivate();
	Q_D(MessageDb);
	d->m_assocDirs = ::std::move(ad);
}
#endif /* Q_COMPILER_RVALUE_REFS */

static
QList<Json::Backup::File> fileListExtract(const QJsonArray &arr,
    bool *ok = Q_NULLPTR)
{
	QList<Json::Backup::File> fl;

	for (const QJsonValue &val : arr) {
		bool iOk = false;
		Json::Backup::File fBu(
		    Json::Backup::File::fromJsonVal(val, &iOk));
		if (iOk) {
			fl.append(fBu);
		} else {
			if (ok != Q_NULLPTR) {
				*ok = false;
			}
			return QList<Json::Backup::File>();
		}
	}

	if (ok != Q_NULLPTR) {
		*ok = true;
	}
	return fl;
}

static
QList<Json::Backup::AssociatedDirectory> assocDirListExtract(
    const QJsonArray &arr, bool *ok = Q_NULLPTR)
{
	QList<Json::Backup::AssociatedDirectory> adl;

	for (const QJsonValue &val : arr) {
		bool iOk = false;
		Json::Backup::AssociatedDirectory dBu(
		    Json::Backup::AssociatedDirectory::fromJsonVal(val, &iOk));
		if (iOk) {
			adl.append(dBu);
		} else {
			if (ok != Q_NULLPTR) {
				*ok = false;
			}
			return QList<Json::Backup::AssociatedDirectory>();
		}
	}

	if (ok != Q_NULLPTR) {
		*ok = true;
	}
	return adl;
}

Json::Backup::MessageDb Json::Backup::MessageDb::fromJsonVal(
    const QJsonValue &jsonVal, bool *ok)
{
	MessageDb mBu;
	bool valBool;
	QString valStr;
	QStringList valList;

	if (Q_UNLIKELY(!jsonVal.isObject())) {
		goto fail;
	}

	{
		const QJsonObject jsonObj = jsonVal.toObject();

		if (Helper::readBool(jsonObj, keyTesting, valBool,
		        Helper::ACCEPT_VALID)) {
			mBu.setTesting(valBool);
		} else {
			goto fail;
		}

		if (Helper::readString(jsonObj, keyAcntName, valStr,
		        Helper::ACCEPT_NULL)) {
			mBu.setAccountName(macroStdMove(valStr));
		} else {
			goto fail;
		}

		if (Helper::readString(jsonObj, keyBoxId, valStr,
		        Helper::ACCEPT_NULL)) {
			mBu.setBoxId(macroStdMove(valStr));
		} else {
			/*
			 * Box identifier may be NULL when account was
			 * created from recovery or from local database file.
			 * Data_box_id entry may missing in the JSON file.
			 */
			//goto fail;
		}

		if (Helper::readString(jsonObj, keyUsername, valStr,
		        Helper::ACCEPT_NULL)) {
			mBu.setUsername(macroStdMove(valStr));
		} else {
			goto fail;
		}

		if (Helper::readString(jsonObj, keySubdir, valStr,
		        Helper::ACCEPT_NULL)) {
			mBu.setSubdir(macroStdMove(valStr));
		} else {
			goto fail;
		}

		/* For backward compatibility. */
		if (Helper::readStringList(jsonObj, keyFileNames, valList,
		        Helper::ACCEPT_MISSING)) {
			/* Convert files names to list of file entries. */
			QList<File> fileList;
			foreach (const QString &fileName, valList) {
				fileList.append(File(fileName, Checksum(), -1));
			}
			mBu.setFiles(macroStdMove(fileList));
		} else {
			goto fail;
		}

		/* Prefer files instead of file_names. */
		{ /* File list. */
			QJsonArray arr;
			bool iOk = false;
			if (Helper::readArray(jsonObj, keyFiles, arr,
			        mBu.files().isEmpty() ? Helper::ACCEPT_NULL : Helper::ACCEPT_MISSING)) {
			} else {
				goto fail;
			}
			iOk = false;
			mBu.setFiles(fileListExtract(arr, &iOk));
			if (Q_UNLIKELY(!iOk)) {
				goto fail;
			}
		}

		{ /* Associated directory list. */
			QJsonArray arr;
			bool iOk = false;
			if (Helper::readArray(jsonObj, keyAssociatedDirectories, arr,
			        Helper::ACCEPT_MISSING)) {
			} else {
				goto fail;
			}
			iOk = false;
			mBu.setAssocDirs(assocDirListExtract(arr, &iOk));
			if (Q_UNLIKELY(!iOk)) {
				goto fail;
			}
		}

		if (ok != Q_NULLPTR) {
			*ok = true;
		}
		return mBu;
	}

fail:
	if (ok != Q_NULLPTR) {
		*ok = false;
	}
	return MessageDb();
}

static
bool fileBackupListToArray(QJsonArray *arr,
    const QList<Json::Backup::File> &files)
{
	if (Q_UNLIKELY(arr == Q_NULLPTR)) {
		Q_ASSERT(0);
		return false;
	}
	for (const Json::Backup::File &bu : files) {
		if (Q_UNLIKELY(!bu.isValid())) {
			Q_ASSERT(0);
			return false;
		}
		QJsonValue jsonVal;
		if (Q_UNLIKELY(!bu.toJsonVal(jsonVal))) {
			Q_ASSERT(0);
			return false;
		}
		arr->append(jsonVal);
	}
	return true;
}

static
bool assocDirBackupListToArray(QJsonArray *arr,
     const QList<Json::Backup::AssociatedDirectory> &assocDirs)
{
	if (Q_UNLIKELY(arr == Q_NULLPTR)) {
		Q_ASSERT(0);
		return false;
	}
	for (const Json::Backup::AssociatedDirectory &bu : assocDirs) {
		if (Q_UNLIKELY(!bu.isValid())) {
			Q_ASSERT(0);
			return false;
		}
		QJsonValue jsonVal;
		if (Q_UNLIKELY(!bu.toJsonVal(jsonVal))) {
			Q_ASSERT(0);
			return false;
		}
		arr->append(jsonVal);
	}
	return true;
}

bool Json::Backup::MessageDb::toJsonVal(QJsonValue &jsonVal) const
{
	QJsonObject jsonObj;

	jsonObj.insert(keyTesting, testing());

	if (!accountName().isEmpty()) {
		jsonObj.insert(keyAcntName, accountName());
	}

	/*
	 * Box identificator may be NULL when account was created
	 * from recovery or from local database file. Empty data_box_id entry
	 * must be added into the JSON for correct recovery.
	 */
	jsonObj.insert(keyBoxId, boxId());

	if (!username().isEmpty()) {
		jsonObj.insert(keyUsername, username());
	}
	if (!subdir().isEmpty()) {
		jsonObj.insert(keySubdir, subdir());
	}
	if (!files().isEmpty()) {
		QJsonArray arr;
		if (fileBackupListToArray(&arr, files())) {
			jsonObj.insert(keyFiles, arr);
		}
	}
	if (!assocDirs().isEmpty()) {
		QJsonArray arr;
		if ( assocDirBackupListToArray(&arr, assocDirs())) {
			jsonObj.insert(keyAssociatedDirectories, arr);
		}
	}

	jsonVal = jsonObj;
	return true;
}

class Json::BackupPrivate {
public:
	BackupPrivate(void)
	    : m_dateTime(), m_type(Backup::BUT_UNKNOWN), m_appInfo(),
	    m_messageDbs(), m_accountDb(), m_tagDb(), m_files()
	{ }

	BackupPrivate &operator=(const BackupPrivate &other) Q_DECL_NOTHROW
	{
		m_dateTime = other.m_dateTime;
		m_type = other.m_type;
		m_appInfo = other.m_appInfo;
		m_messageDbs = other.m_messageDbs;
		m_accountDb = other.m_accountDb;
		m_tagDb = other.m_tagDb;
		m_files = other.m_files;

		return *this;
	}

	bool operator==(const BackupPrivate &other) const
	{
		return (m_dateTime == other.m_dateTime) &&
		    (m_type == other.m_type) &&
		    (m_appInfo == other.m_appInfo) &&
		    (m_messageDbs == other.m_messageDbs) &&
		    (m_accountDb == other.m_accountDb) &&
		    (m_tagDb == other.m_tagDb) &&
		    (m_files == other.m_files);
	}

	QDateTime m_dateTime; /*!< Time of the backup. */
	enum Backup::Type m_type; /*!< Backup type. */
	Backup::AppInfo m_appInfo; /*!< Application information. */
	QList<Backup::MessageDb> m_messageDbs; /*!< List of message database entries. */
	Backup::File m_accountDb; /*!< Account database file entry. */
	Backup::File m_tagDb; /*!< Tag database file entry. */
	QList<Backup::File> m_files; /*!< List of files. */
};

Json::Backup::Backup(void)
    : Object(),
    d_ptr(Q_NULLPTR)
{
}

Json::Backup::Backup(const Backup &other)
    : Object(),
    d_ptr((other.d_func() != Q_NULLPTR) ? (new (::std::nothrow) BackupPrivate) : Q_NULLPTR)
{
	Q_D(Backup);
	if (d == Q_NULLPTR) {
		return;
	}

	*d = *other.d_func();
}

#ifdef Q_COMPILER_RVALUE_REFS
Json::Backup::Backup(Backup &&other) Q_DECL_NOEXCEPT
    : Object(),
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
    d_ptr(other.d_ptr.release()) //d_ptr(::std::move(other.d_ptr))
#else /* < Qt-5.12 */
    d_ptr(other.d_ptr.take())
#endif /* >= Qt-5.12 */
{
}
#endif /* Q_COMPILER_RVALUE_REFS */

Json::Backup::~Backup(void)
{
}

/*!
 * @brief Ensures BackupPrivate presence.
 *
 * @note Returns if BackupPrivate could not be allocated.
 */
#define ensureBackupPrivate(_x_) \
	do { \
		if (Q_UNLIKELY(d_ptr == Q_NULLPTR)) { \
			BackupPrivate *p = new (::std::nothrow) BackupPrivate; \
			if (Q_UNLIKELY(p == Q_NULLPTR)) { \
				Q_ASSERT(0); \
				return _x_; \
			} \
			d_ptr.reset(p); \
		} \
	} while (0)

Json::Backup &Json::Backup::operator=(const Backup &other) Q_DECL_NOTHROW
{
	if (other.d_func() == Q_NULLPTR) {
		d_ptr.reset(Q_NULLPTR);
		return *this;
	}
	ensureBackupPrivate(*this);
	Q_D(Backup);

	*d = *other.d_func();

	return *this;
}

#ifdef Q_COMPILER_RVALUE_REFS
Json::Backup &Json::Backup::operator=(Backup &&other) Q_DECL_NOTHROW
{
	swap(*this, other);
	return *this;
}
#endif /* Q_COMPILER_RVALUE_REFS */

bool Json::Backup::operator==(const Backup &other) const
{
	Q_D(const Backup);
	if ((d == Q_NULLPTR) && ((other.d_func() == Q_NULLPTR))) {
		return true;
	} else if ((d == Q_NULLPTR) || ((other.d_func() == Q_NULLPTR))) {
		return false;
	}

	return *d == *other.d_func();
}

bool Json::Backup::operator!=(const Backup &other) const
{
	return !operator==(other);
}

void Json::Backup::swap(Backup &first, Backup &second) Q_DECL_NOTHROW
{
	using ::std::swap;
	swap(first.d_ptr, second.d_ptr);
}

bool Json::Backup::isNull(void) const
{
	Q_D(const Backup);
	return d == Q_NULLPTR;
}

bool Json::Backup::isValid(void) const
{
	Q_D(const Backup);
	/* Ignoring backup type value. */
	return (!isNull()) &&
	    ((!d->m_messageDbs.isEmpty()) ||
	     d->m_accountDb.isValid() || d->m_tagDb.isValid());
}

bool Json::Backup::isValidMobile(void) const
{
	Q_D(const Backup);
	return (!isNull()) && (d->m_type != BUT_UNKNOWN);
}

const QDateTime &Json::Backup::dateTime(void) const
{
	Q_D(const Backup);
	if (Q_UNLIKELY(d == Q_NULLPTR)) {
		return nullDateTime;
	}

	return d->m_dateTime;
}

void Json::Backup::setDateTime(const QDateTime &t)
{
	ensureBackupPrivate();
	Q_D(Backup);
	d->m_dateTime = t;
}

#ifdef Q_COMPILER_RVALUE_REFS
void Json::Backup::setDateTime(QDateTime &&t)
{
	ensureBackupPrivate();
	Q_D(Backup);
	d->m_dateTime = ::std::move(t);
}
#endif /* Q_COMPILER_RVALUE_REFS */

enum Json::Backup::Type Json::Backup::type(void) const
{
	Q_D(const Backup);
	if (Q_UNLIKELY(d == Q_NULLPTR)) {
		return BUT_UNKNOWN;
	}

	return d->m_type;
}

void Json::Backup::setType(enum Type t)
{
	ensureBackupPrivate();
	Q_D(Backup);
	d->m_type = t;
}

const Json::Backup::AppInfo &Json::Backup::appInfo(void) const
{
	Q_D(const Backup);
	if (Q_UNLIKELY(d == Q_NULLPTR)) {
		return nullAppInfo;
	}

	return d->m_appInfo;
}

void Json::Backup::setAppInfo(const AppInfo &a)
{
	ensureBackupPrivate();
	Q_D(Backup);
	d->m_appInfo = a;
}

#ifdef Q_COMPILER_RVALUE_REFS
void Json::Backup::setAppInfo(AppInfo &&a)
{
	ensureBackupPrivate();
	Q_D(Backup);
	d->m_appInfo = ::std::move(a);
}
#endif /* Q_COMPILER_RVALUE_REFS */

const QList<Json::Backup::MessageDb> &Json::Backup::messageDbs(void) const
{
	Q_D(const Backup);
	if (Q_UNLIKELY(d == Q_NULLPTR)) {
		return nullMessageDbList;
	}

	return d->m_messageDbs;
}

void Json::Backup::setMessageDbs(const QList<MessageDb> &m)
{
	ensureBackupPrivate();
	Q_D(Backup);
	d->m_messageDbs = m;
}

#ifdef Q_COMPILER_RVALUE_REFS
void Json::Backup::setMessageDbs(QList<MessageDb> &&m)
{
	ensureBackupPrivate();
	Q_D(Backup);
	d->m_messageDbs = ::std::move(m);
}
#endif /* Q_COMPILER_RVALUE_REFS */

const Json::Backup::File &Json::Backup::accountDb(void) const
{
	Q_D(const Backup);
	if (Q_UNLIKELY(d == Q_NULLPTR)) {
		return nullFile;
	}

	return d->m_accountDb;
}

void Json::Backup::setAccountDb(const File &a)
{
	ensureBackupPrivate();
	Q_D(Backup);
	d->m_accountDb = a;
}

#ifdef Q_COMPILER_RVALUE_REFS
void Json::Backup::setAccountDb(File &&a)
{
	ensureBackupPrivate();
	Q_D(Backup);
	d->m_accountDb = ::std::move(a);
}
#endif /* Q_COMPILER_RVALUE_REFS */

const Json::Backup::File &Json::Backup::tagDb(void) const
{
	Q_D(const Backup);
	if (Q_UNLIKELY(d == Q_NULLPTR)) {
		return nullFile;
	}

	return d->m_tagDb;
}

void Json::Backup::setTagDb(const File &t)
{
	ensureBackupPrivate();
	Q_D(Backup);
	d->m_tagDb = t;
}

#ifdef Q_COMPILER_RVALUE_REFS
void Json::Backup::setTagDb(File &&t)
{
	ensureBackupPrivate();
	Q_D(Backup);
	d->m_tagDb = ::std::move(t);
}
#endif /* Q_COMPILER_RVALUE_REFS */

const QList<Json::Backup::File> &Json::Backup::files(void) const
{
	Q_D(const Backup);
	if (Q_UNLIKELY(d == Q_NULLPTR)) {
		return nullFileList;
	}

	return d->m_files;
}

void Json::Backup::setFiles(const QList<File> &f)
{
	ensureBackupPrivate();
	Q_D(Backup);
	d->m_files = f;
}

#ifdef Q_COMPILER_RVALUE_REFS
void Json::Backup::setFiles(QList<File> &&f)
{
	ensureBackupPrivate();
	Q_D(Backup);
	d->m_files = ::std::move(f);
}
#endif /* Q_COMPILER_RVALUE_REFS */

const Json::Backup::MessageDb &Json::Backup::messageDb(const QString &username,
    bool testing) const
{
	if (Q_UNLIKELY(username.isEmpty())) {
		Q_ASSERT(0);
		return nullMessageDb;
	}
	Q_D(const Backup);
	if (Q_UNLIKELY(d == Q_NULLPTR)) {
		return nullMessageDb;
	}
	foreach (const MessageDb &mDb, messageDbs()) {
		if ((mDb.username() == username) && (mDb.testing() == testing)) {
			return mDb;
		}
	}
	return nullMessageDb;
}

static
Json::Backup::AppInfo appInfoBackupExtract(const QJsonObject &jsonObj,
    const QString &key, bool *ok = Q_NULLPTR)
{
	Json::Backup::AppInfo aib;

	QJsonValue val;
	if (Json::Helper::readValue(jsonObj, key, val,
	        Json::Helper::ACCEPT_VALID)) {
		bool iOk = false;
		aib = Json::Backup::AppInfo::fromJsonVal(val, &iOk);
		if (iOk) {
			if (ok != Q_NULLPTR) {
				*ok = true;
			}
			return aib;
		} else {
			if (ok != Q_NULLPTR) {
				*ok = false;
			}
			return Json::Backup::AppInfo();
		}
	}
	/* Accept missing value. */
	if (ok != Q_NULLPTR) {
		*ok = true;
	}
	return aib;
}

static
QList<Json::Backup::MessageDb> messageDbBackupListExtract(
    const QJsonArray &arr, bool *ok = Q_NULLPTR)
{
	QList<Json::Backup::MessageDb> ml;

	for (const QJsonValue &val : arr) {
		bool iOk = false;
		Json::Backup::MessageDb mBu(
		    Json::Backup::MessageDb::fromJsonVal(val, &iOk));
		if (iOk) {
			ml.append(mBu);
		} else {
			if (ok != Q_NULLPTR) {
				*ok = false;
			}
			return QList<Json::Backup::MessageDb>();
		}
	}

	if (ok != Q_NULLPTR) {
		*ok = true;
	}
	return ml;
}

static
Json::Backup::File fileBackupExtract(const QJsonObject &jsonObj,
    const QString &key, bool *ok = Q_NULLPTR)
{
	Json::Backup::File fb;

	QJsonValue val;
	if (Json::Helper::readValue(jsonObj, key, val,
	        Json::Helper::ACCEPT_VALID)) {
		bool iOk = false;
		fb = Json::Backup::File::fromJsonVal(val, &iOk);
		if (iOk) {
			if (ok != Q_NULLPTR) {
				*ok = true;
			}
			return fb;
		} else {
			if (ok != Q_NULLPTR) {
				*ok = false;
			}
			return Json::Backup::File();
		}
	}
	/* Accept missing value. */
	if (ok != Q_NULLPTR) {
		*ok = true;
	}
	return fb;
}

Json::Backup Json::Backup::fromJsonData(const QByteArray &json, bool *ok)
{
	QJsonObject jsonObj;
	if (Q_UNLIKELY(!Json::Helper::readRootObject(json, jsonObj))) {
		if (ok != Q_NULLPTR) {
			*ok = false;
		}
		return Backup();
	}

	return fromJsonVal(jsonObj, ok);
}

Json::Backup Json::Backup::fromJsonVal(const QJsonValue &jsonVal, bool *ok)
{
	Backup bu;

	if (Q_UNLIKELY(!jsonVal.isObject())) {
		goto fail;
	}

	{
		const QJsonObject jsonObj = jsonVal.toObject();
		bool iOk = false;

		{ /* Time. */
			QString val;
			if (Helper::readString(jsonObj, keyTime, val,
			        Helper::ACCEPT_NULL)) {
				if (!val.isEmpty()) {
					QDateTime time(QDateTime::fromString(val, Qt::ISODate).toLocalTime());
					if (time.isValid()) {
						bu.setDateTime(macroStdMove(time));
					} else {
						goto fail;
					}
				}
			} else {
				goto fail;
			}
		}

		{ /* Backup type. */
			QString val;
			if (Helper::readString(jsonObj, keyBackupType, val,
			        Helper::ACCEPT_MISSING)) {
				if (val == typeBackup) {
					bu.setType(Backup::BUT_BACKUP);
				} else if (val == typeTransfer) {
					bu.setType(Backup::BUT_TRANSFER);
				} else if (val.isEmpty()) {
					bu.setType(Backup::BUT_UNKNOWN);
				} else {
					Q_ASSERT(0);
					goto fail;
				}
			} else {
				goto fail;
			}
		}

		iOk = false;
		bu.setAppInfo(appInfoBackupExtract(jsonObj, keyAppInfo, &iOk));
		if (Q_UNLIKELY(!iOk)) {
			goto fail;
		}

		{ /* Message database list. */
			QJsonArray arr;
			if (Helper::readArray(jsonObj, keyMsgDbs, arr,
			        Helper::ACCEPT_MISSING)) {
			} else {
				goto fail;
			}
			iOk = false;
			bu.setMessageDbs(messageDbBackupListExtract(arr, &iOk));
			if (Q_UNLIKELY(!iOk)) {
				goto fail;
			}
		}

		/* Account database. */
		iOk = false;
		bu.setAccountDb(fileBackupExtract(jsonObj, keyAcntDb, &iOk));
		if (Q_UNLIKELY(!iOk)) {
			goto fail;
		}

		/* Tag database. */
		iOk = false;
		bu.setTagDb(fileBackupExtract(jsonObj, keyTagDb, &iOk));
		if (Q_UNLIKELY(!iOk)) {
			goto fail;
		}

		{ /* File list. */
			QJsonArray arr;
			if (Helper::readArray(jsonObj, keyFiles, arr,
			        Helper::ACCEPT_MISSING)) {
			} else {
				goto fail;
			}
			iOk = false;
			bu.setFiles(fileListExtract(arr, &iOk));
			if (Q_UNLIKELY(!iOk)) {
				goto fail;
			}
		}

		if (ok != Q_NULLPTR) {
			*ok = true;
		}
		return bu;
	}

fail:
	if (ok != Q_NULLPTR) {
		*ok = false;
	}
	return Backup();
}

static
bool databaseBackupListToArray(QJsonArray *arr,
    const QList<Json::Backup::MessageDb> &messageDbs)
{
	if (Q_UNLIKELY(arr == Q_NULLPTR)) {
		Q_ASSERT(0);
		return false;
	}
	foreach (const Json::Backup::MessageDb &bu, messageDbs) {
		if (Q_UNLIKELY(!bu.isValid())) {
			Q_ASSERT(0);
			return false;
		}
		QJsonValue jsonVal;
		if (Q_UNLIKELY(!bu.toJsonVal(jsonVal))) {
			Q_ASSERT(0);
			return false;
		}
		arr->append(jsonVal);
	}
	return true;
}

bool Json::Backup::toJsonVal(QJsonValue &jsonVal) const
{
	QJsonObject jsonObj;

	if (dateTime().isValid()) {
		jsonObj.insert(keyTime, dateTime().toUTC().toString(Qt::ISODate));
	}
	if (type() != BUT_UNKNOWN) {
		QString val;
		switch (type()) {
		case BUT_BACKUP:
			val = typeBackup;
			break;
		case BUT_TRANSFER:
			val = typeTransfer;
			break;
		default:
			Q_ASSERT(0);
			break;
		}
		if (!val.isEmpty()) {
			jsonObj.insert(keyBackupType, val);
		}
	}
	if (appInfo().isValid()) {
		QJsonValue val;
		if (appInfo().toJsonVal(val)) {
			jsonObj.insert(keyAppInfo, val);
		}
	}
	if (!messageDbs().isEmpty()) {
		QJsonArray arr;
		if (databaseBackupListToArray(&arr, messageDbs())) {
			jsonObj.insert(keyMsgDbs, arr);
		}
	}
	if (accountDb().isValid()) {
		QJsonValue val;
		if (accountDb().toJsonVal(val)) {
			jsonObj.insert(keyAcntDb, val);
		}
	}
	if (tagDb().isValid()) {
		QJsonValue val;
		if (tagDb().toJsonVal(val)) {
			jsonObj.insert(keyTagDb, val);
		}
	}
	if (!files().isEmpty()) {
		QJsonArray arr;
		if (fileBackupListToArray(&arr, files())) {
			jsonObj.insert(keyFiles, arr);
		}
	}

	jsonVal = jsonObj;
	return true;
}
