/*
 * Copyright (C) 2014-2022 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 <QJsonObject>
#include <QJsonValue>
#include <utility> /* ::std::move, ::std::swap */

#include "src/datovka_shared/json/helper.h"
#include "src/datovka_shared/log/log.h"
#include "src/datovka_shared/records_management/json/upload_hierarchy.h"

static
const QString keyName("name");
static
const QString keyId("id");
static
const QString keyMetadata("metadata");
static
const QString keyCaseId("case_id");
static
const QString keyCourtCaseId("court_case_id");
static
const QString keySub("sub");

RecMgmt::UploadHierarchyResp::NodeEntry::NodeEntry(void)
    : m_super(Q_NULLPTR),
    m_name(),
    m_id(),
    m_metadata(),
    m_caseId(),
    m_courtCaseId(),
    m_sub()
{
}

RecMgmt::UploadHierarchyResp::NodeEntry::~NodeEntry(void)
{
	for (NodeEntry *entry : m_sub) {
		delete entry;
	}
}

const RecMgmt::UploadHierarchyResp::NodeEntry *
    RecMgmt::UploadHierarchyResp::NodeEntry::super(void) const
{
	return m_super;
}

const QString &RecMgmt::UploadHierarchyResp::NodeEntry::name(void) const
{
	return m_name;
}

void RecMgmt::UploadHierarchyResp::NodeEntry::setName(const QString &n)
{
	m_name = n;
}

#ifdef Q_COMPILER_RVALUE_REFS
void RecMgmt::UploadHierarchyResp::NodeEntry::setName(QString &&n)
{
	m_name = ::std::move(n);
}
#endif /* Q_COMPILER_RVALUE_REFS */

const QString &RecMgmt::UploadHierarchyResp::NodeEntry::id(void) const
{
	return m_id;
}

void RecMgmt::UploadHierarchyResp::NodeEntry::setId(const QString &i)
{
	m_id = i;
}

#ifdef Q_COMPILER_RVALUE_REFS
void RecMgmt::UploadHierarchyResp::NodeEntry::setId(QString &&i)
{
	m_id = ::std::move(i);
}
#endif /* Q_COMPILER_RVALUE_REFS */

const QSet<QString> &RecMgmt::UploadHierarchyResp::NodeEntry::metadata(void) const
{
	return m_metadata;
}

void RecMgmt::UploadHierarchyResp::NodeEntry::setMetadata(const QSet<QString> &m)
{
	m_metadata = m;
}

#ifdef Q_COMPILER_RVALUE_REFS
void RecMgmt::UploadHierarchyResp::NodeEntry::setMetadata(QSet<QString> &&m)
{
	m_metadata = ::std::move(m);
}
#endif /* Q_COMPILER_RVALUE_REFS */

const QSet<QString> &RecMgmt::UploadHierarchyResp::NodeEntry::caseId(void) const
{
	return m_caseId;
}

void RecMgmt::UploadHierarchyResp::NodeEntry::setCaseId(const QSet<QString> &c)
{
	m_caseId = c;
}

#ifdef Q_COMPILER_RVALUE_REFS
void RecMgmt::UploadHierarchyResp::NodeEntry::setCaseId(QSet<QString> &&c)
{
	m_caseId = ::std::move(c);
}
#endif /* Q_COMPILER_RVALUE_REFS */

const QSet<QString> &RecMgmt::UploadHierarchyResp::NodeEntry::courtCaseId(void) const
{
	return m_courtCaseId;
}

void RecMgmt::UploadHierarchyResp::NodeEntry::setCourtCaseId(const QSet<QString> &cc)
{
	m_courtCaseId = cc;
}

#ifdef Q_COMPILER_RVALUE_REFS
void RecMgmt::UploadHierarchyResp::NodeEntry::setCourtCaseId(QSet<QString> &&cc)
{
	m_courtCaseId = ::std::move(cc);
}
#endif /* Q_COMPILER_RVALUE_REFS */

const QList<RecMgmt::UploadHierarchyResp::NodeEntry *> &
    RecMgmt::UploadHierarchyResp::NodeEntry::sub(void) const
{
	return m_sub;
}

bool RecMgmt::UploadHierarchyResp::NodeEntry::appendSub(NodeEntry *sub)
{
	if (Q_UNLIKELY(sub == Q_NULLPTR)) {
		return false;
	}

	sub->m_super = this;
	m_sub.append(sub);
	return true;
}

