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

#include "src/datovka_shared/json/helper.h"
#include "src/json/tag_assignment.h"
#include "src/json/tag_assignment_hash.h"

Json::TagIdToAssignmentCountHash Json::TagIdToAssignmentCountHash::fromJson(
    const QByteArray &json, bool *ok)
{
	QJsonArray jsonArr;
	if (!Helper::readRootArray(json, jsonArr)) {
		if (ok != Q_NULLPTR) {
			*ok = false;
		}
		return TagIdToAssignmentCountHash();
	}

	return fromJsonVal(jsonArr, ok);
}

Json::TagIdToAssignmentCountHash Json::TagIdToAssignmentCountHash::fromJsonVal(
    const QJsonValue &jsonVal, bool *ok)
{
	TagIdToAssignmentCountHash h;

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

	{
		const QJsonArray jsonArr = jsonVal.toArray();
		for (const QJsonValue &v : jsonArr) {
			bool iOk = false;
			TagAssignmentCount tac = TagAssignmentCount::fromJsonVal(v, &iOk);
			if (Q_UNLIKELY(!iOk)) {
				goto fail;
			}
			h.insert(tac.tagId(), tac.assignmentCount());
		}
	}

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

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

bool Json::TagIdToAssignmentCountHash::toJsonVal(QJsonValue &jsonVal) const
{
	QJsonArray arr;

	for (const qint64 &key : keys()) {
		QJsonValue v;
		if (Q_UNLIKELY(!TagAssignmentCount(key, operator[](key)).toJsonVal(v))) {
			goto fail;
		}
		arr.append(v);
	}

	jsonVal = arr;
	return true;

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

Json::TagIdToMsgIdListHash Json::TagIdToMsgIdListHash::fromJson(
    const QByteArray &json, bool *ok)
{
	QJsonArray jsonArr;
	if (!Helper::readRootArray(json, jsonArr)) {
		if (ok != Q_NULLPTR) {
			*ok = false;
		}
		return TagIdToMsgIdListHash();
	}

	return fromJsonVal(jsonArr, ok);
}

Json::TagIdToMsgIdListHash Json::TagIdToMsgIdListHash::fromJsonVal(
    const QJsonValue &jsonVal, bool *ok)
{
	TagIdToMsgIdListHash h;

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

	{
		const QJsonArray jsonArr = jsonVal.toArray();
		for (const QJsonValue &v : jsonArr) {
			bool iOk = false;
			TagIdToMsgIdList tmil = TagIdToMsgIdList::fromJsonVal(v, &iOk);
			if (Q_UNLIKELY(!iOk)) {
				goto fail;
			}
			h.insert(tmil.tagId(), tmil.tagMsgIds());
		}
	}

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

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

bool Json::TagIdToMsgIdListHash::toJsonVal(QJsonValue &jsonVal) const
{
	QJsonArray arr;

	for (const qint64 &key : keys()) {
		QJsonValue v;
		if (Q_UNLIKELY(!TagIdToMsgIdList(key, operator[](key)).toJsonVal(v))) {
			goto fail;
		}
		arr.append(v);
	}

	jsonVal = arr;
	return true;

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

Json::TagAssignmentHash Json::TagAssignmentHash::fromJson(
    const QByteArray &json, bool *ok)
{
	QJsonArray jsonArr;
	if (!Helper::readRootArray(json, jsonArr)) {
		if (ok != Q_NULLPTR) {
			*ok = false;
		}
		return TagAssignmentHash();
	}

	return fromJsonVal(jsonArr, ok);
}

Json::TagAssignmentHash Json::TagAssignmentHash::fromJsonVal(
    const QJsonValue &jsonVal, bool *ok)
{
	TagAssignmentHash h;

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

	{
		const QJsonArray jsonArr = jsonVal.toArray();
		for (const QJsonValue &v : jsonArr) {
			bool iOk = false;
			TagAssignment ta = TagAssignment::fromJsonVal(v, &iOk);
			if (Q_UNLIKELY(!iOk)) {
				goto fail;
			}
			h.insert(TagMsgId(ta.testEnv(), ta.dmId()), ta.entries());
		}
	}

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

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

bool Json::TagAssignmentHash::toJsonVal(QJsonValue &jsonVal) const
{
	QJsonArray arr;

	for (const TagMsgId &key : keys()) {
		QJsonValue v;
		if (Q_UNLIKELY(!TagAssignment(key.testEnv(), key.dmId(), operator[](key)).toJsonVal(v))) {
			goto fail;
		}
		arr.append(v);
	}

	jsonVal = arr;
	return true;

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

Json::TagAssignmentCommand Json::TagAssignmentCommand::fromJson(
    const QByteArray &json, bool *ok)
{
	QJsonArray jsonArr;
	if (!Helper::readRootArray(json, jsonArr)) {
		if (ok != Q_NULLPTR) {
			*ok = false;
		}
		return TagAssignmentCommand();
	}

	return fromJsonVal(jsonArr, ok);
}

static const QString keyTestEnv("testEnv");
static const QString keyMessageId("messageId");
static const QString keyTagEntries("tagIds");

static
bool readJsonVal(const QJsonValue &jsonVal, Json::TagMsgId &msgId,
    Json::Int64StringList &tagIds)
{
	if (Q_UNLIKELY(!jsonVal.isObject())) {
		goto fail;
	}

	{
		Json::TagMsgId readMsgId;
		Json::Int64StringList readTagIds;

		const QJsonObject jsonObj = jsonVal.toObject();
		{
			enum Isds::Type::NilBool valNilBool = Isds::Type::BOOL_NULL;
			if (Q_UNLIKELY(!Json::Helper::readNilBool(jsonObj,
			        keyTestEnv, valNilBool, Json::Helper::ACCEPT_NULL))) {
				goto fail;
			}
			readMsgId.setTestEnv(valNilBool);
		}
		{
			qint64 valInt = 0;
			if (Q_UNLIKELY(!Json::Helper::readQint64String(jsonObj,
			        keyMessageId, valInt, Json::Helper::ACCEPT_VALID))) {
				goto fail;
			}
			readMsgId.setDmId(valInt);
		}
		{
			QJsonArray arr;
			bool iOk = false;
			if (Q_UNLIKELY(!Json::Helper::readArray(jsonObj,
			        keyTagEntries, arr, Json::Helper::ACCEPT_VALID))) {
				goto fail;
			}
			iOk = false;
			readTagIds = Json::Int64StringList::fromJsonVal(arr, &iOk);
			if (Q_UNLIKELY(!iOk)) {
				goto fail;
			}
		}
	}

fail:
	msgId = Json::TagMsgId();
	tagIds = Json::Int64StringList();
	return false;
}

Json::TagAssignmentCommand Json::TagAssignmentCommand::fromJsonVal(
    const QJsonValue &jsonVal, bool *ok)
{
	TagAssignmentCommand c;

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

	{
		const QJsonArray jsonArr = jsonVal.toArray();
		for (const QJsonValue &v : jsonArr) {
			TagMsgId msgId;
			Int64StringList tagIds;
			bool iOk = readJsonVal(v, msgId, tagIds);
			if (Q_UNLIKELY(!iOk)) {
				goto fail;
			}
			c.insert(msgId, tagIds);
		}
	}

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

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

static
QJsonValue writeJsonVal(const Json::TagMsgId &msgId,
    const Json::Int64StringList &tagIds, bool *ok = nullptr)
{
	QJsonObject jsonObj;

	jsonObj.insert(keyTestEnv, (msgId.testEnv() != Isds::Type::BOOL_NULL) ?
	    QJsonValue(msgId.testEnv() == Isds::Type::BOOL_TRUE) : QJsonValue());
	jsonObj.insert(keyMessageId, (msgId.dmId() >= 0) ? QString::number(msgId.dmId()) : QJsonValue());
	{
		QJsonValue jsonVal;
		if (Q_UNLIKELY(!tagIds.toJsonVal(jsonVal))) {
			goto fail;
		}
		jsonObj.insert(keyTagEntries, jsonVal);
	}

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

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

bool Json::TagAssignmentCommand::toJsonVal(QJsonValue &jsonVal) const
{
	QJsonArray arr;

	for (const TagMsgId &key : keys()) {
		bool iOk = false;
		arr.append(writeJsonVal(key, operator[](key), &iOk));
		if (Q_UNLIKELY(!iOk)) {
			goto fail;
		}
	}

	jsonVal = arr;
	return true;

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