/*
 * Copyright (C) 2014-2026 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.
 */

#if defined(__APPLE__) || defined(__clang__)
#  define __USE_C99_MATH
#  define _Bool bool
#else /* !__APPLE__ */
#  include <stdbool.h>
#endif /* __APPLE__ */

#include <cstdlib> /* ::std::free */
#include <libdatovka/isds.h>
#include <QMutexLocker>

#include "src/datovka_shared/isds/error.h"
#include "src/datovka_shared/isds/internal_conversion.h" /* dateFromStructTM */
#include "src/datovka_shared/isds/message_interface.h"
#include "src/datovka_shared/isds/message_interface_vodz.h"
#include "src/isds/error_conversion.h"
#include "src/isds/internal_type_conversion.h"
#include "src/isds/message_conversion.h"
#include "src/isds/services.h"
#include "src/isds/services_internal.h"
#include "src/isds/session.h"

namespace Isds {

	/*!
	 * @brief Encapsulates private services.
	 */
	class ServicePrivate {
		Q_DECLARE_TR_FUNCTIONS(ServicePrivate)

	private:
		/*!
		 * @brief Private constructor.
		 */
		ServicePrivate(void);

	public:
		/*!
		 * @brief Functions reading message identifier and returning message.
		 */
		typedef isds_error (*isdsMessageGetterFunc)(struct isds_ctx *,
		    const char *, struct isds_message **);

		/*!
		 * @brief Serves as indexes for accessing message getter functions.
		 */
		enum MessageGetter {
			MG_SIGNED_DELIVERY_INFO = 0,
			MG_SIGNED_RECEIVED_MESSAGE = 1,
			MG_SIGNED_SENT_MESSAGE = 2
		};

		/*!
		 * @brief Returns a pointer to a message getter function.
		 *
		 * @param[in] mg Service identifier.
		 * @return Function pointer.
		 */
		static
		isdsMessageGetterFunc bigMessageGetterFunc(enum MessageGetter mg)
		{
			static isdsMessageGetterFunc funcArray[] = {
				isds_get_signed_delivery_info,
				isds_SignedBigMessageDownload_mtomxop,
				isds_SignedSentBigMessageDownload_mtomxop
			};

			return funcArray[mg];
		}

		/*!
		 * @brief Calls big message getter services.
		 *
		 * @param[in]     mg Service identifier.
		 * @param[in,out] ctx Communication context.
		 * @param[in]     dmId Message identifier.
		 * @param[out]    message Signed delivery info.
		 * @return Error description.
		 */
		static
		Error bigMessageGetterService(enum MessageGetter mg,
		    Session *ctx, qint64 dmId, Message &message);
	};

}

Isds::Error Isds::ServicePrivate::bigMessageGetterService(enum MessageGetter mg,
    Session *ctx, qint64 dmId, Message &message)
{
	Error err;

	if (Q_UNLIKELY((ctx == Q_NULLPTR) || (dmId < 0))) {
		Q_ASSERT(0);
		err.setCode(Type::ERR_ERROR);
		err.setLongDescr(tr("Insufficient input."));
		return err;
	}

	struct isds_message *msg = NULL;
	bool ok = true;

	{
		QMutexLocker locker(ctx->mutex());

		isds_error ret = bigMessageGetterFunc(mg)(ctx->ctx(),
		    QString::number(dmId).toUtf8().constData(), &msg);
		if (Q_UNLIKELY(ret != IE_SUCCESS)) {
			err.setCode(libisds2Error(ret));
			err.setLongDescr(IsdsInternal::isdsLongMessage(ctx->ctx()));
			goto fail;
		}
	}

	message = (msg != NULL) ? libisds2message(msg, &ok) : Message();

	if (ok) {
		err.setCode(Type::ERR_SUCCESS);
	} else {
		err.setCode(Type::ERR_ERROR);
		err.setLongDescr(tr("Error converting types."));
	}

fail:
	if (msg != NULL) {
		isds_message_free(&msg);
	}

	return err;
}

Isds::Error Isds::Service::uploadAttachment(Session *ctx,
    const Isds::DmFile &dmFile, Isds::DmAtt &dmAtt)
{
	Error err;

	if (Q_UNLIKELY((ctx == Q_NULLPTR) || dmFile.isNull())) {
		Q_ASSERT(0);
		err.setCode(Type::ERR_ERROR);
		err.setLongDescr(tr("Insufficient input."));
		return err;
	}

	struct isds_dmAtt *att = NULL;
	bool iOk = true;

	{
		struct isds_dmFile *iDmFile = NULL;
		iDmFile = dmFile2libisds(dmFile);

		QMutexLocker locker(ctx->mutex());

		isds_error ret = isds_UploadAttachment_mtomxop(ctx->ctx(),
		    iDmFile, &att);
		if (Q_UNLIKELY(ret != IE_SUCCESS)) {
			err.setCode(libisds2Error(ret));
			err.setLongDescr(IsdsInternal::isdsLongMessage(ctx->ctx()));
			goto fail;
		}
	}

	dmAtt = (att != NULL) ? libisds2dmAtt(att, &iOk) : DmAtt();

	if (iOk) {
		err.setCode(Type::ERR_SUCCESS);
	} else {
		err.setCode(Type::ERR_ERROR);
		err.setLongDescr(tr("Error converting types."));
	}

fail:
	if (att != NULL) {
		isds_dmAtt_free(&att);
	}

	return err;
}

