package api

import "strings"

// Which subtitle track a viewer gets, decided here rather than on the television.
//
// The gateway is already the thing that asks Emby what tracks exist — it calls
// PlaybackInfo and enumerates the streams — so it is also the thing that should say which
// of them to turn on. The television used to guess (forced, then default, then whatever was
// first), which meant the answer depended on which set somebody happened to be watching on,
// and turning subtitles off in the bedroom taught the living room nothing.
//
// The preference itself lives in the synced settings document (`subtitlesEnabled`,
// `subtitleLanguage`), so it follows the person rather than the box, and an operator can
// read and push it from the accounts page like any other setting.

// subtitleLanguage is one entry in the vocabulary offered for `subtitleLanguage`.
//
// Value is ISO 639-1 because that is what a media3 track reports after normalisation;
// aliases are the ISO 639-2 forms Emby actually puts in a stream's Language field, which
// for several languages is two different three-letter codes (bibliographic and
// terminological) for the same thing.
type subtitleLanguage struct {
	Value   string
	Label   string
	Aliases []string
}

var subtitleLanguages = []subtitleLanguage{
	{Value: "en", Label: "English", Aliases: []string{"eng"}},
	{Value: "it", Label: "Italian", Aliases: []string{"ita"}},
	{Value: "es", Label: "Spanish", Aliases: []string{"spa", "esp"}},
	{Value: "fr", Label: "French", Aliases: []string{"fre", "fra"}},
	{Value: "de", Label: "German", Aliases: []string{"ger", "deu"}},
	{Value: "pt", Label: "Portuguese", Aliases: []string{"por"}},
	{Value: "nl", Label: "Dutch", Aliases: []string{"dut", "nld"}},
	{Value: "sv", Label: "Swedish", Aliases: []string{"swe"}},
	{Value: "da", Label: "Danish", Aliases: []string{"dan"}},
	{Value: "no", Label: "Norwegian", Aliases: []string{"nor", "nob", "nno"}},
	{Value: "fi", Label: "Finnish", Aliases: []string{"fin"}},
	{Value: "pl", Label: "Polish", Aliases: []string{"pol"}},
	{Value: "cs", Label: "Czech", Aliases: []string{"cze", "ces"}},
	{Value: "hu", Label: "Hungarian", Aliases: []string{"hun"}},
	{Value: "ro", Label: "Romanian", Aliases: []string{"rum", "ron"}},
	{Value: "el", Label: "Greek", Aliases: []string{"gre", "ell"}},
	{Value: "ru", Label: "Russian", Aliases: []string{"rus"}},
	{Value: "uk", Label: "Ukrainian", Aliases: []string{"ukr"}},
	{Value: "tr", Label: "Turkish", Aliases: []string{"tur"}},
	{Value: "ar", Label: "Arabic", Aliases: []string{"ara"}},
	{Value: "he", Label: "Hebrew", Aliases: []string{"heb", "iw"}},
	{Value: "hi", Label: "Hindi", Aliases: []string{"hin"}},
	{Value: "ja", Label: "Japanese", Aliases: []string{"jpn"}},
	{Value: "ko", Label: "Korean", Aliases: []string{"kor"}},
	{Value: "zh", Label: "Chinese", Aliases: []string{"chi", "zho", "cmn", "yue"}},
	{Value: "th", Label: "Thai", Aliases: []string{"tha"}},
	{Value: "vi", Label: "Vietnamese", Aliases: []string{"vie"}},
}

// subtitleLanguageAuto means "no preference": keep the old flag-driven behaviour, which is
// what somebody who has never opened the subtitle menu should still get.
const subtitleLanguageAuto = "auto"

func subtitleLanguageOptions() []preferenceOption {
	options := make([]preferenceOption, 0, len(subtitleLanguages)+1)
	options = append(options, option(subtitleLanguageAuto, "Automatic"))
	for _, language := range subtitleLanguages {
		options = append(options, option(language.Value, language.Label))
	}
	return options
}

// normalizeSubtitleLanguage folds a stream's language onto the catalogue's vocabulary.
// Anything unrecognised comes back as the lowercased original rather than empty, so an
// exact string match on a language Memby has no entry for still works.
func normalizeSubtitleLanguage(raw string) string {
	value := strings.ToLower(strings.TrimSpace(raw))
	if value == "" {
		return ""
	}
	// Emby writes plain codes, but a region tag ("pt-BR") or a script suffix is possible.
	if cut := strings.IndexAny(value, "-_"); cut > 0 {
		value = value[:cut]
	}
	for _, language := range subtitleLanguages {
		if language.Value == value {
			return language.Value
		}
		for _, alias := range language.Aliases {
			if alias == value {
				return language.Value
			}
		}
	}
	return value
}

// selectSubtitle returns the id of the track to turn on, or "" for none.
//
// The rules, in order, and each one is there for a reason worth keeping:
//
//   - Subtitles off means off. Nothing else is consulted.
//   - A chosen language wins, and among tracks in it a plain full track beats a forced or
//     hearing-impaired one — somebody who picked Italian wants the dialogue, not the
//     signs-only track that happens to sort first.
//   - A chosen language that the title does not have falls back to a *forced* track only.
//     Forced subtitles carry the parts of a film that are foreign to its own audio, so they
//     are wanted regardless; falling through to English instead would put a language the
//     viewer did not ask for on screen.
//   - With no language chosen, the old behaviour stands: forced, then default, then the
//     first track. That is what an install that has never touched the setting keeps.
func selectSubtitle(subtitles []playableSubtitle, enabled bool, language string) string {
	if !enabled || len(subtitles) == 0 {
		return ""
	}
	preferred := normalizeSubtitleLanguage(language)
	if preferred != "" && preferred != subtitleLanguageAuto {
		if match := bestSubtitleInLanguage(subtitles, preferred); match != "" {
			return match
		}
		return firstSubtitleWhere(subtitles, func(s playableSubtitle) bool { return s.IsForced })
	}
	if forced := firstSubtitleWhere(
		subtitles, func(s playableSubtitle) bool { return s.IsForced },
	); forced != "" {
		return forced
	}
	if fallback := firstSubtitleWhere(
		subtitles, func(s playableSubtitle) bool { return s.IsDefault },
	); fallback != "" {
		return fallback
	}
	return subtitles[0].ID
}

// bestSubtitleInLanguage ranks the tracks in one language: a plain track, then a default
// one, then hearing-impaired, then forced. Forced sorts last here precisely because it is
// not a substitute for a full track in the language somebody asked for.
func bestSubtitleInLanguage(subtitles []playableSubtitle, language string) string {
	best, bestRank := "", 0
	for _, subtitle := range subtitles {
		if normalizeSubtitleLanguage(subtitle.Language) != language {
			continue
		}
		rank := 4
		switch {
		case subtitle.IsForced:
			rank = 1
		case subtitle.IsHearingImpaired:
			rank = 2
		case subtitle.IsDefault:
			rank = 5
		}
		if rank > bestRank {
			best, bestRank = subtitle.ID, rank
		}
	}
	return best
}

func firstSubtitleWhere(subtitles []playableSubtitle, match func(playableSubtitle) bool) string {
	for _, subtitle := range subtitles {
		if match(subtitle) {
			return subtitle.ID
		}
	}
	return ""
}