RecMgmt::UploadHierarchyResp::NodeEntry *
    RecMgmt::UploadHierarchyResp::NodeEntry::copyRecursive(
        const NodeEntry *root)
{
	if (Q_UNLIKELY(root == Q_NULLPTR)) {
		Q_ASSERT(0);
		return Q_NULLPTR;
	}

	NodeEntry *newRoot = new (::std::nothrow) NodeEntry();
	if (Q_UNLIKELY(newRoot == Q_NULLPTR)) {
		return Q_NULLPTR;
	}

	newRoot->m_name = root->m_name;
	newRoot->m_id = root->m_id;
	newRoot->m_metadata = root->m_metadata;
	newRoot->m_caseId = root->m_caseId;
	newRoot->m_courtCaseId = root->m_courtCaseId;

	for (const NodeEntry *sub : root->m_sub) {
		if (Q_UNLIKELY(sub == Q_NULLPTR)) {
			Q_ASSERT(0);
			goto fail;
		}
		NodeEntry *newSub = copyRecursive(sub);
		if (Q_UNLIKELY(newSub == Q_NULLPTR)) {
			goto fail;
		}

		newRoot->appendSub(newSub);
	}

	return newRoot;

fail:
	delete newRoot;
	return Q_NULLPTR;
}

RecMgmt::UploadHierarchyResp::NodeEntry *
    RecMgmt::UploadHierarchyResp::NodeEntry::fromJsonValRecursive(
        const QJsonValue &jsonVal, bool &ok, bool acceptNullName)
{
	NodeEntry *newEntry = Q_NULLPTR;

	if (Q_UNLIKELY(!jsonVal.isObject())) {
		logErrorNL("%s",
		    "Attempting to treat a non-object value as an object.");
		goto fail;
	}

	{
		const QJsonObject jsonObj = jsonVal.toObject();

		newEntry = new (::std::nothrow) NodeEntry();
		if (Q_UNLIKELY(newEntry == Q_NULLPTR)) {
			goto fail;
		}

		if (Q_UNLIKELY(!Json::Helper::readString(jsonObj, keyName, newEntry->m_name,
		        acceptNullName ? Json::Helper::ACCEPT_NULL : Json::Helper::ACCEPT_VALID))) {
			goto fail;
		}
		if (Q_UNLIKELY(!Json::Helper::readString(jsonObj, keyId, newEntry->m_id,
		        Json::Helper::ACCEPT_NULL))) {
			goto fail;
		}
		{
			QStringList stringList;
			if (Q_UNLIKELY(!Json::Helper::readStringList(jsonObj, keyMetadata,
			        stringList, Json::Helper::ACCEPT_VALID))) {
				goto fail;
			}
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
			newEntry->m_metadata = QSet<QString>(stringList.begin(), stringList.end());
#else /* < Qt-5.14.0 */
			newEntry->m_metadata = stringList.toSet();
#endif /* >= Qt-5.14.0 */
		}
		{
			QStringList stringList;
			if (Q_UNLIKELY(!Json::Helper::readStringList(jsonObj, keyCaseId,
			        stringList, Json::Helper::ACCEPT_MISSING))) {
				goto fail;
			}
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
			newEntry->m_caseId = QSet<QString>(stringList.begin(), stringList.end());
#else /* < Qt-5.14.0 */
			newEntry->m_caseId = stringList.toSet();
#endif /* >= Qt-5.14.0 */
		}
		{
			QStringList stringList;
			if (Q_UNLIKELY(!Json::Helper::readStringList(jsonObj, keyCourtCaseId,
			        stringList, Json::Helper::ACCEPT_MISSING))) {
				goto fail;
			}
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
			newEntry->m_courtCaseId = QSet<QString>(stringList.begin(), stringList.end());
#else /* < Qt-5.14.0 */
			newEntry->m_courtCaseId = stringList.toSet();
#endif /* >= Qt-5.14.0 */
		}

		{
			QJsonArray jsonArr;
			if (Q_UNLIKELY(!Json::Helper::readArray(jsonObj, keySub, jsonArr,
			        Json::Helper::ACCEPT_VALID))) {
				goto fail;
			}

			for (const QJsonValue &jsonArrVal : jsonArr) {
				NodeEntry *subNewEntry =
				    fromJsonValRecursive(jsonArrVal, ok, false);
				if (Q_UNLIKELY(!ok)) {
					goto fail;
				}
				Q_ASSERT(subNewEntry != Q_NULLPTR);

				newEntry->appendSub(subNewEntry);
			}
		}

		ok = true;
		return newEntry;
	}

fail:
	ok = false;
	if (newEntry != Q_NULLPTR) {
		delete newEntry;
	}
	return Q_NULLPTR;
}

