#include "core/date.h"
#include "core/json.h"

#include <QDate>
#include <QDebug>

#include <algorithm>

/* An implementation of AniList's "fuzzy date" */

template<typename T>
bool CLAMP(T x, T low, T high) {
	return std::max(low, std::min(high, x));
}

Date::Date() {
}

Date::Date(unsigned int y) {
	SetYear(y);
}

Date::Date(unsigned int y, unsigned int m, unsigned int d) {
	SetYear(y);
	SetMonth(m);
	SetDay(d);
}

Date::Date(const QDate& date) {
	SetYear(date.year());
	SetMonth(date.month());
	SetDay(date.day());
}

Date::Date(const nlohmann::json& json) {
	/* NOTE: this constructor is made for use with
	   AniList FuzzyDate-style JSON. In the future, some other
	   methods may be parsed and whatnot if necessary. */
	if (json.contains("year") && json.at("year").is_number())
		SetYear(json.at("year").get<unsigned int>());
	if (json.contains("month") && json.at("month").is_number())
		SetMonth(json.at("month").get<unsigned int>());
	if (json.contains("day") && json.at("day").is_number())
		SetDay(json.at("day").get<unsigned int>());
}

void Date::VoidYear() {
	year.Void();
}

void Date::VoidMonth() {
	month.Void();
}

void Date::VoidDay() {
	day.Void();
}

void Date::SetYear(unsigned int y) {
	year.Set(y);
}

void Date::SetMonth(unsigned int m) {
	month.Set(CLAMP(m, 1U, 12U));
}

void Date::SetDay(unsigned int d) {
	day.Set(CLAMP(d, 1U, 31U));
}

unsigned int Date::GetYear() const {
	return year.Get();
}

unsigned int Date::GetMonth() const {
	return month.Get();
}

unsigned int Date::GetDay() const {
	return day.Get();
}

bool Date::IsValid() const {
	return year.Enabled() && month.Enabled() && day.Enabled();
}

bool Date::operator<(const Date& other) const {
	const unsigned int y = GetYear(), m = GetMonth(), d = GetDay();
	const unsigned int o_y = other.GetYear(), o_m = other.GetMonth(), o_d = other.GetDay();

	return (y < o_y && m < o_m && d < o_d);
}

bool Date::operator>(const Date& other) const {
	return other < (*this);
}

bool Date::operator<=(const Date& other) const {
	return !((*this) > other);
}

bool Date::operator>=(const Date& other) const {
	return !((*this) < other);
}

QDate Date::GetAsQDate() const {
	/* QDate doesn't support "missing" values (for good reason),
	 * so we do our best and return what we can.
	*/

	return QDate(year.Enabled() ? year.Get() : 2000, month.Enabled() ? month.Get() : 1, day.Enabled() ? day.Get() : 1);
}

nlohmann::json Date::GetAsAniListJson() const {
	nlohmann::json result = {};

	if (year.Enabled())
		result["year"] = year.Get();
	else
		result["year"] = nullptr;

	if (month.Enabled())
		result["month"] = month.Get();
	else
		result["month"] = nullptr;

	if (day.Enabled())
		result["day"] = day.Get();
	else
		result["day"] = nullptr;

	return result;
}
