using Cryville.Culture;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Cryville.Common.Font {
	public abstract class FontMatcher {
		protected FontManager Manager { get; private set; }
		public FontMatcher(FontManager manager) { Manager = manager; }
		public abstract IEnumerable<Typeface> MatchLanguage(LanguageId lang, bool distinctFamily = false);
	}
	public class FallbackListFontMatcher : FontMatcher {
		readonly LanguageMatching _matcher;
		static readonly string UltimateFallbackScript = "zzzz";
		public Dictionary<string, List<string>> MapScriptToTypefaces = new();
		public static Dictionary<string, List<string>> GetDefaultWindowsFallbackMap() {
			var map = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
			FillKeysWithScripts(map, () => new List<string>());
			// Reference: https://github.com/chromium/chromium/blob/main/third_party/blink/renderer/platform/fonts/win/font_fallback_win.cc
			map[UltimateFallbackScript].Insert(0, "SimSun"); // Custom
			map[UltimateFallbackScript].Insert(0, "SimHei"); // Custom
			map[UltimateFallbackScript].Insert(0, "Microsoft YaHei"); // Custom
			map[UltimateFallbackScript].Insert(0, "Arial");
			map[UltimateFallbackScript].Insert(0, "Times New Roman");
			map[UltimateFallbackScript].Insert(0, "Segoe UI"); // Custom
			map["arab"].Insert(0, "Tahoma");
			map["cyrl"].Insert(0, "Times New Roman");
			map["grek"].Insert(0, "Times New Roman");
			map["hebr"].Insert(0, "David");
			map["jpan"].Insert(0, "MS PGothic");
			map["latn"].Insert(0, "Times New Roman");
			map["hans"].Insert(0, "SimSun");
			map["hans"].Insert(0, "SimHei"); // Custom
			map["thai"].Insert(0, "Tahoma");
			map["hans"].Insert(0, "PMingLiU");
			// Reference: https://learn.microsoft.com/en-us/globalization/input/font-support
			var ver = Environment.OSVersion.Version;
			if (ver >= new Version(5, 0)) { // Windows 2000
				map["armn"].Insert(0, "Sylfaen");
				map["deva"].Insert(0, "Mangal");
				map["geor"].Insert(0, "Sylfaen");
				map["taml"].Insert(0, "Latha");
			}
			if (ver >= new Version(5, 1)) { // Windows XP
				map["gujr"].Insert(0, "Shruti");
				map["guru"].Insert(0, "Raavi");
				map["knda"].Insert(0, "Tunga");
				map["syrc"].Insert(0, "Estrangelo Edessa");
				map["telu"].Insert(0, "Gautami");
				map["thaa"].Insert(0, "MV Boli");
				// SP2
				map["beng"].Insert(0, "Vrinda");
				map["mlym"].Insert(0, "Kartika");
			}
			if (ver >= new Version(6, 0)) { // Windows Vista
				map["cans"].Insert(0, "Euphemia");
				map["cher"].Insert(0, "Plantagenet");
				map["ethi"].Insert(0, "Nyala");
				map["khmr"].Insert(0, "DaunPenh MoolBoran");
				map["kore"].Insert(0, "Malgun Gothic"); // Reference: https://en.wikipedia.org/wiki/List_of_typefaces_included_with_Microsoft_Windows
				map["laoo"].Insert(0, "DokChampa");
				map["mong"].Insert(0, "Mongolian Baiti");
				map["orya"].Insert(0, "Kalinga");
				map["sinh"].Insert(0, "Iskoola Pota");
				map["tibt"].Insert(0, "Microsoft Himalaya");
				map["yiii"].Insert(0, "Microsoft Yi Baiti");
				map["arab"].Insert(0, "Segoe UI");
				map["cyrl"].Insert(0, "Segoe UI");
				map["grek"].Insert(0, "Segoe UI");
				map["latn"].Insert(0, "Segoe UI");
				map["hans"].Add("SimSun-ExtB");
				map["hant"].Add("MingLiU-ExtB");
				map["hant"].Add("MingLiU_HKSCS-ExtB");
				map["arab"].Add("Microsoft Uighur");
				map["zmth"].Insert(0, "Cambria Math");
				// Reference: https://en.wikipedia.org/wiki/List_of_CJK_fonts
				map["jpan"].Insert(0, "Meiryo");
				map["hans"].Insert(0, "Microsoft YaHei");
			}
			if (ver >= new Version(6, 1)) { // Windows 7
				map["brai"].Insert(0, "Segoe UI Symbol");
				map["dsrt"].Insert(0, "Segoe UI Symbol");
				map["talu"].Insert(0, "Microsoft New Tai Lue");
				map["ogam"].Insert(0, "Segoe UI Symbol");
				map["osma"].Insert(0, "Ebrima");
				map["phag"].Insert(0, "Microsoft PhagsPa");
				map["runr"].Insert(0, "Segoe UI Symbol");
				map["zsym"].Insert(0, "Segoe UI Symbol");
				map["tale"].Insert(0, "Microsoft Tai Le");
				map["tfng"].Insert(0, "Ebrima");
				map["vaii"].Insert(0, "Ebrima");
			}
			if (ver >= new Version(6, 2)) { // Windows 8
				map["glag"].Insert(0, "Segoe UI Symbol");
				map["goth"].Insert(0, "Segoe UI Symbol");
				map["hang"].Add("Malgun Gothic");
				map["ital"].Insert(0, "Segoe UI Symbol");
				map["lisu"].Insert(0, "Segoe UI");
				map["mymr"].Insert(0, "Myanmar Text");
				map["nkoo"].Insert(0, "Ebrima");
				map["orkh"].Insert(0, "Segoe UI Symbol");
				map["ethi"].Insert(0, "Ebrima");
				map["cans"].Insert(0, "Gadugi");
				map["hant"].Insert(0, "Microsoft JhengHei UI");
				map["hans"].Insert(0, "Microsoft YaHei UI");
				map["beng"].Insert(0, "Nirmala UI");
				map["deva"].Insert(0, "Nirmala UI");
				map["gujr"].Insert(0, "Nirmala UI");
				map["guru"].Insert(0, "Nirmala UI"); // NOT DOCUMENTED, UNVERIFIED
				map["knda"].Insert(0, "Nirmala UI"); // NOT DOCUMENTED, UNVERIFIED
				map["mlym"].Insert(0, "Nirmala UI");
				map["orya"].Insert(0, "Nirmala UI");
				map["sinh"].Insert(0, "Nirmala UI"); // NOT DOCUMENTED, UNVERIFIED
				map["taml"].Insert(0, "Nirmala UI"); // NOT DOCUMENTED, UNVERIFIED
				map["telu"].Insert(0, "Nirmala UI");
				map["armn"].Insert(0, "Segoe UI");
				map["geor"].Insert(0, "Segoe UI");
				map["hebr"].Insert(0, "Segoe UI");
			}
			if (ver >= new Version(6, 3)) { // Windows 8.1
				map["bugi"].Insert(0, "Leelawadee UI");
				map["copt"].Insert(0, "Segoe UI Symbol");
				map["java"].Insert(0, "Javanese Text");
				map["merc"].Insert(0, "Segoe UI Symbol");
				map["olck"].Insert(0, "Nirmala UI");
				map["sora"].Insert(0, "Nirmala UI");
				map["khmr"].Insert(0, "Leelawadee UI");
				map["laoo"].Insert(0, "Leelawadee UI");
				map["thai"].Insert(0, "Leelawadee UI");
				map["zsye"].Insert(0, "Segoe UI Emoji");
			}
			if (ver >= new Version(10, 0)) { // Windows 10
				map["brah"].Insert(0, "Segoe UI Historic");
				map["cari"].Insert(0, "Segoe UI Historic");
				map["cprt"].Insert(0, "Segoe UI Historic");
				map["egyp"].Insert(0, "Segoe UI Historic");
				map["armi"].Insert(0, "Segoe UI Historic");
				map["phli"].Insert(0, "Segoe UI Historic");
				map["prti"].Insert(0, "Segoe UI Historic");
				map["khar"].Insert(0, "Segoe UI Historic");
				map["lyci"].Insert(0, "Segoe UI Historic");
				map["lydi"].Insert(0, "Segoe UI Historic");
				map["phnx"].Insert(0, "Segoe UI Historic");
				map["xpeo"].Insert(0, "Segoe UI Historic");
				map["sarb"].Insert(0, "Segoe UI Historic");
				map["shaw"].Insert(0, "Segoe UI Historic");
				map["xsux"].Insert(0, "Segoe UI Historic");
				map["ugar"].Insert(0, "Segoe UI Historic");
				// Segoe UI Symbol -> Segoe UI Historic
				map["glag"].Insert(0, "Segoe UI Historic");
				map["goth"].Insert(0, "Segoe UI Historic");
				map["merc"].Insert(0, "Segoe UI Historic");
				map["ogam"].Insert(0, "Segoe UI Historic");
				map["ital"].Insert(0, "Segoe UI Historic");
				map["orkh"].Insert(0, "Segoe UI Historic");
				map["runr"].Insert(0, "Segoe UI Historic");
				//
				map["jpan"].Insert(0, "Yu Gothic UI");
				map["zsym"].Add("Segoe MDL2 Assets");
			}
			return map;
		}
		public static Dictionary<string, List<string>> GetDefaultAndroidFallbackMap() {
			var map = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
			FillKeysWithScripts(map, () => new List<string>());
			map[UltimateFallbackScript].Insert(0, "Noto Sans CJK TC"); // TODO Modify default fallback
			map[UltimateFallbackScript].Insert(0, "Noto Sans CJK JP");
			map[UltimateFallbackScript].Insert(0, "Noto Sans CJK SC");
			map[UltimateFallbackScript].Insert(0, "Roboto");
			map["zsye"].Insert(0, "Noto Color Emoji");
			map["zsye"].Add("Noto Color Emoji Flags");
			map["arab"].Insert(0, "Noto Naskh Arabic");
			map["adlm"].Insert(0, "Noto Sans Adlam");
			map["ahom"].Insert(0, "Noto Sans Ahom");
			map["hluw"].Insert(0, "Noto Sans Anatolian Hieroglyphs");
			map["armn"].Insert(0, "Noto Sans Armenian");
			map["avst"].Insert(0, "Noto Sans Avestan");
			map["bali"].Insert(0, "Noto Sans Balinese");
			map["bamu"].Insert(0, "Noto Sans Bamum");
			map["bass"].Insert(0, "Noto Sans Bassa Vah");
			map["batk"].Insert(0, "Noto Sans Batak");
			map["beng"].Insert(0, "Noto Sans Bengali");
			map["bhks"].Insert(0, "Noto Sans Bhaiksuki");
			map["brah"].Insert(0, "Noto Sans Brahmi");
			map["bugi"].Insert(0, "Noto Sans Buginese");
			map["buhd"].Insert(0, "Noto Sans Buhid");
			map["jpan"].Insert(0, "Noto Sans CJK JP");
			map["kore"].Insert(0, "Noto Sans CJK KR");
			map["hans"].Insert(0, "Noto Sans CJK SC");
			map["hant"].Insert(0, "Noto Sans CJK TC");
			map["hant"].Add("Noto Sans CJK HK");
			map["cans"].Insert(0, "Noto Sans Canadian Aboriginal");
			map["cari"].Insert(0, "Noto Sans Carian");
			map["cakm"].Insert(0, "Noto Sans Chakma");
			map["cham"].Insert(0, "Noto Sans Cham");
			map["cher"].Insert(0, "Noto Sans Cherokee");
			map["copt"].Insert(0, "Noto Sans Coptic");
			map["xsux"].Insert(0, "Noto Sans Cuneiform");
			map["cprt"].Insert(0, "Noto Sans Cypriot");
			map["dsrt"].Insert(0, "Noto Sans Deseret");
			map["deva"].Insert(0, "Noto Sans Devanagari");
			map["egyp"].Insert(0, "Noto Sans Egyptian Hieroglyphs");
			map["elba"].Insert(0, "Noto Sans Elbasan");
			map["ethi"].Insert(0, "Noto Sans Ethiopic");
			map["geor"].Insert(0, "Noto Sans Georgian");
			map["glag"].Insert(0, "Noto Sans Glagolitic");
			map["goth"].Insert(0, "Noto Sans Gothic");
			map["gran"].Insert(0, "Noto Sans Grantha");
			map["gujr"].Insert(0, "Noto Sans Gujarati");
			map["gong"].Insert(0, "Noto Sans Gunjala Gondi");
			map["guru"].Insert(0, "Noto Sans Gurmukhi");
			map["rohg"].Insert(0, "Noto Sans Hanifi Rohingya");
			map["hano"].Insert(0, "Noto Sans Hanunoo");
			map["hatr"].Insert(0, "Noto Sans Hatran");
			map["hebr"].Insert(0, "Noto Sans Hebrew");
			map["armi"].Insert(0, "Noto Sans Imperial Aramaic");
			map["phli"].Insert(0, "Noto Sans Inscriptional Pahlavi");
			map["prti"].Insert(0, "Noto Sans Inscriptional Parthian");
			map["java"].Insert(0, "Noto Sans Javanese");
			map["kthi"].Insert(0, "Noto Sans Kaithi");
			map["knda"].Insert(0, "Noto Sans Kannada");
			map["kali"].Insert(0, "Noto Sans KayahLi");
			map["khar"].Insert(0, "Noto Sans Kharoshthi");
			map["khmr"].Insert(0, "Noto Sans Khmer");
			map["khoj"].Insert(0, "Noto Sans Khojki");
			map["laoo"].Insert(0, "Noto Sans Lao");
			map["lepc"].Insert(0, "Noto Sans Lepcha");
			map["limb"].Insert(0, "Noto Sans Limbu");
			map["lina"].Insert(0, "Noto Sans Linear A");
			map["linb"].Insert(0, "Noto Sans Linear B");
			map["lisu"].Insert(0, "Noto Sans Lisu");
			map["lyci"].Insert(0, "Noto Sans Lycian");
			map["lydi"].Insert(0, "Noto Sans Lydian");
			map["mlym"].Insert(0, "Noto Sans Malayalam");
			map["mand"].Insert(0, "Noto Sans Mandiac");
			map["mani"].Insert(0, "Noto Sans Manichaean");
			map["marc"].Insert(0, "Noto Sans Marchen");
			map["gonm"].Insert(0, "Noto Sans Masaram Gondi");
			map["medf"].Insert(0, "Noto Sans Medefaidrin");
			map["mtei"].Insert(0, "Noto Sans Meetei Mayek");
			map["merc"].Insert(0, "Noto Sans Meroitic");
			map["mero"].Insert(0, "Noto Sans Meroitic");
			map["plrd"].Insert(0, "Noto Sans Miao");
			map["modi"].Insert(0, "Noto Sans Modi");
			map["mong"].Insert(0, "Noto Sans Mongolian");
			map["mroo"].Insert(0, "Noto Sans Mro");
			map["mult"].Insert(0, "Noto Sans Multani");
			map["mymr"].Insert(0, "Noto Sans Myanmar");
			map["nkoo"].Insert(0, "Noto Sans Nko");
			map["nbat"].Insert(0, "Noto Sans Nabataean");
			map["talu"].Insert(0, "Noto Sans New Tai Lue");
			map["newa"].Insert(0, "Noto Sans Newa");
			map["ogam"].Insert(0, "Noto Sans Ogham");
			map["olck"].Insert(0, "Noto Sans Ol Chiki");
			map["ital"].Insert(0, "Noto Sans Old Italian");
			map["narb"].Insert(0, "Noto Sans Old North Arabian");
			map["perm"].Insert(0, "Noto Sans Old Permic");
			map["xpeo"].Insert(0, "Noto Sans Old Persian");
			map["sarb"].Insert(0, "Noto Sans Old South Arabian");
			map["orkh"].Insert(0, "Noto Sans Old Turkic");
			map["orya"].Insert(0, "Noto Sans Oriya");
			map["osge"].Insert(0, "Noto Sans Osage");
			map["osma"].Insert(0, "Noto Sans Osmanya");
			map["hmng"].Insert(0, "Noto Sans Pahawh Hmong");
			map["palm"].Insert(0, "Noto Sans Palmyrene");
			map["pauc"].Insert(0, "Noto Sans Pau Cin Hau");
			map["phag"].Insert(0, "Noto Sans Phags Pa");
			map["phnx"].Insert(0, "Noto Sans Phoenician");
			map["rjng"].Insert(0, "Noto Sans Rejang");
			map["runr"].Insert(0, "Noto Sans Runic");
			map["samr"].Insert(0, "Noto Sans Samaritan");
			map["saur"].Insert(0, "Noto Sans Saurashtra");
			map["shrd"].Insert(0, "Noto Sans Sharada");
			map["shaw"].Insert(0, "Noto Sans Shavian");
			map["sinh"].Insert(0, "Noto Sans Sinhala");
			map["sora"].Insert(0, "Noto Sans Sora Sompeng");
			map["soyo"].Insert(0, "Noto Sans Soyombo");
			map["sund"].Insert(0, "Noto Sans Sundanese");
			map["sylo"].Insert(0, "Noto Sans Syloti Nagri");
			map["zsym"].Insert(0, "Noto Sans Symbols");
			map["syrc"].Add("Noto Sans Syriac Eastern");
			map["syrc"].Add("Noto Sans Syriac Western");
			map["syrc"].Add("Noto Sans Syriac Estrangela");
			map["tglg"].Insert(0, "Noto Sans Tagalog");
			map["tagb"].Insert(0, "Noto Sans Tagbanwa");
			map["tale"].Insert(0, "Noto Sans Tai Le");
			map["lana"].Insert(0, "Noto Sans Tai Tham");
			map["tavt"].Insert(0, "Noto Sans Tai Viet");
			map["takr"].Insert(0, "Noto Sans Takri");
			map["taml"].Insert(0, "Noto Sans Tamil");
			map["telu"].Insert(0, "Noto Sans Telugu");
			map["thaa"].Insert(0, "Noto Sans Thaana");
			map["thai"].Insert(0, "Noto Sans Thai");
			map["tfng"].Insert(0, "Noto Sans Tifinagh");
			map["ugar"].Insert(0, "Noto Sans Ugaritic");
			map["vaii"].Insert(0, "Noto Sans Vai");
			map["wcho"].Insert(0, "Noto Sans Wancho");
			map["wara"].Insert(0, "Noto Sans Warang Citi");
			map["yiii"].Insert(0, "Noto Sans Yi");
			return map;
		}
		static void FillKeysWithScripts<T>(IDictionary<string, T> map, Func<T> value) {
			foreach (var s in IdValidity.Enumerate("script")) map.Add(s, value());
		}

		public FallbackListFontMatcher(LanguageMatching matcher, FontManager manager) : base(manager) {
			_matcher = matcher;
		}
		public override IEnumerable<Typeface> MatchLanguage(LanguageId lang, bool distinctFamily = false) {
			var supported = MapScriptToTypefaces.Keys.Select(i => new LanguageId(i)).ToList();
			bool flag = false;
			while (_matcher.Match(lang, supported, out var match, out var distance)) {
				if (distance > 40) break;
				if (match.Script.Equals(UltimateFallbackScript, StringComparison.OrdinalIgnoreCase)) {
					flag = true;
				}
				var candidates = MapScriptToTypefaces[match.Script];
				foreach (var typeface in EnumerateTypefaces(candidates, distinctFamily)) {
					yield return typeface;
				}
				supported.Remove(match);
			}
			if (flag) yield break;
			foreach (var typeface in EnumerateTypefaces(MapScriptToTypefaces[UltimateFallbackScript], distinctFamily)) {
				yield return typeface;
			}
		}
		IEnumerable<Typeface> EnumerateTypefaces(List<string> candidates, bool distinctFamily) {
			foreach (var candidate in candidates) {
				if (Manager.MapFullNameToTypeface.TryGetValue(candidate, out var typeface1)) {
					yield return typeface1;
				}
				if (distinctFamily) continue;
				if (Manager.MapNameToTypefaces.TryGetValue(candidate, out var typefaces2)) {
					foreach (var typeface in typefaces2) {
						if (typeface1 == typeface) continue;
						yield return typeface;
					}
				}
			}
		}
	}
}
