using Cryville.Common.Font;
using Cryville.Culture;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using TMPro;
using UnityEngine;
using UnityEngine.TextCore.LowLevel;
using UnityEngine.TextCore.Text;

namespace Cryville.Common.Unity.UI {
	[RequireComponent(typeof(TMP_Text))]
	public class TMPLocalizedText : MonoBehaviour {
		public static Shader DefaultShader;
		public static FontMatcher FontMatcher;
		public static int MaxFallbackCount = 4;

		static readonly Dictionary<CultureInfo, FontAsset> _cachedFonts = new();

		[SerializeField]
		Shader m_shader;

		public TMP_Text Text { get; private set; }
		void Awake() {
			Text = GetComponent<TMP_Text>();
		}

		public void SetText(string text, CultureInfo culture = null) {
			Text.text = text;
			SetCulture(culture ?? CultureInfo.CurrentCulture);
		}

		void SetCulture(CultureInfo culture) {
			if (FontMatcher == null) return;
			string cultureName = culture.Name;
			if (string.IsNullOrEmpty(cultureName)) cultureName = CultureInfo.CurrentCulture.Name;
			if (!_cachedFonts.TryGetValue(culture, out var font)) {
				foreach (var typeface in FontMatcher.MatchLanguage(new LanguageId(cultureName), true)) {
					try {
						var ifont = CreateFontAsset(typeface.File.FullName, typeface.IndexInFile);
						if (m_shader) ifont.material.shader = m_shader;
						else if (DefaultShader) ifont.material.shader = DefaultShader;
						if (font == null) {
							font = ifont;
							if (MaxFallbackCount <= 0) break;
						}
						else {
							font.fallbackFontAssetTable ??= new List<FontAsset>();
							font.fallbackFontAssetTable.Add(ifont);
							if (font.fallbackFontAssetTable.Count >= MaxFallbackCount) break;
						}
					}
					catch (Exception) { }
				}
				_cachedFonts.Add(culture, font);
			}
			Text.font = font;
		}

		static MethodInfo _methodCreateFontAsset;
		static readonly object[] _paramsCreateFontAsset = new object[] { null, null, 90, 9, GlyphRenderMode.SDFAA, 1024, 1024, Type.Missing, Type.Missing };
		static FontAsset CreateFontAsset(string path, int index) {
			if (_methodCreateFontAsset == null) {
				_methodCreateFontAsset = typeof(FontAsset).GetMethod(
					"CreateFontAsset", BindingFlags.Static | BindingFlags.NonPublic, null,
					new Type[] {
						typeof(string), typeof(int), typeof(int), typeof(int),
						typeof(GlyphRenderMode), typeof(int), typeof(int),
						typeof(AtlasPopulationMode), typeof(bool)
					},
					null
				);
			}
			_paramsCreateFontAsset[0] = path;
			_paramsCreateFontAsset[1] = index;
			return (FontAsset)_methodCreateFontAsset.Invoke(null, _paramsCreateFontAsset);
		}
	}
}
