#include "core/date.h"
#include "core/json.h"
#include <QDate>
#include <QDebug>
#include <algorithm>
#include <cstdint>
#include <tuple>

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

#define CLAMP(x, low, high) (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());
}

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

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

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

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

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

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

unsigned int Date::GetYear() const {
	unsigned int* ptr = year.get();
	if (ptr != nullptr)
		return *year;
	return -1;
}

unsigned int Date::GetMonth() const {
	unsigned int* ptr = month.get();
	if (ptr != nullptr)
		return *month;
	return -1;
}

unsigned int Date::GetDay() const {
	unsigned int* ptr = day.get();
	if (ptr != nullptr)
		return *day;
	return -1;
}

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

bool Date::operator<(const Date& other) const {
	unsigned int y = GetYear(), m = GetMonth(), d = GetDay();
	unsigned int o_y = other.GetYear(), o_m = other.GetMonth(), o_d = other.GetDay();
	return std::tie(y, m, d) < std::tie(o_y, o_m, 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 {
	/* QDates don't support "missing" values, for good reason. */
	if (IsValid())
		return QDate(*year, *month, *day);
	else
		return QDate();
}

nlohmann::json Date::GetAsAniListJson() const {
	nlohmann::json result = {};
	if (year.get())
		result["year"] = *year;
	else
		result["year"] = nullptr;
	if (month.get())
		result["month"] = *month;
	else
		result["month"] = nullptr;
	if (day.get())
		result["day"] = *day;
	else
		result["day"] = nullptr;
	return result;
}
