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

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

#define MIN(A, B) \
	({ \
		__typeof__(A) __a = (A); \
		__typeof__(B) __b = (B); \
		__a < __b ? __a : __b; \
	})
#define MAX(A, B) \
	({ \
		__typeof__(A) __a = (A); \
		__typeof__(B) __b = (B); \
		__a < __b ? __b : __a; \
	})

#define CLAMP(x, low, high) \
	({ \
		__typeof__(x) __x = (x); \
		__typeof__(low) __low = (low); \
		__typeof__(high) __high = (high); \
		__x > __high ? __high : (__x < __low ? __low : __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(MAX(0U, 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.insert(result.end(), {"year", *year});
	else
		result.insert(result.end(), {"year", nullptr});
	if (month.get())
		result.insert(result.end(), {"month", *month});
	else
		result.insert(result.end(), {"month", nullptr});
	if (day.get())
		result.insert(result.end(), {"day", *day});
	else
		result.insert(result.end(), {"day", nullptr});
	return result;
}
