/*
 * 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 <QMap>
#include <QSet>
#include <utility> /* std::move */

#include "src/datovka_shared/records_management/json/upload_hierarchy.h"
#include "src/records_management/content/automatic_upload_target.h"

/*!
 * @brief PIMPL AutomaticUploadTarget class.
 */
class RecMgmt::AutomaticUploadTargetPrivate {
public:
	AutomaticUploadTargetPrivate(void)
	    : m_caseIdTgts(), m_courtCaseIdTgts()
	{ }

	AutomaticUploadTargetPrivate &operator=(
	    const AutomaticUploadTargetPrivate &other) Q_DECL_NOTHROW
	{
		m_caseIdTgts = other.m_caseIdTgts;
		m_courtCaseIdTgts = other.m_courtCaseIdTgts;

		return *this;
	}

	QMap<QString, QString> m_caseIdTgts; /*< Maps case identifiers to hierarchy identifiers. */
	QMap<QString, QString> m_courtCaseIdTgts; /*!< Maps court case identifiers to hierarchy identifiers. */
};

RecMgmt::AutomaticUploadTarget::AutomaticUploadTarget(void)
    : d_ptr(Q_NULLPTR)
{
}

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

	*d = *other.d_func();
}

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


RecMgmt::AutomaticUploadTarget::~AutomaticUploadTarget(void)
{
}

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

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

	*d = *other.d_func();

	return *this;
}

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

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

bool RecMgmt::AutomaticUploadTarget::haveCaseId(const QString &cId) const
{
	if (Q_UNLIKELY(cId.isEmpty())) {
		Q_ASSERT(0);
		return false;
	}

	Q_D(const AutomaticUploadTarget);
	if (Q_UNLIKELY(d == Q_NULLPTR)) {
		return false;
	}

	return d->m_caseIdTgts.contains(cId);
}

QString RecMgmt::AutomaticUploadTarget::hierarchyForCaseId(const QString &cId) const
{
	if (Q_UNLIKELY(cId.isEmpty())) {
		Q_ASSERT(0);
		return QString();
	}

	Q_D(const AutomaticUploadTarget);
	if (Q_UNLIKELY(d == Q_NULLPTR)) {
		return QString();
	}

	return d->m_caseIdTgts.value(cId, QString());
}

void RecMgmt::AutomaticUploadTarget::setHierarchyForCaseId(const QString &cId,
    const QString &hId)
{
	if (Q_UNLIKELY(cId.isEmpty() || hId.isEmpty())) {
		Q_ASSERT(0);
		return;
	}

	ensureAutomaticUploadTargetPrivate();
	Q_D(AutomaticUploadTarget);
	d->m_caseIdTgts.insert(cId, hId);
}

bool RecMgmt::AutomaticUploadTarget::haveCourtCaseId(const QString &ccId) const
{
	if (Q_UNLIKELY(ccId.isEmpty())) {
		Q_ASSERT(0);
		return false;
	}

	Q_D(const AutomaticUploadTarget);
	if (Q_UNLIKELY(d == Q_NULLPTR)) {
		return false;
	}

	return d->m_courtCaseIdTgts.contains(ccId);
}

QString RecMgmt::AutomaticUploadTarget::hierarchyForCourtCaseId(
    const QString &ccId) const
{
	if (Q_UNLIKELY(ccId.isEmpty())) {
		Q_ASSERT(0);
		return QString();
	}

	Q_D(const AutomaticUploadTarget);
	if (Q_UNLIKELY(d == Q_NULLPTR)) {
		return QString();
	}

	return d->m_courtCaseIdTgts.value(ccId, QString());
}

void RecMgmt::AutomaticUploadTarget::setHierarchyForCourtCaseId(
    const QString &ccId, const QString &hId)
{
	if (Q_UNLIKELY(ccId.isEmpty() || hId.isEmpty())) {
		Q_ASSERT(0);
		return;
	}

	ensureAutomaticUploadTargetPrivate();
	Q_D(AutomaticUploadTarget);
	d->m_courtCaseIdTgts.insert(ccId, hId);
}

/*!
 * @brief Collects all case and court-case hierarchy targets.
 *
 * @param[out] cTgts Map, key is the case id, value is a set of hierarchy targets.
 * @param[out] ccTgts Map, key is the court case id, value is a set of hierarchy targets.
 * @param[in]  node  Upload hierarchy node.
 */
static
void collectCaseTargetsRecursive(QMap<QString, QSet<QString> > &cTgts,
    QMap<QString, QSet<QString> > &ccTgts,
    const RecMgmt::UploadHierarchyResp::NodeEntry *node)
{
	if (Q_UNLIKELY(node == Q_NULLPTR)) {
		return;
	}

	if ((!node->id().isEmpty()) && (node->caseId().size() > 0)) {
		foreach (const QString &caseId, node->caseId()) {
			cTgts[caseId].insert(node->id());
		}
	}
	if ((!node->id().isEmpty()) && (node->courtCaseId().size() > 0)) {
		foreach (const QString &courtCaseId, node->courtCaseId()) {
			ccTgts[courtCaseId].insert(node->id());
		}
	}

	foreach (const RecMgmt::UploadHierarchyResp::NodeEntry *n, node->sub()) {
		collectCaseTargetsRecursive(cTgts, ccTgts, n);
	}
}

RecMgmt::AutomaticUploadTarget RecMgmt::AutomaticUploadTarget::getUnambiguousTargets(
    const UploadHierarchyResp &resp)
{
	AutomaticUploadTarget targets;

	QMap<QString, QSet<QString> > caseTgts;
	QMap<QString, QSet<QString> > courtCaseTgts;

	collectCaseTargetsRecursive(caseTgts, courtCaseTgts, resp.root());

	QMap<QString, QSet<QString> >::const_iterator it;
	for (it = caseTgts.cbegin(); it != caseTgts.cend(); ++it) {
		if (it.value().size() == 1) {
			/* There is only one target tor this case id. */
			const QString key(it.key());
			const QString val(it.value().toList().first());
			if (Q_UNLIKELY(key.isEmpty() || val.isEmpty())) {
				Q_ASSERT(0);
				continue;
			}
			targets.setHierarchyForCaseId(key, val);
		}
	}
	for (it = courtCaseTgts.cbegin(); it != courtCaseTgts.cend(); ++it) {
		if (it.value().size() == 1) {
			/* There is only one target for this court case id. */
			const QString key(it.key());
			const QString val(it.value().toList().first());
			if (Q_UNLIKELY(key.isEmpty() || val.isEmpty())) {
				Q_ASSERT(0);
				continue;
			}
			targets.setHierarchyForCourtCaseId(key, val);
		}
	}

	return targets;
}

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