#include "core/time.h"
#include "core/strings.h"

#include <cassert>
#include <cmath>
#include <cstdint>
#include <ctime>
#include <string>

#include <QDateTime>

namespace Time {

static Timestamp GetSecondsInMinutes(Timestamp length) {
	return std::llround(static_cast<double>(length) / 60.0);
}

static Timestamp GetSecondsInHours(Timestamp length) {
	return std::llround(static_cast<double>(length) / 3600.0);
}

static Timestamp GetSecondsInDays(Timestamp length) {
	return std::llround(static_cast<double>(length) / 86400.0);
}

std::string GetSecondsAsRelativeString(Timestamp length) {
	std::string result;

	auto get = [](Timestamp val, const std::string& s, const std::string& p) {
		return Strings::ToUtf8String(val) + " " + (val == 1 ? s : p);
	};

	if (length < 60)
		result = get(length, "second", "seconds");
	else if (GetSecondsInMinutes(length) < 60)
		result = get(GetSecondsInMinutes(length), "minute", "minutes");
	else if (GetSecondsInHours(length) < 24)
		result = get(GetSecondsInHours(length), "hour", "hours");
	else if (GetSecondsInDays(length) < 28)
		result = get(GetSecondsInDays(length), "day", "days");
	else if (GetSecondsInDays(length) < 365)
		result = get(GetSecondsInDays(length) / 30, "month", "months");
	else
		result = get(GetSecondsInDays(length) / 365, "year", "years");

	if (length < 0)
		result = "In " + result;
	else
		result += " ago";

	return result;
}

/* "amount" does not have to be in seconds, and can be any unit if the correct ratio to seconds
 * is passed to "unit_in_seconds" (for example, if the input is minutes, pass 60.0) */
std::string GetSecondsAsAbsoluteString(Units unit_cutoff, Timestamp amount, double unit_in_seconds) {
	/* avoid calculating this twice */
	const double years_conv = (31556952.0 / unit_in_seconds);
	const double months_conv = (2629746.0 / unit_in_seconds);
	const double days_conv = (86400.0 / unit_in_seconds);
	const double hours_conv = (3600.0 / unit_in_seconds);
	const double minutes_conv = (60.0 / unit_in_seconds);
	const double seconds_conv = (1.0 / unit_in_seconds);

	const int years = amount / years_conv;
	const int months = std::fmod(amount, years_conv) / months_conv;
	const int days = std::fmod(amount, months_conv) / days_conv;
	const int hours = std::fmod(amount, days_conv) / hours_conv;
	const int minutes = std::fmod(amount, hours_conv) / minutes_conv;
	const int seconds = std::fmod(amount, minutes_conv) / seconds_conv;

	const auto add_time_segment = [](std::ostringstream& str, int64_t amount, const std::string_view& singular,
	                                 const std::string_view& plural, bool always = false) {
		if (amount > 0 || always)
			str << amount << ((amount == 1) ? singular : plural);
	};

	/* for now, this function is very en_US specific */

	std::ostringstream string;
	add_time_segment(string, years, " year ", " years ");
	add_time_segment(string, months, " month ", " months ");
	add_time_segment(string, days, " day ", " days ");
	add_time_segment(string, hours, " hour ", " hours ");

	if (unit_cutoff == Units::Minutes) {
		add_time_segment(string, minutes, " minute", " minutes", true);
		return string.str();
	} else {
		add_time_segment(string, minutes, " minute ", " minutes ");
	}

	add_time_segment(string, seconds, " second", " seconds", true);
	return string.str();
}

Timestamp GetSystemTime() {
	return QDateTime::currentDateTime().toUTC().toSecsSinceEpoch();
}

Timestamp ParseISO8601Time(const std::string& str) {
	return QDateTime::fromString(Strings::ToQString(str), Qt::ISODateWithMs).toUTC().toSecsSinceEpoch();
}

} // namespace Time
