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

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

/* Null objects - for convenience. */
static const BackupEntry::File nullFile;
static const BackupEntry::MessageDb nullMessageDb;
static const QList<BackupEntry::MessageDb> nullMessageDbList;
static const QDateTime nullDateTime;
static const QString nullString;
static const QList<QString> nullStringList;

static const QString keyTime("time");
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");
static const QString keyAcntDb("account_database");
static const QString keyTagDb("tag_database");
static const QString keyFileName("file_name");

#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 */

class BackupEntry::FilePrivate {
public:
	FilePrivate(void)
	    : m_fileName()
	{ }

	QString m_fileName; /*!< Name of backed-up file. */
};

BackupEntry::File::File(void)
    : d_ptr(Q_NULLPTR)
{
}

BackupEntry::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
BackupEntry::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 */

BackupEntry::File::~File(void)
{
}

BackupEntry::File::File(const QString &f)
    : d_ptr(Q_NULLPTR)
{
	setFileName(f);
}

#ifdef Q_COMPILER_RVALUE_REFS
BackupEntry::File::File(QString &&f)
{
	setFileName(f);
}
#endif /* Q_COMPILER_RVALUE_REFS */

/*!
 * @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)

BackupEntry::File &BackupEntry::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
BackupEntry::File &BackupEntry::File::operator=(File &&other) Q_DECL_NOTHROW
{
	swap(*this, other);
	return *this;
}
#endif /* Q_COMPILER_RVALUE_REFS */

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

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

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

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

	return d->m_fileName;
}

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

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

BackupEntry::File BackupEntry::File::fromJson(
    const QJsonObject &jsonObj, bool *ok)
{
	QString val;
	bool ret = Json::Helper::readString(jsonObj, keyFileName, val,
	    Json::Helper::ACCEPT_NULL);
	if (ret) {
		if (ok != Q_NULLPTR) {
			*ok = true;
		}
		return File(val);
	} else {
		if (ok != Q_NULLPTR) {
			*ok = false;
		}
		return File();
	}
}

bool BackupEntry::File::toJson(QJsonObject *jsonObj) const
{
	if (jsonObj == Q_NULLPTR) {
		Q_ASSERT(0);
		return false;
	}

	if (!fileName().isEmpty()) {
		jsonObj->insert(keyFileName, fileName());
	}
	return true;
}

class BackupEntry::MessageDbPrivate {
public:
	MessageDbPrivate(void)
	    : m_testing(false), m_accountName(), m_boxId(), m_username(),
	    m_subDir(), m_fileNames()
	{ }

	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 back-up message databases are located. */
	QList<QString> m_fileNames; /*!< File names of the respective database files. */
};

BackupEntry::MessageDb::MessageDb(void)
    : d_ptr(Q_NULLPTR)
{
}

BackupEntry::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
BackupEntry::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 */

BackupEntry::MessageDb::~MessageDb(void)
{
}

BackupEntry::MessageDb::MessageDb(bool t, const QString &a, const QString &b,
    const QString &u, const QString &s, const QList<QString> &f)
    : d_ptr(Q_NULLPTR)
{
	setTesting(t);
	setAccountName(a);
	setBoxId(b);
	setUsername(u);
	setSubDir(s);
	setFileNames(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)

BackupEntry::MessageDb &BackupEntry::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
BackupEntry::MessageDb &BackupEntry::MessageDb::operator=(MessageDb &&other) Q_DECL_NOTHROW
{
	swap(*this, other);
	return *this;
}
#endif /* Q_COMPILER_RVALUE_REFS */

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

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

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

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

	return d->m_testing;
}

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

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

	return d->m_accountName;
}

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

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

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

	return d->m_boxId;
}

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

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

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

	return d->m_username;
}

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

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

const QString &BackupEntry::MessageDb::subDir(void) const
{
	Q_D(const MessageDb);
	if (Q_UNLIKELY(d == Q_NULLPTR)) {
		return nullString;
	}

	return d->m_subDir;
}

