/*
 * Copyright 2022 Peter Han
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
 * and associated documentation files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
using HarmonyLib;
using PeterHan.PLib.Core;
using System;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
namespace PeterHan.PLib.UI {
	/// 
	/// Patches bugs in Text Mesh Pro.
	/// 
	internal static class TextMeshProPatcher {
		/// 
		/// The ID to use for Harmony patches.
		/// 
		private const string HARMONY_ID = "TextMeshProPatch";
		/// 
		/// Tracks whether the TMP patches have been checked.
		/// 
		private static volatile bool patchChecked = false;
		/// 
		/// Serializes multiple thread access to the patch status.
		/// 
		private static readonly object patchLock = new object();
		/// 
		/// Applied to TMP_InputField to fix a bug that prevented auto layout from ever
		/// working.
		/// 
		private static bool AssignPositioningIfNeeded_Prefix(TMP_InputField __instance,
				RectTransform ___caretRectTrans, TMP_Text ___m_TextComponent) {
			bool cont = true;
			var crt = ___caretRectTrans;
			if (___m_TextComponent != null && crt != null && __instance != null &&
				___m_TextComponent.isActiveAndEnabled) {
				var rt = ___m_TextComponent.rectTransform;
				if (crt.localPosition != rt.localPosition ||
						crt.localRotation != rt.localRotation ||
						crt.localScale != rt.localScale ||
						crt.anchorMin != rt.anchorMin ||
						crt.anchorMax != rt.anchorMax ||
						crt.anchoredPosition != rt.anchoredPosition ||
						crt.sizeDelta != rt.sizeDelta ||
						crt.pivot != rt.pivot) {
					__instance.StartCoroutine(ResizeCaret(crt, rt));
					cont = false;
				}
			}
			return cont;
		}
		/// 
		/// Checks to see if a patch with our class name has already been applied.
		/// 
		/// The patch list to search.
		/// true if a patch with this class has already patched the method, or false otherwise.
		private static bool HasOurPatch(IEnumerable patchList) {
			bool found = false;
			if (patchList != null) {
				foreach (var patch in patchList) {
					string ownerName = patch.PatchMethod.DeclaringType.Name;
					// Avoid stomping ourselves, or legacy PLibs < 3.14
					if (ownerName == nameof(TextMeshProPatcher) || ownerName == "PLibPatches")
					{
#if DEBUG
						PUtil.LogDebug("TextMeshProPatcher found existing patch from: {0}".
							F(patch.owner));
#endif
						found = true;
						break;
					}
				}
			}
			return found;
		}
		/// 
		/// Patches TMP_InputField with fixes, but only if necessary.
		/// 
		/// The type of TMP_InputField.
		/// The Harmony instance to use for patching.
		private static void InputFieldPatches(Type tmpType) {
			var instance = new Harmony(HARMONY_ID);
			var aip = tmpType.GetMethodSafe("AssignPositioningIfNeeded", false,
				PPatchTools.AnyArguments);
			if (aip != null && !HasOurPatch(Harmony.GetPatchInfo(aip)?.Prefixes))
				instance.Patch(aip, prefix: new HarmonyMethod(typeof(TextMeshProPatcher),
					nameof(AssignPositioningIfNeeded_Prefix)));
			var oe = tmpType.GetMethodSafe("OnEnable", false, PPatchTools.AnyArguments);
			if (oe != null && !HasOurPatch(Harmony.GetPatchInfo(oe)?.Postfixes))
				instance.Patch(oe, postfix: new HarmonyMethod(typeof(TextMeshProPatcher),
					nameof(OnEnable_Postfix)));
		}
		/// 
		/// Applied to TMPro.TMP_InputField to fix a clipping bug inside of Scroll Rects.
		/// 
		/// https://forum.unity.com/threads/textmeshpro-text-still-visible-when-using-nested-rectmask2d.537967/
		/// 
		private static void OnEnable_Postfix(UnityEngine.UI.Scrollbar ___m_VerticalScrollbar,
				TMP_Text ___m_TextComponent) {
			var component = ___m_TextComponent;
			if (component != null)
				component.ignoreRectMaskCulling = ___m_VerticalScrollbar != null;
		}
		/// 
		/// Patches Text Mesh Pro input fields to fix a variety of bugs. Should be used before
		/// any Text Mesh Pro objects are created.
		/// 
		public static void Patch() {
			lock (patchLock) {
				if (!patchChecked) {
					var tmpType = PPatchTools.GetTypeSafe("TMPro.TMP_InputField");
					if (tmpType != null)
						try {
							InputFieldPatches(tmpType);
						} catch (Exception) {
							PUtil.LogWarning("Unable to patch TextMeshPro bug, text fields may display improperly inside scroll areas");
						}
					patchChecked = true;
				}
			}
		}
		/// 
		/// Resizes the caret object to match the text. Used as an enumerator.
		/// 
		/// The rectTransform of the caret.
		/// The rectTransform of the text.
		private static System.Collections.IEnumerator ResizeCaret(
				RectTransform caretTransform, RectTransform textTransform) {
			yield return new WaitForEndOfFrame();
			caretTransform.localPosition = textTransform.localPosition;
			caretTransform.localRotation = textTransform.localRotation;
			caretTransform.localScale = textTransform.localScale;
			caretTransform.anchorMin = textTransform.anchorMin;
			caretTransform.anchorMax = textTransform.anchorMax;
			caretTransform.anchoredPosition = textTransform.anchoredPosition;
			caretTransform.sizeDelta = textTransform.sizeDelta;
			caretTransform.pivot = textTransform.pivot;
		}
	}
}