/*
 * Copyright (C) 2014-2021 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 <QJsonDocument>
#include <QJsonObject>
#include <utility> /* ::std::swap */

#include "src/datovka_shared/json/helper.h"
#include "src/json/backup.h"

#ifdef Q_COMPILER_RVALUE_REFS
#  define macroStdMove(x) ::std::move(x)
#else /* Q_COMPILER_RVALUE_REFS */
#  define macroStdMove(x) (x)
#endif /* Q_COMPILER_RVALUE_REFS */

/* 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 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 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)
    : d_ptr(Q_NULLPTR)
{
}

Json::Backup::AppInfo::AppInfo(const AppInfo &other)
    : 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
    : d_ptr(other.d_ptr.take()) //d_ptr(::std::move(other.d_ptr))
{
}
#endif /* Q_COMPILER_RVALUE_REFS */

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

Json::Backup::AppInfo::AppInfo(const QString &n, const QString &var,
    const QString &ver)
    : d_ptr(Q_NULLPTR)
{
	setAppName(n);
	setAppVariant(var);
	setAppVersion(ver);
}

/*!
 * @brief Ensures private app info presence.
 *
 * @note Returns if private app info 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::fromJson(
    const QJsonObject &jsonObj, bool *ok)
{
	AppInfo aBu;
	QString valStr;

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

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

	if (Json::Helper::readString(jsonObj, keyAppVersion, valStr,
	        Json::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::toJson(QJsonObject *jsonObj) const
{
	if (Q_UNLIKELY(jsonObj == Q_NULLPTR)) {
		Q_ASSERT(0);
		return false;
	}

	if (!appName().isEmpty()) {
		jsonObj->insert(keyAppName, appName());
	}
	if (!appVariant().isEmpty()) {
		jsonObj->insert(keyAppVariant, appVariant());
	}
	if (!appVersion().isEmpty()) {
		jsonObj->insert(keyAppVersion, appVersion());
	}
	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)
    : d_ptr(Q_NULLPTR)
{
}

Json::Backup::Checksum::Checksum(const Checksum &other)
    : 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
    : d_ptr(other.d_ptr.take()) //d_ptr(::std::move(other.d_ptr))
{
}
#endif /* Q_COMPILER_RVALUE_REFS */

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

Json::Backup::Checksum::Checksum(enum Algorithm a, const QByteArray &v)
    : d_ptr(Q_NULLPTR)
{
	setAlgorithm(a);
	setValue(v);
}

#ifdef Q_COMPILER_RVALUE_REFS
Json::Backup::Checksum::Checksum(enum Algorithm a, QByteArray &&v)
{
	setAlgorithm(a);
	setValue(::std::move(v));
}
#endif /* Q_COMPILER_RVALUE_REFS */

/*!
 * @brief Ensures private checksum presence.
 *
 * @note Returns if private checksum 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::fromJson(
    const QJsonObject &jsonObj, bool *ok)
{
	Checksum cBu;
	QString valStr;

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

	if (Json::Helper::readString(jsonObj, keyValue, valStr,
	        Json::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::toJson(QJsonObject *jsonObj) const
{
	if (Q_UNLIKELY(jsonObj == Q_NULLPTR)) {
		Q_ASSERT(0);
		return false;
	}

	if (algorithm() != ALG_UNKNOWN) {
		jsonObj->insert(keyAlgorithm, algorithm2str(algorithm()));
	}
	if (!value().isEmpty()) {
		jsonObj->insert(keyValue, QString::fromUtf8(value().toHex()));
	}
	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)
    : d_ptr(Q_NULLPTR)
{
}

Json::Backup::File::File(const File &other)
    : 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
    : d_ptr(other.d_ptr.take()) //d_ptr(::std::move(other.d_ptr))
{
}
#endif /* Q_COMPILER_RVALUE_REFS */

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

Json::Backup::File::File(const QString &f, const Checksum &c, qint64 s)
    : d_ptr(Q_NULLPTR)
{
	setFileName(f);
	setChecksum(c);
	setSize(s);
}

/*!
 * @brief Ensures private file presence.
 *
 * @note Returns if private file 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)) {
		if (Q_UNLIKELY(!val.isObject())) {
			if (ok != Q_NULLPTR) {
				*ok = false;
			}
			return Json::Backup::Checksum();
		}

		bool iOk = false;
		cb = Json::Backup::Checksum::fromJson(val.toObject(), &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::fromJson(
    const QJsonObject &jsonObj, bool *ok)
{
	File fBu;
	QString valStr;
	bool iOk = false;
	qint64 valQint64;

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

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

	if (Json::Helper::readQint64String(jsonObj, keySize, valQint64,
	    Json::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::toJson(QJsonObject *jsonObj) const
{
	if (Q_UNLIKELY(jsonObj == Q_NULLPTR)) {
		Q_ASSERT(0);
		return false;
	}

	if (!fileName().isEmpty()) {
		jsonObj->insert(keyFileName, fileName());
	}
	if (checksum().isValid()) {
		QJsonObject obj;
		if (checksum().toJson(&obj)) {
			jsonObj->insert(keyChecksum, obj);
		}
	}
	if (size() >= 0) {
		jsonObj->insert(keySize, QString::number(size()));
	}
	return true;
}

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

	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;

		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);
	}

	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. */
};

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

Json::Backup::MessageDb::MessageDb(const MessageDb &other)
    : 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
    : d_ptr(other.d_ptr.take()) //d_ptr(::std::move(other.d_ptr))
{
}
#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)
    : d_ptr(Q_NULLPTR)
{
	setTesting(t);
	setAccountName(a);
	setBoxId(b);
	setUsername(u);
	setSubdir(s);
	setFiles(f);
}

