/*
 * anime.cpp: defining of custom anime-related
 * datatypes & variables
 */
#include "core/anime.h"
#include "core/anime_season.h"
#include "core/date.h"
#include "core/session.h"
#include "core/strings.h"

#include <algorithm>
#include <string>
#include <vector>
#include <cassert>

namespace Anime {

/* User list data */
bool Anime::IsInUserList() const {
	if (list_info_.has_value())
		return true;
	return false;
}

void Anime::AddToUserList() {
	ListInformation list;
	list_info_.emplace(list);
}

void Anime::RemoveFromUserList() {
	list_info_.reset();
}

ListStatus Anime::GetUserStatus() const {
	assert(list_info_.has_value());
	return list_info_->status;
}

int Anime::GetUserProgress() const {
	assert(list_info_.has_value());
	return list_info_->progress;
}

int Anime::GetUserScore() const {
	assert(list_info_.has_value());
	return list_info_->score;
}

std::string Anime::GetUserPresentableScore() const {
	assert(list_info_.has_value());

	const int score = list_info_->score;
	if (score == 0)
		return "";

	switch (session.config.anime_list.score_format) {
		case ScoreFormat::Point10Decimal:
			return Strings::ToUtf8String(score / 10) + "." + Strings::ToUtf8String(score % 10);
		case ScoreFormat::Point10: return Strings::ToUtf8String(score / 10);
		case ScoreFormat::Point5: {
			std::string stars = "";

			for (int i = 0; i < 100; i += 20)
				stars.append((i <= score) ? "★" : "☆");

			return stars;
		}
		case ScoreFormat::Point3: {
			if (score >= 100)
				return ":)";
			else if (score >= 66)
				return ":|";
			else if (score >= 33)
				return ":(";
			else
				return "";
		}
		default:
		case ScoreFormat::Point100: return Strings::ToUtf8String(score);
	}
}

Date Anime::GetUserDateStarted() const {
	assert(list_info_.has_value());
	return list_info_->started;
}

Date Anime::GetUserDateCompleted() const {
	assert(list_info_.has_value());
	return list_info_->completed;
}

bool Anime::GetUserIsPrivate() const {
	assert(list_info_.has_value());
	return list_info_->is_private;
}

unsigned int Anime::GetUserRewatchedTimes() const {
	assert(list_info_.has_value());
	return list_info_->rewatched_times;
}

bool Anime::GetUserIsRewatching() const {
	assert(list_info_.has_value());
	return list_info_->rewatching;
}

uint64_t Anime::GetUserTimeUpdated() const {
	assert(list_info_.has_value());
	return list_info_->updated;
}

std::string Anime::GetUserNotes() const {
	assert(list_info_.has_value());
	return list_info_->notes;
}

void Anime::SetUserStatus(ListStatus status) {
	assert(list_info_.has_value());
	list_info_->status = status;
}

void Anime::SetUserScore(int score) {
	assert(list_info_.has_value());
	list_info_->score = score;
}

void Anime::SetUserProgress(int progress) {
	assert(list_info_.has_value());
	list_info_->progress = progress;
}

void Anime::SetUserDateStarted(Date const& started) {
	assert(list_info_.has_value());
	list_info_->started = started;
}

void Anime::SetUserDateCompleted(Date const& completed) {
	assert(list_info_.has_value());
	list_info_->completed = completed;
}

void Anime::SetUserIsPrivate(bool is_private) {
	assert(list_info_.has_value());
	list_info_->is_private = is_private;
}

void Anime::SetUserRewatchedTimes(int rewatched) {
	assert(list_info_.has_value());
	list_info_->rewatched_times = rewatched;
}

void Anime::SetUserIsRewatching(bool rewatching) {
	assert(list_info_.has_value());
	list_info_->rewatching = rewatching;
}

void Anime::SetUserTimeUpdated(uint64_t updated) {
	assert(list_info_.has_value());
	list_info_->updated = updated;
}

void Anime::SetUserNotes(std::string const& notes) {
	assert(list_info_.has_value());
	list_info_->notes = notes;
}

/* Series data */
int Anime::GetId() const {
	return info_.id;
}

std::optional<std::string> Anime::GetServiceId(Service service) const {
	if (info_.ids.find(service) == info_.ids.end())
		return std::nullopt;

	return info_.ids.at(service);
}

/* note: this should use std::optional */
std::optional<std::string> Anime::GetTitle(TitleLanguage language) const {
	if (info_.titles.find(language) == info_.titles.end())
		return std::nullopt;

	return info_.titles.at(language);
}

std::vector<std::string> Anime::GetTitleSynonyms() const {
	/* mainly for the GUI */
	std::vector<std::string> result;

	auto add_to_synonyms = [this](std::vector<std::string>& vec, std::string key) {
		if (!key.empty() && !std::count(vec.begin(), vec.end(), key) && key != GetUserPreferredTitle())
			vec.push_back(key);
	};

	for (const auto& lang : TitleLanguages)
		if (info_.titles.find(lang) != info_.titles.end())
			add_to_synonyms(result, info_.titles.at(lang));

	for (auto& synonym : info_.synonyms)
		add_to_synonyms(result, synonym);

	return result;
}

int Anime::GetEpisodes() const {
	return info_.episodes;
}

SeriesStatus Anime::GetAiringStatus() const {
	return info_.status;
}

Date Anime::GetAirDate() const {
	return info_.air_date;
}

std::vector<std::string> Anime::GetGenres() const {
	return info_.genres;
}

std::vector<std::string> Anime::GetProducers() const {
	return info_.producers;
}

SeriesFormat Anime::GetFormat() const {
	return info_.format;
}

SeriesSeason Anime::GetSeason() const {
	std::optional<Date::Month> month = info_.air_date.GetMonth();
	return (month.has_value() ? GetSeasonForMonth(month.value()) : SeriesSeason::Unknown);
}

int Anime::GetAudienceScore() const {
	return info_.audience_score;
}

std::string Anime::GetSynopsis() const {
	return info_.synopsis;
}

int Anime::GetDuration() const {
	return info_.duration;
}

std::string Anime::GetPosterUrl() const {
	/* this isn't really service-specific. this could use
	 * kitsu, MAL, or anilist, and would achieve basically
	 * the same effect. */
	return info_.poster_url;
}

std::optional<std::string> Anime::GetServiceUrl(Service service) const {
	/* todo: add support for other services... */
	std::optional<std::string> id = GetServiceId(service);
	if (!id.has_value())
		return std::nullopt;

	switch (service) {
		case Service::AniList:
			return "https://anilist.co/anime/" + id.value();
		default: return "";
	}
}

void Anime::SetId(int id) {
	info_.id = id;
}

void Anime::SetServiceId(Service service, const std::string& id) {
	info_.ids[service] = id;
}

void Anime::SetTitle(TitleLanguage language, const std::string& title) {
	info_.titles[language] = title;
}

void Anime::SetTitleSynonyms(std::vector<std::string> const& synonyms) {
	info_.synonyms = synonyms;
}

void Anime::AddTitleSynonym(std::string const& synonym) {
	info_.synonyms.push_back(synonym);
}

void Anime::SetEpisodes(int episodes) {
	info_.episodes = episodes;
}

void Anime::SetAiringStatus(SeriesStatus status) {
	info_.status = status;
}

void Anime::SetAirDate(Date const& date) {
	info_.air_date = date;
}

void Anime::SetGenres(std::vector<std::string> const& genres) {
	info_.genres = genres;
}

void Anime::SetProducers(std::vector<std::string> const& producers) {
	info_.producers = producers;
}

void Anime::SetFormat(SeriesFormat format) {
	info_.format = format;
}

void Anime::SetAudienceScore(int audience_score) {
	info_.audience_score = audience_score;
}

void Anime::SetSynopsis(std::string synopsis) {
	info_.synopsis = synopsis;
}

void Anime::SetDuration(int duration) {
	info_.duration = duration;
}

void Anime::SetPosterUrl(std::string url) {
	info_.poster_url = url;
}

std::string Anime::GetUserPreferredTitle() const {
	std::optional<std::string> title = GetTitle(session.config.anime_list.language);
	if (title.has_value())
		return title.value();

	title = GetTitle(TitleLanguage::Romaji);
	if (title.has_value())
		return title.value();

	/* what? */
	return std::string();
}

} // namespace Anime
