package net.filebot.web;

import static java.util.Collections.*;
import static java.util.stream.Collectors.*;
import static net.filebot.WebServices.*;

import java.text.Collator;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.TreeSet;

public final class EpisodeUtilities {

	public static Episode createEpisode(List<Episode> episode) {
		if (episode.isEmpty()) {
			throw new IllegalArgumentException("No such Episode");
		}

		return episode.size() == 1 ? episode.get(0) : new MultiEpisode(episode);
	}

	public static List<Episode> getMultiEpisodeList(Episode e) {
		return e instanceof MultiEpisode ? ((MultiEpisode) e).getEpisodes() : singletonList(e);
	}

	public static boolean isAnime(Episode e) {
		return SortOrder.Absolute.name().equals(e.getSeriesInfo().getOrder()) || AniDB.getIdentifier().equals(e.getSeriesInfo().getDatabase());
	}

	public static boolean isRegular(Episode e) {
		return e.getEpisode() != null && e.getSpecial() == null;
	}

	public static boolean isAbsolute(Episode e) {
		return e.getAbsolute() != null && e.getSeriesInfo().getOrder() != null && SortOrder.Absolute == SortOrder.valueOf(e.getSeriesInfo().getOrder());
	}

	public static List<Episode> fetchEpisodeList(Episode episode) throws Exception {
		return fetchEpisodeList(episode, null, null);
	}

	public static List<Episode> fetchEpisodeList(Episode episode, SortOrder preferredSortOrder, Locale preferredLocale) throws Exception {
		SeriesInfo info = episode.getSeriesInfo();

		SortOrder order = preferredSortOrder;
		if (order == null) {
			order = SortOrder.valueOf(info.getOrder()); // default to original order
		}

		Locale locale = preferredLocale;
		if (locale == null) {
			locale = new Locale(info.getLanguage()); // default to original locale
		}

		return getEpisodeListProvider(info.getDatabase()).getEpisodeList(info.getId(), order, locale);
	}

	public static Episode fetchEpisode(Episode episode, SortOrder preferredSortOrder, Locale preferredLocale) throws Exception {
		List<Episode> episodeList = fetchEpisodeList(episode, preferredSortOrder, preferredLocale);
		List<Episode> includes = getMultiEpisodeList(episode);

		return createEpisode(episodeList.stream().filter(includes::contains).sorted(EPISODE_NUMBERS_COMPARATOR).collect(toList()));
	}

	public static Episode getEpisodeByAbsoluteNumber(Episode e, EpisodeListProvider service, SortOrder order) throws Exception {
		// e.g. match AniDB episode to TheTVDB episode
		Set<String> seriesNames = getLenientSeriesNameSet(e);
		Locale locale = new Locale(e.getSeriesInfo().getLanguage());

		// episode may be a multi-episode
		List<Episode> multiEpisode = getMultiEpisodeList(e);

		for (SearchResult series : service.search(e.getSeriesName(), locale)) {
			// sanity check
			if (!series.getEffectiveNames().stream().anyMatch(seriesNames::contains)) {
				continue;
			}

			// match by absolute number or airdate if possible, default to absolute number otherwise
			List<Episode> airdateEpisodeList = service.getEpisodeList(series, order, locale);
			List<Episode> airdateEpisode = multiEpisode.stream().flatMap(abs -> {
				return airdateEpisodeList.stream().filter(sxe -> abs.getSpecial() == null && sxe.getSpecial() == null).filter(sxe -> {
					return abs.getAbsolute() != null && abs.getAbsolute().equals(sxe.getAbsolute());
				});
			}).collect(toList());

			// sanity check
			if (airdateEpisode.size() != multiEpisode.size()) {
				break;
			}

			return createEpisode(airdateEpisode);
		}

		// return episode object as is by default
		return e;
	}

	private static Set<String> getLenientSeriesNameSet(Episode e) {
		// use maximum strength collator by default
		Collator collator = Collator.getInstance(new Locale(e.getSeriesInfo().getLanguage()));
		collator.setDecomposition(Collator.FULL_DECOMPOSITION);
		collator.setStrength(Collator.PRIMARY);

		Set<String> seriesNames = new TreeSet<String>(collator);
		seriesNames.addAll(e.getSeriesNames());
		return seriesNames;
	}

	public static List<Episode> filterBySeason(Collection<Episode> episodes, int season) {
		return episodes.stream().filter(it -> {
			return it.getSeason() != null && season == it.getSeason();
		}).collect(toList());
	}

	public static int getLastSeason(Collection<Episode> episodes) {
		return episodes.stream().mapToInt(it -> {
			return it.getSeason() == null ? 0 : it.getSeason();
		}).max().orElse(0);
	}

	public static Comparator<Episode> episodeComparator() {
		return EPISODE_NUMBERS_COMPARATOR;
	}

	public static final Comparator<Episode> EPISODE_NUMBERS_COMPARATOR = new Comparator<Episode>() {

		@Override
		public int compare(Episode a, Episode b) {
			int diff = compareValue(a.getSeason(), b.getSeason());
			if (diff != 0)
				return diff;

			diff = compareValue(a.getEpisode(), b.getEpisode());
			if (diff != 0)
				return diff;

			diff = compareValue(a.getSpecial(), b.getSpecial());
			if (diff != 0)
				return diff;

			return compareValue(a.getAbsolute(), b.getAbsolute());
		}

		private <T> int compareValue(Comparable<T> o1, T o2) {
			if (o1 == null && o2 == null)
				return 0;
			if (o1 == null && o2 != null)
				return Integer.MAX_VALUE;
			if (o1 != null && o2 == null)
				return Integer.MIN_VALUE;

			return o1.compareTo(o2);
		}
	};

	private EpisodeUtilities() {
		throw new UnsupportedOperationException();
	}

}
