using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;
namespace EnhancedHierarchy
{
    /// 
    /// Main class, draws hierarchy items.
    /// 
    [InitializeOnLoad]
    public static partial class EnhancedHierarchy
    {
        private static MiniLabelProvider[] MiniLabelProviders
        {
            get { return Preferences.miniLabelProviders; }
        }
        static EnhancedHierarchy()
        {
            if (Preferences.DebugEnabled || Preferences.ProfilingEnabled)
            {
                Utility.EnableFPSCounter();
                Utility.ForceUpdateHierarchyEveryFrame();
            }
            EditorApplication.hierarchyWindowItemOnGUI += SetItemInformation;
            EditorApplication.hierarchyWindowItemOnGUI += OnItemGUI;
            EditorApplication.RepaintHierarchyWindow();
        }
        private static void OnItemGUI(int id, Rect rect)
        {
            if (!Preferences.Enabled)
                return;
            bool isActivie = EditorPrefs.GetBool("isActiveObjectTool", false);
            if (!isActivie)
            {
                return;
            }
            using (ProfilerSample.Get("Enhanced Hierarchy"))
                try
                {
                    if (IsGameObject)
                    {
                        for (var i = 0; i < Preferences.RightIcons.Value.Count; i++)
                            Preferences.RightIcons.Value[i].SafeInit();
                        for (var i = 0; i < Preferences.LeftIcons.Value.Count; i++)
                            Preferences.LeftIcons.Value[i].SafeInit();
                        Preferences.LeftSideButton.SafeInit();
                        for (var i = 0; i < MiniLabelProviders.Length; i++)
                        {
                            MiniLabelProviders[i].Init();
                        }
                    }
                    if (IsFirstVisible && Reflected.HierarchyArea.Supported)
                    {
                        Reflected.HierarchyArea.IndentWidth = Preferences.Indent;
                        Reflected.HierarchyArea.BaseIndent = Preferences.LeftMargin;
                    }
                    //SetTitle("EH 2.0");
                    CalculateIconsWidth();
                    DoSelection(RawRect);
                    IgnoreLockedSelection();
                    DrawTree(RawRect);
                    ChildToggle();
                    var trailingWidth = DoTrailing();
                    DrawHover();
                    ColorSort(RawRect);
                    DrawLeftSideIcons(RawRect);
                    DrawTooltip(RawRect, trailingWidth);
                    if (Reflected.IconWidthSupported)
                        Reflected.IconWidth = Preferences.DisableNativeIcon ? 0 : 16;
                    if (IsGameObject)
                    {
                        rect.xMax -= Preferences.RightMargin;
                        rect.xMin = rect.xMax;
                        rect.y++;
                        for (var i = 0; i < Preferences.RightIcons.Value.Count; i++)
                            using (new GUIBackgroundColor(Styles.backgroundColorEnabled))
                            {
                                var icon = Preferences.RightIcons.Value[i];
                                rect.xMin -= icon.SafeGetWidth();
                                icon.SafeDoGUI(rect);
                                rect.xMax -= icon.SafeGetWidth();
                            }
                        var leftSideRect = RawRect;
                        if (Preferences.LeftmostButton)
                            leftSideRect.xMin = 0f;
                        else
                            leftSideRect.xMin -= 2f + CurrentGameObject.transform.childCount > 0 || Preferences.TreeOpacity > ALPHA_THRESHOLD ? 30f : 18f;
                        leftSideRect.xMax = leftSideRect.xMin + Preferences.LeftSideButton.SafeGetWidth();
                        using (new GUIBackgroundColor(Styles.backgroundColorEnabled))
                            Preferences.LeftSideButton.SafeDoGUI(leftSideRect);
                    }
                    DrawMiniLabel(ref rect);
                    DrawHorizontalSeparator(RawRect);
                }
                catch (Exception e)
                {
                    Utility.LogException(e);
                }
        }
        private static void DrawHover()
        {
            if (Reflected.NativeHierarchyHoverTintSupported)
            {
                if (IsFirstVisible && IsRepaintEvent)
                    Reflected.NativeHierarchyHoverTint = Preferences.HoverTintColor;
                return;
            }
            var tint = Preferences.HoverTintColor.Value;
            if (IsFirstVisible && Reflected.NativeHierarchyHoverTintSupported)
                Reflected.HierarchyWindowInstance.wantsMouseMove = tint.a >= ALPHA_THRESHOLD;
            if (tint.a < ALPHA_THRESHOLD)
                return;
            if (!Utility.ShouldCalculateTooltipAt(FullSizeRect))
                return;
            if (IsRepaintEvent)
                EditorGUI.DrawRect(FullSizeRect, tint);
            switch (Event.current.type)
            {
                case EventType.MouseMove:
                    Event.current.Use();
                    break;
            }
        }
        private static void IgnoreLockedSelection()
        {
            if (Preferences.AllowSelectingLockedObjects || !IsFirstVisible || !IsRepaintEvent)
                return;
            using (ProfilerSample.Get())
            {
                var selection = Selection.objects;
                var changed = false;
                for (var i = 0; i < selection.Length; i++)
                    if (selection[i] is GameObject && (selection[i].hideFlags & HideFlags.NotEditable) != 0 && !EditorUtility.IsPersistent(selection[i]))
                    {
                        selection[i] = null;
                        changed = true;
                    }
                if (changed)
                {
                    Selection.objects = selection;
                    Reflected.SetHierarchySelectionNeedSync();
                    EditorApplication.RepaintHierarchyWindow();
                }
            }
        }
        private static void ChildToggle()
        {
            using (ProfilerSample.Get())
            {
                if (!Preferences.NumericChildExpand || !IsRepaintEvent || !IsGameObject || CurrentGameObject.transform.childCount <= 0)
                    return;
                var rect = RawRect;
                var childString = CurrentGameObject.transform.childCount.ToString("00");
                var expanded = Reflected.GetTransformIsExpanded(CurrentGameObject);
                rect.xMax = rect.xMin - 1f;
                rect.xMin -= 15f;
                if (childString.Length > 2)
                    rect.xMin -= 4f;
                using (new GUIBackgroundColor(Styles.childToggleColor))
                    Styles.newToggleStyle.Draw(rect, Utility.GetTempGUIContent(childString), false, false, expanded, false);
            }
        }
        private static void DrawHorizontalSeparator(Rect rect)
        {
            if (Preferences.LineSize < 1 || Preferences.LineColor.Value.a <= ALPHA_THRESHOLD || !IsRepaintEvent)
                return;
            using (ProfilerSample.Get())
            {
                rect.xMin = 0f;
                rect.xMax = rect.xMax + 50f;
                rect.yMin -= Preferences.LineSize / 2;
                rect.yMax = rect.yMin + Preferences.LineSize;
                EditorGUI.DrawRect(rect, Preferences.LineColor);
                if (!IsFirstVisible)
                    return;
                rect.y = FinalRect.y - Preferences.LineSize / 2;
                var height = Reflected.HierarchyWindowInstance.position.height;
                var count = (height - FinalRect.y) / FinalRect.height;
                if (FinalRect.height <= 0f)
                    count = 100f;
                for (var i = 0; i < count; i++)
                {
                    rect.y += RawRect.height;
                    EditorGUI.DrawRect(rect, Preferences.LineColor);
                }
            }
        }
        private static void ColorSort(Rect rect)
        {
            if (!IsRepaintEvent)
                return;
            using (ProfilerSample.Get())
            {
                rect.xMin = 0f;
                rect.xMax = rect.xMax + 50f;
                var rowTint = GetRowTint();
                var rowCustomTint = GetRowCustomTint();
                if (rowCustomTint.color.a > ALPHA_THRESHOLD)
                    using (new GUIColor(rowCustomTint.color))
                    {
                        switch (rowCustomTint.mode)
                        {
                            case TintMode.Flat:
                                EditorGUI.DrawRect(rect, Color.white);
                                break;
                            case TintMode.GradientLeftToRight:
                                GUI.DrawTexture(Utility.FlipRectHorizontally(rect), Styles.fadeTexture, ScaleMode.StretchToFill);
                                break;
                            case TintMode.GradientRightToLeft:
                                GUI.DrawTexture(rect, Styles.fadeTexture, ScaleMode.StretchToFill);
                                break;
                        }
                    }
                if (rowTint.a > ALPHA_THRESHOLD)
                    EditorGUI.DrawRect(rect, rowTint);
                if (!IsFirstVisible)
                    return;
                rect.y = FinalRect.y;
                var height = Reflected.HierarchyWindowInstance.position.height;
                var count = (height - FinalRect.y) / FinalRect.height;
                if (FinalRect.height <= 0f)
                    count = 100f;
                for (var i = 0; i < count; i++)
                {
                    rect.y += RawRect.height;
                    rowTint = GetRowTint(rect);
                    if (rowTint.a > ALPHA_THRESHOLD)
                        EditorGUI.DrawRect(rect, rowTint);
                }
            }
        }
        private static void DrawTree(Rect rect)
        {
            if (Preferences.TreeOpacity <= ALPHA_THRESHOLD || !IsGameObject)
                return;
            if (!IsRepaintEvent && !Preferences.SelectOnTree)
                return;
            using (ProfilerSample.Get())
            using (new GUIColor(Utility.GetHierarchyColor(CurrentGameObject.transform.parent), Preferences.TreeOpacity))
            {
                var indent = Reflected.HierarchyArea.Supported ? Reflected.HierarchyArea.IndentWidth : 16f;
                // #42 - Jules: pull back indent one level and neatly align tree lines with expansion arrow tips
                rect.x -= indent + 2f;
                rect.xMin -= 14f;
                rect.xMax = rect.xMin + 14f;
                // #42 - Jules: allow showing of stems for container objects
                if (CurrentGameObject.transform.parent)
                {
                    var lastInHierarchy = Utility.TransformIsLastChild(CurrentGameObject.transform);
                    GUI.DrawTexture(rect, lastInHierarchy ? Styles.treeElbowTexture : Styles.treeTeeTexture);
                    /*
                    	#42 - Jules: add short horizontal line to extend stem if there's no expansion triangle.
                    	NOTE: Please make this value into a slider in the preferences, since it's arguably a bad thing to have it.
                    	It looks "nicer" with extended stems but is less clear since it throws off the stem alignments. 
                    	At a value of 1 this can make things look an entire level off of where they truly are relative to other things!!!
                    	I think 0.5 is an okay compromise, but 0 is the most consistent, which means no extra stem line at all.
                    	So it's the sort of thing where people might have different ideas of what's best for them...
                    */
                    var extendStemProportion = CurrentGameObject.transform.childCount == 0 ? Preferences.TreeStemProportion.Value * indent : indent - 14f;
                    if (extendStemProportion > 0.01f)
                    {
                        var extendedStemRect = new Rect(rect.x + rect.size.x, rect.y + (lastInHierarchy ? 9f : 8f), extendStemProportion, 1f);
                        EditorGUI.DrawRect(extendedStemRect, Color.white);
                    }
                    if (Preferences.SelectOnTree && GUI.Button(rect, GUIContent.none, Styles.labelNormal))
                        Selection.activeTransform = CurrentGameObject.transform.parent;
                }
                var currentTransform = CurrentGameObject.transform.parent;
                for (rect.x -= indent; rect.xMin > 0f && currentTransform && currentTransform.parent; rect.x -= indent)
                {
                    if (!Utility.TransformIsLastChild(currentTransform))
                        using (new GUIColor(Utility.GetHierarchyColor(currentTransform.parent), Preferences.TreeOpacity))
                        {
                            GUI.DrawTexture(rect, Styles.treeLineTexture);
                            if (Preferences.SelectOnTree && GUI.Button(rect, GUIContent.none, Styles.labelNormal))
                                Selection.activeTransform = currentTransform.parent;
                        }
                    currentTransform = currentTransform.parent;
                }
            }
        }
        private static void CalculateIconsWidth()
        {
            using (ProfilerSample.Get())
            {
                LeftIconsWidth = 0f;
                RightIconsWidth = 0f;
                if (!IsGameObject)
                    return;
                for (var i = 0; i < Preferences.RightIcons.Value.Count; i++)
                    RightIconsWidth += Preferences.RightIcons.Value[i].SafeGetWidth();
                for (var i = 0; i < Preferences.LeftIcons.Value.Count; i++)
                    LeftIconsWidth += Preferences.LeftIcons.Value[i].SafeGetWidth();
            }
        }
        private static void DrawLeftSideIcons(Rect rect)
        {
            if (!IsGameObject)
                return;
            using (ProfilerSample.Get())
            {
                rect.xMin += LabelSize;
                rect.xMin = Math.Min(rect.xMax - RightIconsWidth - LeftIconsWidth - CalcMiniLabelSize() - 5f - Preferences.RightMargin, rect.xMin);
                for (var i = 0; i < Preferences.LeftIcons.Value.Count; i++)
                    using (new GUIBackgroundColor(Styles.backgroundColorEnabled))
                    {
                        var icon = Preferences.LeftIcons.Value[i];
                        rect.xMax = rect.xMin + icon.SafeGetWidth();
                        icon.SafeDoGUI(rect);
                        rect.xMin = rect.xMax;
                    }
            }
        }
        private static float DoTrailing()
        {
            if (!IsRepaintEvent || !Preferences.Trailing || !IsGameObject)
                return RawRect.xMax;
            using (ProfilerSample.Get())
            {
                var size = LabelSize; // CurrentStyle.CalcSize(Utility.GetTempGUIContent(GameObjectName)).x;
                var iconsWidth = RightIconsWidth + LeftIconsWidth + CalcMiniLabelSize() + Preferences.RightMargin;
                var iconsMin = FullSizeRect.xMax - iconsWidth;
                var labelMax = LabelOnlyRect.xMax;
                var overlapping = iconsMin <= labelMax;
                if (!overlapping)
                    return labelMax;
                var rect = FullSizeRect;
                rect.xMin = iconsMin - 18;
                rect.xMax = labelMax;
                if (Selection.gameObjects.Contains(CurrentGameObject))
                    EditorGUI.DrawRect(rect, Reflected.HierarchyFocused ? Styles.selectedFocusedColor : Styles.selectedUnfocusedColor);
                else
                    EditorGUI.DrawRect(rect, Styles.normalColor);
                rect.y++;
                using (new GUIColor(CurrentColor))
                    EditorStyles.boldLabel.Draw(rect, trailingContent, 0);
                return iconsMin;
            }
        }
        private static void DrawMiniLabel(ref Rect rect)
        {
            if (!IsGameObject)
                return;
            rect.x -= 3f;
            using (ProfilerSample.Get())
                switch (MiniLabelProviders.Length)
                {
                    case 0:
                        return;
                    case 1:
                        if (MiniLabelProviders[0].HasValue())
                            MiniLabelProviders[0].Draw(ref rect);
                        break;
                    default:
                        var ml0 = MiniLabelProviders[0];
                        var ml1 = MiniLabelProviders[1];
                        var ml0HasValue = ml0.HasValue();
                        var ml1HasValue = ml1.HasValue();
                        if (ml0HasValue && ml1HasValue || !Preferences.CentralizeMiniLabelWhenPossible)
                        {
                            var topRect = rect;
                            var bottomRect = rect;
                            topRect.yMax = RawRect.yMax - RawRect.height / 2f;
                            bottomRect.yMin = RawRect.yMin + RawRect.height / 2f;
                            if (ml0HasValue)
                                ml0.Draw(ref topRect);
                            if (ml1HasValue)
                                ml1.Draw(ref bottomRect);
                            rect.xMin = Mathf.Min(topRect.xMin, bottomRect.xMin);
                        }
                        else if (ml1HasValue)
                            ml1.Draw(ref rect);
                        else if (ml0HasValue)
                            ml0.Draw(ref rect);
                        break;
                }
        }
        private static float CalcMiniLabelSize()
        {
            Styles.miniLabelStyle.fontSize = Preferences.SmallerMiniLabel ? 8 : 9;
            using (ProfilerSample.Get())
            {
                switch (MiniLabelProviders.Length)
                {
                    case 0:
                        return 0f;
                    case 1:
                        return MiniLabelProviders[0].Measure();
                    default:
                        return Math.Max(
                            MiniLabelProviders[0].Measure(),
                            MiniLabelProviders[1].Measure()
                        );
                }
            }
        }
        private static void DrawTooltip(Rect rect, float fullTrailingWidth)
        {
            if (!Preferences.Tooltips || !IsGameObject || !IsRepaintEvent)
                return;
            using (ProfilerSample.Get())
            {
                if (DragSelection != null)
                    return;
                rect.xMax = Mathf.Min(fullTrailingWidth, rect.xMin + LabelSize);
                rect.xMin = 0f;
                if (!Utility.ShouldCalculateTooltipAt(rect))
                    return;
                var tooltip = new StringBuilder(100);
                tooltip.AppendLine(GameObjectName);
                tooltip.AppendFormat("\nTag: {0}", GameObjectTag);
                tooltip.AppendFormat("\nLayer: {0}", LayerMask.LayerToName(CurrentGameObject.layer));
                if (GameObjectUtility.GetStaticEditorFlags(CurrentGameObject) != 0)
                    tooltip.AppendFormat("\nStatic: {0}", Utility.EnumFlagsToString(GameObjectUtility.GetStaticEditorFlags(CurrentGameObject)));
                tooltip.AppendLine();
                tooltip.AppendLine();
                foreach (var component in Components)
                    if (component is Transform)
                        continue;
                    else if (component)
                        tooltip.AppendLine(ObjectNames.GetInspectorTitle(component));
                    else
                        tooltip.AppendLine("Missing Component");
                EditorGUI.LabelField(rect, Utility.GetTempGUIContent(null, tooltip.ToString().TrimEnd('\n', '\r')));
            }
        }
        private static void DoSelection(Rect rect)
        {
            if (!Preferences.EnhancedSelectionSupported || !Preferences.EnhancedSelection || Event.current.button != 1)
            {
                DragSelection = null;
                return;
            }
            using (ProfilerSample.Get())
            {
                rect.xMin = 0f;
                switch (Event.current.type)
                {
                    case EventType.MouseDrag:
                        if (!IsFirstVisible)
                            return;
                        if (DragSelection == null)
                        {
                            DragSelection = new List