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

#include <QDate>
#include <QDebug>

#include <algorithm>
#include <cstdio>

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

Date::Date()
{
}

Date::Date(Date::Year y)
{
	SetYear(y);
}

Date::Date(Date::Year y, Date::Month m, Date::Day d)
{
	SetYear(y);
	SetMonth(m);
	SetDay(d);
}

Date::Date(const std::string &str)
{
	unsigned int y, m, d;

	/* I don't like this that much, but it works... */
	int amt = std::sscanf(str.c_str(), "%4u-%2u-%2u", &y, &m, &d);

	if (amt > 0)
		SetYear(y);

	if (amt > 1)
		SetMonth(static_cast<Date::Month>(m - 1));

	if (amt > 2)
		SetDay(d);
}

Date::Date(const QDate &date)
{
	SetYear(date.year());
	auto m = date.month();
	m = std::clamp(m, static_cast<decltype(m)>(Date::Month::Jan), static_cast<decltype(m)>(Date::Month::Dec));
	SetMonth(static_cast<Date::Month>(m));
	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_pointer) && json.at("/year"_json_pointer).is_number())
		SetYear(json.at("/year"_json_pointer).get<unsigned int>());

	if (json.contains("/month"_json_pointer) && json.at("/month"_json_pointer).is_number()) {
		auto m = json.at("/month"_json_pointer).get<unsigned int>();
		m = std::clamp(m, static_cast<decltype(m)>(Date::Month::Jan), static_cast<decltype(m)>(Date::Month::Dec));
		SetMonth(static_cast<Date::Month>(m));
	}

	if (json.contains("/day"_json_pointer) && json.at("/day"_json_pointer).is_number())
		SetDay(json.at("/day"_json_pointer).get<unsigned char>());
}

Date::Date(Time::Timestamp timestamp)
{
	Date(QDateTime::fromSecsSinceEpoch(timestamp).date());
}

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

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

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

void Date::SetYear(Date::Year y)
{
	year.emplace(y);
}

void Date::SetMonth(Date::Month m)
{
	month.emplace(m);
}

void Date::SetDay(Date::Day d)
{
	day.emplace(std::clamp(d, static_cast<Date::Day>(1U), static_cast<Date::Day>(31U)));
}

std::optional<Date::Year> Date::GetYear() const
{
	return year;
}

std::optional<Date::Month> Date::GetMonth() const
{
	return month;
}

std::optional<Date::Day> Date::GetDay() const
{
	return day;
}

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

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.value_or(2000), static_cast<unsigned int>(month.value_or(Date::Month::Jan)), day.value_or(1));
}

nlohmann::json Date::GetAsAniListJson() const
{
	nlohmann::json json = {
	    {"year",  nullptr},
        {"month", nullptr},
        {"day",   nullptr}
    };

	if (year)
		json["year"] = static_cast<unsigned int>(year.value());

	if (month)
		json["month"] = static_cast<unsigned char>(month.value());

	if (day)
		json["day"] = static_cast<unsigned char>(day.value());

	return json;
}