/*!
 * @brief Ensures private message db presence.
 *
 * @note Returns if private message db 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 */

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

	foreach (const QJsonValue &val, arr) {
		if (Q_UNLIKELY(!val.isObject())) {
			if (ok != Q_NULLPTR) {
				*ok = false;
			}
			return QList<Json::Backup::File>();
		}

		bool iOk = false;
		Json::Backup::File fBu(
		    Json::Backup::File::fromJson(val.toObject(), &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;
}

Json::Backup::MessageDb Json::Backup::MessageDb::fromJson(
    const QJsonObject &jsonObj, bool *ok)
{
	MessageDb mBu;
	bool valBool;
	QString valStr;
	QStringList valList;

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

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

	if (Json::Helper::readString(jsonObj, keyBoxId, valStr,
	        Json::Helper::ACCEPT_NULL)) {
		mBu.setBoxId(macroStdMove(valStr));
	} else {
		goto fail;
	}

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

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

	/* For backward compatibility. */
	if (Json::Helper::readStringList(jsonObj, keyFileNames, valList,
	        Json::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 (Json::Helper::readArray(jsonObj, keyFiles, arr,
		        mBu.files().isEmpty() ? Json::Helper::ACCEPT_NULL : Json::Helper::ACCEPT_MISSING)) {
		} else {
			goto fail;
		}
		iOk = false;
		mBu.setFiles(fileListExtract(arr, &iOk));
		if (!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;
	}
	foreach (const Json::Backup::File &bu, files) {
		if (Q_UNLIKELY(!bu.isValid())) {
			Q_ASSERT(0);
			return false;
		}
		QJsonObject jsonObj;
		if (Q_UNLIKELY(!bu.toJson(&jsonObj))) {
			Q_ASSERT(0);
			return false;
		}
		arr->append(jsonObj);
	}
	return true;
}

bool Json::Backup::MessageDb::toJson(QJsonObject *jsonObj) const
{
	if (Q_UNLIKELY(jsonObj == Q_NULLPTR)) {
		Q_ASSERT(0);
		return false;
	}

	jsonObj->insert(keyTesting, testing());

	if (!accountName().isEmpty()) {
		jsonObj->insert(keyAcntName, accountName());
	}
	if (!boxId().isEmpty()) {
		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);
		}
	}
	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)
    : d_ptr(Q_NULLPTR)
{
}

Json::Backup::Backup(const Backup &other)
    : 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
    : d_ptr(other.d_ptr.take()) //d_ptr(::std::move(other.d_ptr))
{
}
#endif /* Q_COMPILER_RVALUE_REFS */

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

/*!
 * @brief Ensures private backup presence.
 *
 * @note Returns if private backup 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());
}

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)) {
		if (Q_UNLIKELY(!val.isObject())) {
			if (ok != Q_NULLPTR) {
				*ok = false;
			}
			return Json::Backup::AppInfo();
		}

		bool iOk = false;
		aib = Json::Backup::AppInfo::fromJson(val.toObject(), &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;

	foreach (const QJsonValue &val, arr) {
		if (Q_UNLIKELY(!val.isObject())) {
			if (ok != Q_NULLPTR) {
				*ok = false;
			}
			return QList<Json::Backup::MessageDb>();
		}

		bool iOk = false;
		Json::Backup::MessageDb mBu(
		    Json::Backup::MessageDb::fromJson(val.toObject(), &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)) {
		if (Q_UNLIKELY(!val.isObject())) {
			if (ok != Q_NULLPTR) {
				*ok = false;
			}
			return Json::Backup::File();
		}

		bool iOk = false;
		fb = Json::Backup::File::fromJson(val.toObject(), &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::fromJson(const QByteArray &json, bool *ok)
{
	Backup bu;
	QJsonObject jsonObj;
	bool iOk = false;
	if (!Json::Helper::readRootObject(json, jsonObj)) {
		goto fail;
	}

	{ /* Time. */
		QString val;
		if (Json::Helper::readString(jsonObj, keyTime, val,
		        Json::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 (Json::Helper::readString(jsonObj, keyBackupType, val,
		        Json::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 (!iOk) {
		goto fail;
	}

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

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

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

	{ /* File list. */
		QJsonArray arr;
		if (Json::Helper::readArray(jsonObj, keyFiles, arr,
		        Json::Helper::ACCEPT_MISSING)) {
		} else {
			goto fail;
		}
		iOk = false;
		bu.setFiles(fileListExtract(arr, &iOk));
		if (!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;
		}
		QJsonObject jsonObj;
		if (Q_UNLIKELY(!bu.toJson(&jsonObj))) {
			Q_ASSERT(0);
			return false;
		}
		arr->append(jsonObj);
	}
	return true;
}

QByteArray Json::Backup::toJson(void) 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()) {
		QJsonObject obj;
		if (appInfo().toJson(&obj)) {
			jsonObj.insert(keyAppInfo, obj);
		}
	}
	if (!messageDbs().isEmpty()) {
		QJsonArray arr;
		if (databaseBackupListToArray(&arr, messageDbs())) {
			jsonObj.insert(keyMsgDbs, arr);
		}
	}
	if (accountDb().isValid()) {
		QJsonObject obj;
		if (accountDb().toJson(&obj)) {
			jsonObj.insert(keyAcntDb, obj);
		}
	}
	if (tagDb().isValid()) {
		QJsonObject obj;
		if (tagDb().toJson(&obj)) {
			jsonObj.insert(keyTagDb, obj);
		}
	}
	if (!files().isEmpty()) {
		QJsonArray arr;
		if (fileBackupListToArray(&arr, files())) {
			jsonObj.insert(keyFiles, arr);
		}
	}

	return QJsonDocument(jsonObj).toJson(QJsonDocument::Indented);
}
