/*
 * Copyright (C) 2014-2024 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 <cstring> /* ::std::memset */
#include <ctime> /* struct tm */
#include <QDate>
#include <QDateTime>
#include <QTime>
#include <QTimeZone>

#include "src/compat/compat_time.h"

#define TM_YEAR_OFFSET 1900
#define TM_MONTH_OFFSET 1
#define TM_DAY_OFFSET 0

int64_t qtBasedTimegm(struct tm *tm)
{
	if (Q_UNLIKELY(tm == NULL)) {
		Q_ASSERT(0);
		return -1;
	}

	/*
	 * Libdatovka generates struct tm from ISO string format in a crude way.
	 * The string "2021-09-20T01:28:49.354+02:00" is parsed into:
	 * tm {
	 *   .tm_sec = 49,
	 *   .tm_min = 28,
	 *   .tm_hour = -1,
	 *   .tm_mday = 20,
	 *   .tm_mon = 8,
	 *   .tm_year = 121,
	 *   .tm_wday = 1,
	 *   .tm_yday = 262,
	 *   .tm_isdst = 0
	 * }
	 * Negative or too large values don't play well with the QTime
	 * constructor.
	 */

	const bool validHour = (tm->tm_hour >= 0) && (tm->tm_hour < 24);
	const bool validMin = (tm->tm_min >= 0) && (tm->tm_min < 60);
	const bool validSec = (tm->tm_sec >= 0) && (tm->tm_sec < 60);

	QDate date(tm->tm_year + TM_YEAR_OFFSET, tm->tm_mon + TM_MONTH_OFFSET,
	    tm->tm_mday + TM_DAY_OFFSET);
	QTime time(validHour ? tm->tm_hour : 0, validMin ? tm->tm_min : 0,
	    validSec ? tm->tm_sec : 0, 0);
	QDateTime dateTime(date, time,
#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0))
	    QTimeZone::utc()
#else /* < Qt-5.5 */
	    QTimeZone(0) /* QTimeZone() should be equivalent to QTimeZone::utc(). */
#endif /* >= Qt-5.5 */
	    );

	/* Adjust for time offset. */
	{
		int64_t diff = 0;
		if (!validHour) {
			diff += ((int64_t)tm->tm_hour) * 3600;
		}
		if (!validMin) {
			diff += ((int64_t)tm->tm_min) * 60;
		}
		if (!validSec) {
			diff += tm->tm_sec;
		}

		if (diff != 0) {
			dateTime = dateTime.addSecs(diff);
		}
	}

#if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0))
	return dateTime.toSecsSinceEpoch();
#else /* < Qt-5.8 */
	/* Don't use QDateTime::toTime_t() because it returns uint. */
	return dateTime.toMSecsSinceEpoch() / 1000;
#endif /* >= Qt-5.8 */
}

struct tm *qtBasedGmtime_r(const int64_t *timep, struct tm *result)
{
	if (Q_UNLIKELY(timep == NULL)) {
		Q_ASSERT(0);
		return NULL;
	}
	if (Q_UNLIKELY(result == NULL)) {
		Q_ASSERT(0);
		return NULL;
	}

	QDateTime dateTime;
	dateTime.setTimeZone(
#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0))
	    QTimeZone::utc()
#else /* < Qt-5.5 */
	    QTimeZone(0) /* QTimeZone() should be equivalent to QTimeZone::utc(). */
#endif /* >= Qt-5.5 */
	);

#if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0))
	dateTime.setSecsSinceEpoch(*timep);
#else /* < Qt-5.8 */
	/* Don't use QDateTime::setTime_t because it takes uint. */
	dateTime.setMSecsSinceEpoch((*timep) * 1000);
#endif /* >= Qt-5.8 */

	::std::memset(result, 0, sizeof(*result));

	{
		const QDate date = dateTime.date();
		result->tm_year = date.year() - TM_YEAR_OFFSET;
		result->tm_mon = date.month() - TM_MONTH_OFFSET;
		result->tm_mday = date.day() - TM_DAY_OFFSET;
	}

	{
		const QTime time = dateTime.time();
		result->tm_hour = time.hour();
		result->tm_min = time.minute();
		result->tm_sec = time.second();
	}

	return result;
}