Isds::Error Isds::Service::authenticateBigMessage(Session *ctx,
    const QByteArray &raw)
{
	Error err;

	if (Q_UNLIKELY((ctx == Q_NULLPTR) || raw.isEmpty())) {
		Q_ASSERT(0);
		err.setCode(Type::ERR_ERROR);
		err.setLongDescr(tr("Insufficient input."));
		return err;
	}

	{
		QMutexLocker locker(ctx->mutex());

		isds_error ret = isds_AuthenticateBigMessage_mtomxop(ctx->ctx(),
		    raw.constData(), raw.size());
		if (Q_UNLIKELY(ret != IE_SUCCESS)) {
			err.setCode(libisds2Error(ret));
			err.setLongDescr(IsdsInternal::isdsLongMessage(ctx->ctx()));
			return err;
		}
	}

	err.setCode(Type::ERR_SUCCESS);
	return err;
}

Isds::Error Isds::Service::createBigMessage(Session *ctx,
    const Message &message, qint64 &dmId)
{
	Error err;

	if (Q_UNLIKELY((ctx == Q_NULLPTR) || message.isNull())) {
		Q_ASSERT(0);
		err.setCode(Type::ERR_ERROR);
		err.setLongDescr(tr("Insufficient input."));
		return err;
	}

	bool ok = false;
	isds_error ret = IE_SUCCESS;
	struct isds_message *msg = message2libisds(message, &ok);
	if (Q_UNLIKELY(!ok)) {
		err.setCode(Type::ERR_ERROR);
		err.setLongDescr(tr("Error converting types."));
		goto fail;
	}

	{
		QMutexLocker locker(ctx->mutex());

		ret = isds_CreateBigMessage(ctx->ctx(), msg);
		if (Q_UNLIKELY(ret != IE_SUCCESS)) {
			err.setCode(libisds2Error(ret));
			err.setLongDescr(IsdsInternal::isdsLongMessage(ctx->ctx()));
			goto fail;
		}
	}

	if (Q_UNLIKELY(msg->envelope->dmID == NULL)) {
		Q_ASSERT(0);
		err.setCode(Type::ERR_ERROR);
		err.setLongDescr(tr("Missing identifier of sent message."));
		goto fail;
	}

	ok = false;
	dmId = QString(msg->envelope->dmID).toLongLong(&ok);
	if (Q_UNLIKELY((!ok) || (dmId < 0))) {
		Q_ASSERT(0);
		err.setCode(Type::ERR_ERROR);
		err.setLongDescr(tr("Cannot convert sent message identifier."));
		goto fail;
	}

	err.setCode(Type::ERR_SUCCESS);

fail:
	if (msg != NULL) {
		isds_message_free(&msg);
	}

	return err;
}

Isds::Error Isds::Service::signedReceivedBigMessageDownload(Session *ctx,
    qint64 dmId, Message &message)
{
	return ServicePrivate::bigMessageGetterService(
	    ServicePrivate::MG_SIGNED_RECEIVED_MESSAGE, ctx, dmId, message);
}

Isds::Error Isds::Service::signedSentBigMessageDownload(Session *ctx,
    qint64 dmId, Message &message)
{
	return ServicePrivate::bigMessageGetterService(
	    ServicePrivate::MG_SIGNED_SENT_MESSAGE, ctx, dmId, message);
}

Isds::Error Isds::Service::archiveISDSDocument(Session *ctx,
   const QByteArray &rawIn, QByteArray &rawRestamped, QDate &newStampTo)
{
	Error err;

	if (Q_UNLIKELY((ctx == Q_NULLPTR) || rawIn.isEmpty())) {
		Q_ASSERT(0);
		err.setCode(Type::ERR_ERROR);
		err.setLongDescr(tr("Insufficient input."));
		return err;
	}

	void *output_data = NULL;
	size_t output_length = 0;
	struct tm *iTstExp = NULL;

	{
		QMutexLocker locker(ctx->mutex());

		isds_error ret = isds_ArchiveISDSDocument_mtomxop(ctx->ctx(),
		    rawIn.constData(), rawIn.size(),
		    &output_data, &output_length,
		    &iTstExp);
		if (Q_UNLIKELY(ret != IE_SUCCESS)) {
			err.setCode(libisds2Error(ret));
			err.setLongDescr(IsdsInternal::isdsLongMessage(ctx->ctx()));
			goto fail;
		}
	}

	rawRestamped = (output_data != NULL) ? QByteArray((char *)output_data, output_length) : QByteArray();
	newStampTo = (iTstExp != NULL) ? dateFromStructTM(iTstExp) : QDate();

	err.setCode(Type::ERR_SUCCESS);

fail:
	if (output_data != NULL) {
		::std::free(output_data);
	}
	if (iTstExp != NULL) {
		::std::free(iTstExp);
	}

	return err;
}