void BackupEntry::MessageDb::setSubDir(const QString &s)
{
	ensureMessageDbPrivate();
	Q_D(MessageDb);
	d->m_subDir = s;
}

#ifdef Q_COMPILER_RVALUE_REFS
void BackupEntry::MessageDb::setSubDir(QString &&s)
{
	ensureMessageDbPrivate();
	Q_D(MessageDb);
	d->m_subDir = s;
}
#endif /* Q_COMPILER_RVALUE_REFS */

const QList<QString> &BackupEntry::MessageDb::fileNames(void) const
{
	Q_D(const MessageDb);
	if (Q_UNLIKELY(d == Q_NULLPTR)) {
		return nullStringList;
	}

	return d->m_fileNames;
}

void BackupEntry::MessageDb::setFileNames(const QList<QString> &f)
{
	ensureMessageDbPrivate();
	Q_D(MessageDb);
	d->m_fileNames = f;
}

#ifdef Q_COMPILER_RVALUE_REFS
void BackupEntry::MessageDb::setFileNames(QList<QString> &&f)
{
	ensureMessageDbPrivate();
	Q_D(MessageDb);
	d->m_fileNames = f;
}
#endif /* Q_COMPILER_RVALUE_REFS */

BackupEntry::MessageDb BackupEntry::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;
	}

	if (Json::Helper::readStringList(jsonObj, keyFileNames, valList,
	        Json::Helper::ACCEPT_NULL)) {
		mBu.setFileNames(macroStdMove(valList));
	} else {
		goto fail;
	}

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

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

bool BackupEntry::MessageDb::toJson(QJsonObject *jsonObj) const
{
	if (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 (!fileNames().isEmpty()) {
		jsonObj->insert(keyFileNames,
		    QJsonArray::fromStringList(fileNames()));
	}
	return true;
}

class BackupEntryPrivate {
public:
	BackupEntryPrivate(void)
	    : m_dateTime(), m_messageDbs(), m_accountDb(), m_tagDb()
	{ }

	QDateTime m_dateTime; /*!< Time of the back-up. */
	QList<BackupEntry::MessageDb> m_messageDbs; /*!< List of message database entries. */
	BackupEntry::File m_accountDb; /*!< Account database file entry. */
	BackupEntry::File m_tagDb; /*!< Tag database file entry. */
};

BackupEntry::BackupEntry(void)
    : d_ptr(Q_NULLPTR)
{
}

BackupEntry::BackupEntry(const BackupEntry &other)
    : d_ptr((other.d_func() != Q_NULLPTR) ? (new (std::nothrow) BackupEntryPrivate) : Q_NULLPTR)
{
	Q_D(BackupEntry);
	if (d == Q_NULLPTR) {
		return;
	}

	*d = *other.d_func();
}

#ifdef Q_COMPILER_RVALUE_REFS
BackupEntry::BackupEntry(BackupEntry &&other) Q_DECL_NOEXCEPT
    : d_ptr(other.d_ptr.take()) //d_ptr(std::move(other.d_ptr))
{
}
#endif /* Q_COMPILER_RVALUE_REFS */

BackupEntry::~BackupEntry(void)
{
}

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

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

	*d = *other.d_func();

	return *this;
}

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

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

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

bool BackupEntry::isValid(void) const
{
	Q_D(const BackupEntry);
	return (!isNull()) &&
	    ((!d->m_messageDbs.isEmpty()) ||
	     d->m_accountDb.isValid() || d->m_tagDb.isValid());
}

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

	return d->m_dateTime;
}

void BackupEntry::setDateTime(const QDateTime &t)
{
	ensureBackupEntryPrivate();
	Q_D(BackupEntry);
	d->m_dateTime = t;
}

#ifdef Q_COMPILER_RVALUE_REFS
void BackupEntry::setDateTime(QDateTime &&t)
{
	ensureBackupEntryPrivate();
	Q_D(BackupEntry);
	d->m_dateTime = t;
}
#endif /* Q_COMPILER_RVALUE_REFS */

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

	return d->m_messageDbs;
}

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

