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

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

#include <libdatovka/isds.h>

#include "src/compat/compat_time.h"
#include "src/datovka_shared/log/log.h"
#include "src/isds/error_conversion.h"
#include "src/isds/initialisation.h"

/*!
 * @brief Returns logging facility name.
 *
 * @param[in] facility Logging facility identifier.
 * @return String with facility name.
 */
static
const char *logFacilityName(isds_log_facility facility)
{
	const char *str = "libdatovka unknown";

	switch (facility) {
	case ILF_NONE:
		str = "libdatovka none";
		break;
	case ILF_HTTP:
		str = "libdatovka http";
		break;
	case ILF_SOAP:
		str = "libdatovka soap";
		break;
	case ILF_ISDS:
		str = "libdatovka isds";
		break;
	case ILF_FILE:
		str = "libdatovka file";
		break;
	case ILF_SEC:
		str = "libdatovka sec";
		break;
	case ILF_XML:
		str = "libdatovka xml";
		break;
	default:
		break;
	}

	return str;
}

/*!
 * @brief Logging callback used in libdatovka.
 *
 * @param[in] facility Logging facility identifier.
 * @param[in] level Urgency level.
 * @param[in] message Null-terminated string.
 * @param[in] length Passed string length without terminating null character.
 * @param[in] data Pointer to data passed on every invocation of the callback.
 */
static
void logCallback(isds_log_facility facility, isds_log_level level,
    const char *message, int length, void *data)
{
	Q_UNUSED(data);
	Q_UNUSED(length);
	const char *logFac = logFacilityName(facility);

	switch (level) {
	case ILL_NONE:
	case ILL_CRIT:
	case ILL_ERR:
		logErrorMl("%s: %s", logFac, message);
		break;
	case ILL_WARNING:
		logWarningMl("%s: %s", logFac, message);
		break;
	case ILL_INFO:
		logInfoMl("%s: %s", logFac, message);
		break;
	case ILL_DEBUG:
		logDebugMlLv3("%s: %s", logFac, message);
		break;
	default:
		logError("Unknown ISDS log level %d.\n", level);
		break;
	}
}

/*!
 * @brief Install Qt-based time conversion functions into libdatovka.
 */
static
void installTimeConversionReplacement(void)
{
	struct isds_ctx *ctx = isds_ctx_create();

	if (ctx != NULL) {
		if (IE_SUCCESS == isds_check_func_timegm(ctx)) {
			logInfoNL("%s",
			    "Default ISDS-library timegm() function works as expected.");
		} else {
			logWarningNL("%s",
			    "Default ISDS-library timegm() has conversion problems. Using replacement function.");
		}
		if (IE_SUCCESS == isds_check_func_gmtime_r(ctx)) {
			logInfoNL("%s",
			    "Default ISDS-library gmtime_r() function works as expected.");
		} else {
			logWarningNL("%s",
			    "Default ISDS-library gmtime_r() has conversion problems. Using replacement function.");
		}
	}

	isds_set_func_timegm(qtBasedTimegm);
	isds_set_func_gmtime_r(qtBasedGmtime_r);

	if (ctx != NULL) {
		if (IE_SUCCESS == isds_check_func_timegm(ctx)) {
			logInfoNL("%s",
			    "Replacement timegm() function works as expected.");
		} else {
			logWarningNL("%s",
			    "Replacement ISDS timegm() has conversion problems.");
		}
		if (IE_SUCCESS == isds_check_func_gmtime_r(ctx)) {
			logInfoNL("%s",
			    "Replacement ISDS gmtime_r() function works as expected.");
		} else {
			logWarningNL("%s",
			    "Replacement ISDS gmtime_r() has conversion problems.");
		}

		isds_ctx_free(&ctx);
	}
}

enum Isds::Type::Error Isds::init(void)
{
	enum Type::Error status = libisds2Error(isds_init());
	if (Q_UNLIKELY(status != Type::ERR_SUCCESS)) {
		Q_ASSERT(0);
		logErrorNL("%s", "Unsuccessful ISDS initialisation.");
		return status;
	}

	isds_set_log_callback(logCallback, NULL);

/* Logging. */
#if !defined(Q_OS_WIN)
	isds_set_logging(ILF_ALL, ILL_ALL);
#else /* defined(Q_OS_WIN) */
	/*
	 * There is an issue related to logging when using libdatovka compiled
	 * with MinGW. See https://gitlab.labs.nic.cz/datovka/datovka/issues/233
	 * for more details.
	 */
	if (GlobInstcs::logPtr->logVerbosity() < 3) {
		/* Don't write transferred data into log. */
		isds_set_logging(ILF_ALL, ILL_INFO);
	} else {
		logInfoNL("%s",
		    "Allowing unrestricted logging from inside libdatovka.");
		isds_set_logging(ILF_ALL, ILL_ALL);
	}
#endif /* !defined(Q_OS_WIN) */

	installTimeConversionReplacement();

	logInfoNL("%s", "Successful ISDS initialisation");
	return status;
}

enum Isds::Type::Error Isds::cleanup(void)
{
	enum Type::Error status = libisds2Error(isds_cleanup());
	if (status == Type::ERR_SUCCESS) {
		logInfoNL("%s", "Successful ISDS clean-up.");
	} else {
		logErrorNL("%s", "Unsuccessful ISDS clean-up.");
	}

	return status;
}
