/*
 * 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 <cassert>
#include <string>
#include <vector>

#include <iostream>

namespace Anime {
/* FIXME need to parse relations data as well.
 *
 * For example
 *   A Certain Magical Index - 2x13.mkv
 * How do we know which anime is the second season of
 * "A Certain Magical Index"? We can't possibly know
 * without relations data!
 *
 * A way to solve this would be to store prequel/sequel.
 * This is sorta-kinda anime speak for seasons. We can
 * then find when the prequels stop. HOWEVER, this isn't
 * exactly fool proof, because there are shows with a
 * second "season" that is actually a prequel, and may
 * show up as one in relations data. So it's not as
 * straightforward as one may think. */

/* 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();
}

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

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::SetUserId(const std::string &id)
{
	assert(list_info_.has_value());
	list_info_->id = id;
}

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(const Date &started)
{
	assert(list_info_.has_value());
	list_info_->started = started;
}

void Anime::SetUserDateCompleted(const Date &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(const std::string &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::GetStartedDate() const
{
	return info_.started;
}

Date Anime::GetCompletedDate() const
{
	return info_.completed;
}

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;
}

Season Anime::GetSeason() const
{
	return Season(info_.started);
}

double 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(const std::vector<std::string> &synonyms)
{
	info_.synonyms = synonyms;
}

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

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

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

void Anime::SetStartedDate(const Date &date)
{
	info_.started = date;
}

void Anime::SetCompletedDate(const Date &date)
{
	info_.completed = date;
}

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

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

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

void Anime::SetAudienceScore(double 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