#ifdef Q_COMPILER_RVALUE_REFS
void BackupEntry::setMessageDbs(QList<MessageDb> &&m)
{
	ensureBackupEntryPrivate();
	Q_D(BackupEntry);
	d->m_messageDbs = m;
}
#endif /* Q_COMPILER_RVALUE_REFS */

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

	return d->m_accountDb;
}

void BackupEntry::setAccountDb(const File &a)
{
	ensureBackupEntryPrivate();
	Q_D(BackupEntry);
	d->m_accountDb = a;
}

#ifdef Q_COMPILER_RVALUE_REFS
void BackupEntry::setAccountDb(File &&a)
{
	ensureBackupEntryPrivate();
	Q_D(BackupEntry);
	d->m_accountDb = a;
}
#endif /* Q_COMPILER_RVALUE_REFS */

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

	return d->m_tagDb;
}

void BackupEntry::setTagDb(const File &t)
{
	ensureBackupEntryPrivate();
	Q_D(BackupEntry);
	d->m_tagDb = t;
}

#ifdef Q_COMPILER_RVALUE_REFS
void BackupEntry::setTagDb(File &&t)
{
	ensureBackupEntryPrivate();
	Q_D(BackupEntry);
	d->m_tagDb = t;
}
#endif /* Q_COMPILER_RVALUE_REFS */

const BackupEntry::MessageDb &BackupEntry::messageDb(const QString &username,
    bool testing) const
{
	if (Q_UNLIKELY(username.isEmpty())) {
		Q_ASSERT(0);
		return nullMessageDb;
	}
	Q_D(const BackupEntry);
	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
QList<BackupEntry::MessageDb> messageDbBackupListExtract(const QJsonArray &arr,
    bool *ok = Q_NULLPTR)
{
	QList<BackupEntry::MessageDb> ml;

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

		bool iOk = false;
		BackupEntry::MessageDb mBu(
		    BackupEntry::MessageDb::fromJson(val.toObject(), &iOk));
		if (iOk) {
			ml.append(mBu);
		} else {
			if (ok != Q_NULLPTR) {
				*ok = false;
			}
			return QList<BackupEntry::MessageDb>();
		}
	}

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

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

	QJsonValue val;
	if (Json::Helper::readValue(jsonObj, key, val)) {
		if (Q_UNLIKELY(!val.isObject())) {
			if (ok != Q_NULLPTR) {
				*ok = false;
			}
			return BackupEntry::File();
		}

		bool iOk = false;
		fb = BackupEntry::File::fromJson(val.toObject(), &iOk);
		if (iOk) {
			if (ok != Q_NULLPTR) {
				*ok = true;
			}
			return fb;
		} else {
			if (ok != Q_NULLPTR) {
				*ok = false;
			}
			return BackupEntry::File();
		}
	}
	/* Accept missing value. */
	if (ok != Q_NULLPTR) {
		*ok = true;
	}
	return fb;
}

BackupEntry BackupEntry::fromJson(const QByteArray &json, bool *ok)
{
	BackupEntry 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));
				if (time.isValid()) {
					bu.setDateTime(macroStdMove(time));
				} else {
					goto fail;
				}
			}
		} else {
			goto fail;
		}
	}

	{ /* Message database list. */
		QJsonArray arr;
		if (Json::Helper::readArray(jsonObj, keyMsgDbs, arr,
		        Json::Helper::ACCEPT_NULL)) {
		} 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;
	}

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

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

static
bool databaseBackupListToArray(QJsonArray *arr,
    const QList<BackupEntry::MessageDb> &messageDbs)
{
	if (Q_UNLIKELY(arr == Q_NULLPTR)) {
		Q_ASSERT(0);
		return false;
	}
	foreach (const BackupEntry::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 BackupEntry::toJson(void) const
{
	QJsonObject jsonObj;
	if (dateTime().isValid()) {
		jsonObj.insert(keyTime, dateTime().toUTC().toString(Qt::ISODate));
	}
	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);
		}
	}

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