using System;
using System.Collections;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.UI;
using System.Collections.Generic;
using Unity.Profiling;
using UnityEngine.TextCore;
using UnityEngine.TextCore.LowLevel;
using Object = UnityEngine.Object;
#pragma warning disable 0414 // Disabled a few warnings related to serialized variables not used in this script but used in the editor.
#pragma warning disable 0618 // Disabled warning due to SetVertices being deprecated until new release with SetMesh() is available.
namespace TMPro
{
    [DisallowMultipleComponent]
    [RequireComponent(typeof(RectTransform))]
    [RequireComponent(typeof(CanvasRenderer))]
    [AddComponentMenu("UI/TextMeshPro - Text (UI)", 11)]
    [ExecuteAlways]
    #if UNITY_2023_2_OR_NEWER
    [HelpURL("https://docs.unity3d.com/Packages/com.unity.ugui@2.0/manual/TextMeshPro/index.html")]
    #else
    [HelpURL("https://docs.unity3d.com/Packages/com.unity.textmeshpro@3.2")]
    #endif
    public class TextMeshProUGUI : TMP_Text, ILayoutElement
    {
        /// 
        /// Get the material that will be used for rendering.
        /// 
        public override Material materialForRendering
        {
            get { return TMP_MaterialManager.GetMaterialForRendering(this, m_sharedMaterial); }
        }
        /// 
        /// Determines if the size of the text container will be adjusted to fit the text object when it is first created.
        /// 
        public override bool autoSizeTextContainer
        {
            get { return m_autoSizeTextContainer; }
            set { if (m_autoSizeTextContainer == value) return; m_autoSizeTextContainer = value; if (m_autoSizeTextContainer) { CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this); SetLayoutDirty(); } }
        }
        /// 
        /// Reference to the Mesh used by the text object.
        /// 
        public override Mesh mesh
        {
            get { return m_mesh; }
        }
        /// 
        /// Reference to the CanvasRenderer used by the text object.
        /// 
        public new CanvasRenderer canvasRenderer
        {
            get
            {
                if (m_canvasRenderer == null) m_canvasRenderer = GetComponent();
                return m_canvasRenderer;
            }
        }
        /// 
        /// Anchor dampening prevents the anchor position from being adjusted unless the positional change exceeds about 40% of the width of the underline character. This essentially stabilizes the anchor position.
        /// 
        //public bool anchorDampening
        //{
        //    get { return m_anchorDampening; }
        //    set { if (m_anchorDampening != value) { havePropertiesChanged = true; m_anchorDampening = value; /* ScheduleUpdate(); */ } }
        //}
        #if !UNITY_2019_3_OR_NEWER
        [SerializeField]
        private bool m_Maskable = true;
        #endif
        private bool m_isRebuildingLayout = false;
        private Coroutine m_DelayedGraphicRebuild;
        private Coroutine m_DelayedMaterialRebuild;
        /// 
        /// Function called by Unity when the horizontal layout needs to be recalculated.
        /// 
        public void CalculateLayoutInputHorizontal()
        {
            //Debug.Log("*** CalculateLayoutHorizontal() on Object ID: " + GetInstanceID() + " at frame: " + Time.frameCount + "***");
        }
        /// 
        /// Function called by Unity when the vertical layout needs to be recalculated.
        /// 
        public void CalculateLayoutInputVertical()
        {
            //Debug.Log("*** CalculateLayoutInputVertical() on Object ID: " + GetInstanceID() + " at frame: " + Time.frameCount + "***");
        }
        public override void SetVerticesDirty()
        {
            if (this == null || !this.IsActive())
                return;
            if (CanvasUpdateRegistry.IsRebuildingGraphics())
                return;
            CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this);
            if (m_OnDirtyVertsCallback != null)
                m_OnDirtyVertsCallback();
        }
        /// 
        ///
        /// 
        public override void SetLayoutDirty()
        {
            m_isPreferredWidthDirty = true;
            m_isPreferredHeightDirty = true;
            if (this == null || !this.IsActive())
                return;
            LayoutRebuilder.MarkLayoutForRebuild(this.rectTransform);
            m_isLayoutDirty = true;
            if (m_OnDirtyLayoutCallback != null)
                m_OnDirtyLayoutCallback();
        }
        /// 
        ///
        /// 
        public override void SetMaterialDirty()
        {
            if (this == null || !this.IsActive())
                return;
            if (CanvasUpdateRegistry.IsRebuildingGraphics())
                return;
            m_isMaterialDirty = true;
            CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this);
            if (m_OnDirtyMaterialCallback != null)
                m_OnDirtyMaterialCallback();
        }
        /// 
        ///
        /// 
        public override void SetAllDirty()
        {
            SetLayoutDirty();
            SetVerticesDirty();
            SetMaterialDirty();
        }
        /// 
        /// Delay registration of text object for graphic rebuild by one frame.
        /// 
        /// 
        IEnumerator DelayedGraphicRebuild()
        {
            yield return null;
            CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this);
            if (m_OnDirtyVertsCallback != null)
                m_OnDirtyVertsCallback();
            m_DelayedGraphicRebuild = null;
        }
        /// 
        /// Delay registration of text object for graphic rebuild by one frame.
        /// 
        /// 
        IEnumerator DelayedMaterialRebuild()
        {
            yield return null;
            m_isMaterialDirty = true;
            CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this);
            if (m_OnDirtyMaterialCallback != null)
                m_OnDirtyMaterialCallback();
            m_DelayedMaterialRebuild = null;
        }
        /// 
        ///
        /// 
        /// 
        public override void Rebuild(CanvasUpdate update)
        {
            if (this == null) return;
            if (update == CanvasUpdate.Prelayout)
            {
                if (m_autoSizeTextContainer)
                {
                    m_rectTransform.sizeDelta = GetPreferredValues(Mathf.Infinity, Mathf.Infinity);
                }
            }
            else if (update == CanvasUpdate.PreRender)
            {
                OnPreRenderCanvas();
                if (!m_isMaterialDirty) return;
                UpdateMaterial();
                m_isMaterialDirty = false;
            }
        }
        /// 
        /// Method to keep the pivot of the sub text objects in sync with the parent pivot.
        /// 
        private void UpdateSubObjectPivot()
        {
            if (m_textInfo == null) return;
            for (int i = 1; i < m_subTextObjects.Length && m_subTextObjects[i] != null; i++)
            {
                m_subTextObjects[i].SetPivotDirty();
            }
            //m_isPivotDirty = false;
        }
        /// 
        ///
        /// 
        /// 
        /// 
        public override Material GetModifiedMaterial(Material baseMaterial)
        {
            Material mat = baseMaterial;
            if (m_ShouldRecalculateStencil)
            {
                var rootCanvas = MaskUtilities.FindRootSortOverrideCanvas(transform);
                m_StencilValue = maskable ? MaskUtilities.GetStencilDepth(transform, rootCanvas) : 0;
                m_ShouldRecalculateStencil = false;
            }
            if (m_StencilValue > 0)
            {
                var maskMat = StencilMaterial.Add(mat, (1 << m_StencilValue) - 1, StencilOp.Keep, CompareFunction.Equal, ColorWriteMask.All, (1 << m_StencilValue) - 1, 0);
                StencilMaterial.Remove(m_MaskMaterial);
                m_MaskMaterial = maskMat;
                mat = m_MaskMaterial;
            }
            return mat;
        }
        /// 
        ///
        /// 
        protected override void UpdateMaterial()
        {
            //Debug.Log("*** UpdateMaterial() ***");
            //if (!this.IsActive())
            //    return;
            if (m_sharedMaterial == null || canvasRenderer == null) return;
            m_canvasRenderer.materialCount = 1;
            m_canvasRenderer.SetMaterial(materialForRendering, 0);
            //m_canvasRenderer.SetTexture(materialForRendering.mainTexture);
        }
        //public override void OnRebuildRequested()
        //{
        //    //Debug.Log("OnRebuildRequested");
        //    base.OnRebuildRequested();
        //}
        //public override bool Raycast(Vector2 sp, Camera eventCamera)
        //{
        //    //Debug.Log("Raycast Event. ScreenPoint: " + sp);
        //    return base.Raycast(sp, eventCamera);
        //}
        // MASKING RELATED PROPERTIES
        /// 
        /// Sets the masking offset from the bounds of the object
        /// 
        public Vector4 maskOffset
        {
            get { return m_maskOffset; }
            set { m_maskOffset = value; UpdateMask(); m_havePropertiesChanged = true; }
        }
        //public override Material defaultMaterial
        //{
        //    get { Debug.Log("Default Material called."); return m_sharedMaterial; }
        //}
        //protected override void OnCanvasHierarchyChanged()
        //{
        //    //Debug.Log("OnCanvasHierarchyChanged...");
        //}
        // IClippable implementation
        /// 
        /// Method called when the state of a parent changes.
        /// 
        public override void RecalculateClipping()
        {
            //Debug.Log("***** RecalculateClipping() *****");
            base.RecalculateClipping();
        }
        // IMaskable Implementation
        /// 
        /// Method called when Stencil Mask needs to be updated on this element and parents.
        /// 
        // public override void RecalculateMasking()
        // {
        //     //Debug.Log("***** RecalculateMasking() *****");
        //
        //     this.m_ShouldRecalculateStencil = true;
        //     SetMaterialDirty();
        // }
        //public override void SetClipRect(Rect clipRect, bool validRect)
        //{
        //    //Debug.Log("***** SetClipRect (" + clipRect + ", " + validRect + ") *****");
        //    base.SetClipRect(clipRect, validRect);
        //}
        /// 
        /// Override of the Cull function to provide for the ability to override the culling of the text object.
        /// 
        /// 
        /// 
        public override void Cull(Rect clipRect, bool validRect)
        {
            m_ShouldUpdateCulling = false;
            // Delay culling check in the event the text layout is dirty and geometry has to be updated.
            if (m_isLayoutDirty)
            {
                m_ShouldUpdateCulling = true;
                m_ClipRect = clipRect;
                m_ValidRect = validRect;
                return;
            }
            // Get compound rect for the text object and sub text objects in local canvas space.
            Rect rect = GetCanvasSpaceClippingRect();
            // No point culling if geometry bounds have no width or height.
            //if (rect.width == 0 || rect.height == 0)
            //    return;
            var cull = !validRect || !clipRect.Overlaps(rect, true);
            if (m_canvasRenderer.cull != cull)
            {
                m_canvasRenderer.cull = cull;
                onCullStateChanged.Invoke(cull);
                OnCullingChanged();
                // Update any potential sub mesh objects
                for (int i = 1; i < m_subTextObjects.Length && m_subTextObjects[i] != null; i++)
                {
                    m_subTextObjects[i].canvasRenderer.cull = cull;
                }
            }
        }
        private bool m_ShouldUpdateCulling;
        private Rect m_ClipRect;
        private bool m_ValidRect;
        /// 
        /// Internal function to allow delay of culling until the text geometry has been updated.
        /// 
        internal override void UpdateCulling()
        {
            // Get compound rect for the text object and sub text objects in local canvas space.
            Rect rect = GetCanvasSpaceClippingRect();
            // No point culling if geometry bounds have no width or height.
            //if (rect.width == 0 || rect.height == 0)
            //    return;
            var cull = !m_ValidRect || !m_ClipRect.Overlaps(rect, true);
            if (m_canvasRenderer.cull != cull)
            {
                m_canvasRenderer.cull = cull;
                onCullStateChanged.Invoke(cull);
                OnCullingChanged();
                // Update any potential sub mesh objects
                for (int i = 1; i < m_subTextObjects.Length && m_subTextObjects[i] != null; i++)
                {
                    m_subTextObjects[i].canvasRenderer.cull = cull;
                }
            }
            m_ShouldUpdateCulling = false;
        }
        /*
        /// 
        /// Sets the mask type
        /// 
        public MaskingTypes mask
        {
            get { return m_mask; }
            set { m_mask = value; havePropertiesChanged = true; isMaskUpdateRequired = true; }
        }
        /// 
        /// Set the masking offset mode (as percentage or pixels)
        /// 
        public MaskingOffsetMode maskOffsetMode
        {
            get { return m_maskOffsetMode; }
            set { m_maskOffsetMode = value; havePropertiesChanged = true; isMaskUpdateRequired = true; }
        }
        */
        /*
        /// 
        /// Sets the softness of the mask
        /// 
        public Vector2 maskSoftness
        {
            get { return m_maskSoftness; }
            set { m_maskSoftness = value; havePropertiesChanged = true; isMaskUpdateRequired = true; }
        }
        /// 
        /// Allows to move / offset the mesh vertices by a set amount
        /// 
        public Vector2 vertexOffset
        {
            get { return m_vertexOffset; }
            set { m_vertexOffset = value; havePropertiesChanged = true; isMaskUpdateRequired = true; }
        }
        */
        /// 
        /// Function to be used to force recomputing of character padding when Shader / Material properties have been changed via script.
        /// 
        public override void UpdateMeshPadding()
        {
            m_padding = ShaderUtilities.GetPadding(m_sharedMaterial, m_enableExtraPadding, m_isUsingBold);
            m_isMaskingEnabled = ShaderUtilities.IsMaskingEnabled(m_sharedMaterial);
            m_havePropertiesChanged = true;
            checkPaddingRequired = false;
            // Return if text object is not awake yet.
            if (m_textInfo == null) return;
            // Update sub text objects
            for (int i = 1; i < m_textInfo.materialCount; i++)
                m_subTextObjects[i].UpdateMeshPadding(m_enableExtraPadding, m_isUsingBold);
        }
        /// 
        /// Tweens the CanvasRenderer color associated with this Graphic.
        /// 
        /// Target color.
        /// Tween duration.
        /// Should ignore Time.scale?
        /// Should also Tween the alpha channel?
        protected override void InternalCrossFadeColor(Color targetColor, float duration, bool ignoreTimeScale, bool useAlpha)
        {
            if (m_textInfo == null)
                return;
            int materialCount = m_textInfo.materialCount;
            for (int i = 1; i < materialCount; i++)
            {
                m_subTextObjects[i].CrossFadeColor(targetColor, duration, ignoreTimeScale, useAlpha);
            }
        }
        /// 
        /// Tweens the alpha of the CanvasRenderer color associated with this Graphic.
        /// 
        /// Target alpha.
        /// Duration of the tween in seconds.
        /// Should ignore Time.scale?
        protected override void InternalCrossFadeAlpha(float alpha, float duration, bool ignoreTimeScale)
        {
            if (m_textInfo == null)
                return;
            int materialCount = m_textInfo.materialCount;
            for (int i = 1; i < materialCount; i++)
            {
                m_subTextObjects[i].CrossFadeAlpha(alpha, duration, ignoreTimeScale);
            }
        }
        /// 
        /// Function to force regeneration of the text object before its normal process time. This is useful when changes to the text object properties need to be applied immediately.
        /// 
        /// Ignore Active State of text objects. Inactive objects are ignored by default.
        /// Force re-parsing of the text.
        public override void ForceMeshUpdate(bool ignoreActiveState = false, bool forceTextReparsing = false)
        {
            m_havePropertiesChanged = true;
            m_ignoreActiveState = ignoreActiveState;
            // Special handling in the event the Canvas is only disabled
            if (m_canvas == null)
                m_canvas = GetComponentInParent