bool RecMgmt::UploadHierarchyResp::NodeEntry::toJsonValRecursive(
    QJsonValue &jsonVal) const
{
	QJsonObject jsonObj;

	/* Intentionally using isEmpty() instead of isNull(). */
	jsonObj.insert(keyName, !m_name.isEmpty() ? m_name : QJsonValue());
	jsonObj.insert(keyId, !m_id.isEmpty() ? m_id : QJsonValue());
	jsonObj.insert(keyMetadata, QJsonArray::fromStringList(
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
	    QStringList(m_metadata.begin(), m_metadata.end())
#else /* < Qt-5.14.0 */
	    m_metadata.toList()
#endif /* >= Qt-5.14.0 */
	    ));
	if (!m_caseId.isEmpty()) {
		jsonObj.insert(keyCaseId, QJsonArray::fromStringList(
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
		    QStringList(m_caseId.begin(), m_caseId.end())
#else /* < Qt-5.14.0 */
		    m_caseId.toList()
#endif /* >= Qt-5.14.0 */
		    ));
	}
	if (!m_courtCaseId.isEmpty()) {
		jsonObj.insert(keyCourtCaseId, QJsonArray::fromStringList(
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
		    QStringList(m_courtCaseId.begin(), m_courtCaseId.end())
#else /* < Qt-5.14.0 */
		    m_courtCaseId.toList()
#endif /* >= Qt-5.14.0 */
		    ));
	}

	{
		QJsonArray arr;

		for (const NodeEntry *entry : m_sub) {
			if (Q_UNLIKELY(entry == Q_NULLPTR)) {
				Q_ASSERT(0);
				goto fail;
			}
			/* Sub-node must have a name. */
			if (Q_UNLIKELY(entry->m_name.isEmpty())) {
				goto fail;
			}
			QJsonValue subJsonVal;
			if (Q_UNLIKELY(!entry->toJsonValRecursive(subJsonVal))) {
				goto fail;
			}
			arr.append(subJsonVal);
		}

		jsonObj.insert(keySub, arr);
	}

	jsonVal = jsonObj;
	return true;

fail:
	jsonVal = QJsonObject();
	return false;
}

RecMgmt::UploadHierarchyResp::UploadHierarchyResp(void)
    : Json::Object(),
    m_root(Q_NULLPTR)
{
}

RecMgmt::UploadHierarchyResp::UploadHierarchyResp(
    const UploadHierarchyResp &other)
    : Json::Object(),
    m_root(Q_NULLPTR)
{
	if (other.m_root != Q_NULLPTR) {
		m_root = NodeEntry::copyRecursive(other.m_root);
	}
}

#ifdef Q_COMPILER_RVALUE_REFS
RecMgmt::UploadHierarchyResp::UploadHierarchyResp(
    UploadHierarchyResp &&other) Q_DECL_NOEXCEPT
    : Json::Object(),
    m_root(Q_NULLPTR)
{
	::std::swap(m_root, other.m_root);
}
#endif /* Q_COMPILER_RVALUE_REFS */

RecMgmt::UploadHierarchyResp::~UploadHierarchyResp(void)
{
	delete m_root;
}

RecMgmt::UploadHierarchyResp &RecMgmt::UploadHierarchyResp::operator=(
    const UploadHierarchyResp &other) Q_DECL_NOTHROW
{
	NodeEntry *tmpRoot = Q_NULLPTR;

	if (other.m_root != Q_NULLPTR) {
		tmpRoot = NodeEntry::copyRecursive(other.m_root);
		if (Q_UNLIKELY(tmpRoot == Q_NULLPTR)) {
			/* Copying failed. */
			Q_ASSERT(0);
		}
	}

	delete m_root;
	m_root = tmpRoot;

	return *this;
}

#ifdef Q_COMPILER_RVALUE_REFS
RecMgmt::UploadHierarchyResp &RecMgmt::UploadHierarchyResp::operator=(
    UploadHierarchyResp &&other) Q_DECL_NOTHROW
{
	::std::swap(m_root, other.m_root);
	return *this;
}
#endif /* Q_COMPILER_RVALUE_REFS */

const RecMgmt::UploadHierarchyResp::NodeEntry *
    RecMgmt::UploadHierarchyResp::root(void) const
{
	return m_root;
}

bool RecMgmt::UploadHierarchyResp::isValid(void) const
{
	return m_root != Q_NULLPTR;
}

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

	return fromJsonVal(jsonObj, ok);
}

RecMgmt::UploadHierarchyResp RecMgmt::UploadHierarchyResp::fromJsonVal(
    const QJsonValue &jsonVal, bool *ok)
{
	UploadHierarchyResp uhr;

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

	{
		const QJsonObject jsonObj = jsonVal.toObject();
		bool iOk = false;
		uhr.m_root = NodeEntry::fromJsonValRecursive(jsonObj, iOk, true);
		if (Q_UNLIKELY(!iOk)) {
			goto fail;
		}

		Q_ASSERT(uhr.m_root != Q_NULLPTR);
		if (ok != Q_NULLPTR) {
			*ok = true;
		}
		return uhr;
	}

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

bool RecMgmt::UploadHierarchyResp::toJsonVal(QJsonValue &jsonVal) const
{
	QJsonObject jsonObj;

	if (m_root == Q_NULLPTR) {
		jsonVal = QJsonObject();
		return true;
	}

	return m_root->toJsonValRecursive(jsonVal);
}
