/*
 * anime.cpp: defining of custom anime-related
 * datatypes & variables
*/
#include <chrono>
#include <string>
#include <vector>
#include <cmath>
#include <algorithm>
#include "anilist.h"
#include "anime.h"
#include "date.h"
#include "session.h"

std::map<enum AnimeWatchingStatus, std::string> AnimeWatchingToStringMap = {
	{CURRENT,   "Watching"},
	{PLANNING,  "Planning"},
	{COMPLETED, "Completed"},
	{DROPPED,   "Dropped"},
	{PAUSED,    "On hold"},
	{REPEATING, "Rewatching"}
};

std::map<enum AnimeAiringStatus, std::string> AnimeAiringToStringMap = {
	{FINISHED,         "Finished"},
	{RELEASING,        "Airing"},
	{NOT_YET_RELEASED, "Not aired yet"},
	{CANCELLED,        "Cancelled"},
	{HIATUS,           "On hiatus"}
};

std::map<enum AnimeSeason, std::string> AnimeSeasonToStringMap = {
	{UNKNOWN, "Unknown"},
	{WINTER,  "Winter"},
	{SPRING,  "Spring"},
	{SUMMER,  "Summer"},
	{FALL,    "Fall"}
};

std::map<enum AnimeFormat, std::string> AnimeFormatToStringMap = {
	{TV,       "TV"},
	{TV_SHORT, "TV short"},
	{MOVIE,    "Movie"},
	{SPECIAL,  "Special"},
	{OVA,      "OVA"},
	{ONA,      "ONA"},
	{MUSIC,    "Music video"},
	/* these should NEVER be in the list. naybe we should
	   remove them? */
	{MANGA,    "Manga"},
	{NOVEL,    "Novel"},
	{ONE_SHOT, "One-shot"}
};

Anime::Anime() {}
Anime::Anime(const Anime& a) {
	status = a.status;
	progress = a.progress;
	score = a.score;
	started = a.started;
	completed = a.completed;
	updated = a.updated;
	notes = a.notes;
	id = a.id;
	title = a.title;
	episodes = a.episodes;
	airing = a.airing;
	air_date = a.air_date;
	genres = a.genres;
	producers = a.producers;
	type = a.type;
	season = a.season;
	audience_score = a.audience_score;
	synopsis = a.synopsis;
	duration = a.duration;
}

std::string Anime::GetUserPreferredTitle() {
	switch (session.config.anime_list.language) {
		case NATIVE:
			return (title.native.empty()) ? title.romaji : title.native;
		case ENGLISH:
			return (title.english.empty()) ? title.romaji : title.english;
		default:
			return title.romaji;
	}
}

std::vector<std::string> Anime::GetTitleSynonyms() {
	std::vector<std::string> result;
#define IN_VECTOR(v, k) \
	(std::count(v.begin(), v.end(), k))
#define ADD_TO_SYNONYMS(v, k) \
	if (!k.empty() && !IN_VECTOR(v, k) && k != GetUserPreferredTitle()) v.push_back(k)
	ADD_TO_SYNONYMS(result, title.english);
	ADD_TO_SYNONYMS(result, title.romaji);
	ADD_TO_SYNONYMS(result, title.native);
	for (auto& synonym : synonyms) {
		ADD_TO_SYNONYMS(result, synonym);
	}
#undef ADD_TO_SYNONYMS
#undef IN_VECTOR
	return result;
}

void AnimeList::Add(Anime& anime) {
	if (anime_id_to_anime.contains(anime.id))
		return;
	anime_list.push_back(anime);
	anime_id_to_anime.emplace(anime.id, &anime);
}

void AnimeList::Insert(size_t pos, Anime& anime) {
	if (anime_id_to_anime.contains(anime.id))
		return;
	anime_list.insert(anime_list.begin()+pos, anime);
	anime_id_to_anime.emplace(anime.id, &anime);
}

void AnimeList::Delete(size_t index) {
	anime_list.erase(anime_list.begin()+index);
}

void AnimeList::Clear() {
	anime_list.clear();
}

size_t AnimeList::Size() const {
	return anime_list.size();
}

std::vector<Anime>::iterator AnimeList::begin() noexcept {
	return anime_list.begin();
}

std::vector<Anime>::iterator AnimeList::end() noexcept {
	return anime_list.end();
}

std::vector<Anime>::const_iterator AnimeList::cbegin() noexcept {
	return anime_list.cbegin();
}

std::vector<Anime>::const_iterator AnimeList::cend() noexcept {
	return anime_list.cend();
}

AnimeList::AnimeList() {}
AnimeList::AnimeList(const AnimeList& l) {
	for (unsigned long long i = 0; i < l.Size(); i++) {
		anime_list.push_back(Anime(l[i]));
	}
	name = l.name;
}

AnimeList& AnimeList::operator=(const AnimeList& l) {
	if (this != &l) {
		for (unsigned long long i = 0; i < l.Size(); i++) {
			this->anime_list.push_back(Anime(l[i]));
		}
		this->name = l.name;
	}
	return *this;
}

AnimeList::~AnimeList() {
	anime_list.clear();
	anime_list.shrink_to_fit();
}

Anime* AnimeList::AnimeById(int id) {
	return anime_id_to_anime.contains(id) ? anime_id_to_anime[id] : nullptr;
}

bool AnimeList::AnimeInList(int id) {
	return anime_id_to_anime.contains(id);
}

int AnimeList::GetAnimeIndex(Anime& anime) const {
	for (unsigned long long i = 0; i < Size(); i++) {
		if (&anime_list.at(i) == &anime) { // lazy
			return i;
		}
	}
	return -1;
}

Anime& AnimeList::operator[](std::size_t index) {
	return anime_list.at(index);
}

const Anime& AnimeList::operator[](std::size_t index) const {
	return anime_list.at(index);
}
