using System;
using System.Linq;
using System.Collections.Generic;
using UnityEditorInternal;
using UnityEngine;
using UnityEngine.U2D.Animation;

namespace UnityEditor.U2D.Animation
{
    internal class SpriteLibraryDataInspector
    {
        class Style
        {
            public GUIContent duplicateWarningText = EditorGUIUtility.TrTextContent("Duplicate name found or name hash clashes. Please use a different name");
            public GUIContent duplicateWarning;
            public int lineSpacing = 3;
            public int  spriteGridSize = 64;
            public float gridPadding = EditorGUIUtility.standardVerticalSpacing * 2;
            public int gridHeaderSize = 20;
            public int gridFooterSize = 20;
            public float gridHeight;
            public Vector2 gridSize;
            public GUIStyle footerBackground;
            public GUIStyle boxBackground;
            public GUIStyle preButton;
            public GUIStyle headerBackground;
            public GUIStyle gridList;
            public GUIContent iconToolbarPlus = EditorGUIUtility.TrIconContent("Toolbar Plus", "Add to list");
            public GUIContent iconToolbarMinus = EditorGUIUtility.TrIconContent("Toolbar Minus", "Remove selection or last element from list");
            public GUIContent overrideIcon = EditorGUIUtility.TrIconContent("PrefabOverlayAdded Icon");
            public string newCategoryText = L10n.Tr("New Category");
            public string categoryLabel = L10n.Tr("Category");
            public float categoryElementHeight;
            public float categoryTextFieldHeight;
            public string newEntryText = L10n.Tr("Entry");
            public float categoryLabelWidth = 100;

            public Style()
            {
               gridSize = new Vector2(spriteGridSize + gridPadding * 2,
                   spriteGridSize + lineSpacing + EditorGUIUtility.singleLineHeight + gridPadding * 2);
               duplicateWarning = EditorGUIUtility.TrIconContent("console.warnicon.sml", duplicateWarningText.text);
               gridHeight = gridSize.y +gridPadding + gridFooterSize;
               categoryTextFieldHeight  = EditorGUIUtility.singleLineHeight + lineSpacing;
               categoryElementHeight = gridHeight + categoryTextFieldHeight + gridHeaderSize;
               
            }

            public void InitStyle(Event currentEvent)
            {
                if (footerBackground == null && currentEvent.type == EventType.Repaint)
                {
                    footerBackground = "RL Footer";
                    boxBackground = "RL Background";
                    preButton = "RL FooterButton";
                    headerBackground = "RL Header";
                    gridList = "GridList";
                }
            }
        }
        
        class SpriteCategoryGridState
        {
            public Vector2 scrollPos;
            public int selectedIndex;

            public static SpriteCategoryGridState Default =>
                new SpriteCategoryGridState()
                {
                    scrollPos = Vector2.zero,
                    selectedIndex = 0
                };
        }

        private Style m_Style;
        private SerializedProperty m_Library;
        private List<SpriteCategoryGridState> m_GridStates = new List<SpriteCategoryGridState>();
        private ReorderableList m_LabelReorderableList;

        public SpriteLibraryDataInspector(SerializedObject so,
            SerializedProperty library)
        {
            m_Style = new Style();
            m_Library = library;
            m_LabelReorderableList = new ReorderableList(so, library, true, false,
                true, true);
            m_LabelReorderableList.drawElementCallback = DrawElement;
            m_LabelReorderableList.elementHeight = m_Style.categoryTextFieldHeight;
            m_LabelReorderableList.elementHeightCallback = delegate(int index) { return m_Style.categoryElementHeight; };
            m_LabelReorderableList.onAddCallback = OnAddCallback;
            m_LabelReorderableList.onCanRemoveCallback = OnCanRemoveCallback;
        }

        public static void UpdateLibraryWithNewMainLibrary(SpriteLibraryAsset spriteLib, SerializedProperty library)
        {
            var emptyStringArray = new string[0];
            var categories = spriteLib != null ? spriteLib.GetCategoryNames() : emptyStringArray;
            
            // populate new primary
            int newCatgoryIndex = 0;
            foreach (var newCategory in categories)
            {
                SerializedProperty existingCategory = null;
                if (library.arraySize > 0)
                {
                    var cat = library.GetArrayElementAtIndex(0);
                    for (int i = 0; i < library.arraySize; ++i)
                    {
                        if (cat.FindPropertyRelative(SpriteLibraryPropertyString.name).stringValue == newCategory)
                        {
                            existingCategory = cat;
                            if(i != newCatgoryIndex)
                                library.MoveArrayElement(i, newCatgoryIndex);
                            break;
                        }
                        cat.Next(false);
                    }
                }

                if (existingCategory != null)
                {
                    if(!existingCategory.FindPropertyRelative(SpriteLibraryPropertyString.fromMain).boolValue)
                        existingCategory.FindPropertyRelative(SpriteLibraryPropertyString.fromMain).boolValue = true;
                }
                else
                {
                    library.InsertArrayElementAtIndex(newCatgoryIndex);
                    existingCategory = library.GetArrayElementAtIndex(newCatgoryIndex);
                    SetPropertyName(existingCategory, newCategory);
                    existingCategory.FindPropertyRelative(SpriteLibraryPropertyString.fromMain).boolValue = true;
                    existingCategory.FindPropertyRelative(SpriteLibraryPropertyString.overrideEntryCount).intValue = 0;
                    existingCategory.FindPropertyRelative(SpriteLibraryPropertyString.overrideEntries).arraySize = 0;
                }
                newCatgoryIndex++;
                
                var newEntries = spriteLib.GetCategoryLabelNames(newCategory);
                var entries = existingCategory.FindPropertyRelative(SpriteLibraryPropertyString.overrideEntries);
                int newEntryIndex = 0;
                foreach (var newEntry in newEntries)
                {
                    SerializedProperty cacheEntry = null;
                    if (entries.arraySize > 0)
                    {
                        var ent = entries.GetArrayElementAtIndex(0);
                        for (int j = 0; j < entries.arraySize; ++j)
                        {
                            if (ent.FindPropertyRelative(SpriteLibraryPropertyString.name).stringValue == newEntry)
                            {
                                cacheEntry = ent;
                                if(j != newEntryIndex)
                                    entries.MoveArrayElement(j, newEntryIndex);
                                break;
                            }
                            ent.Next(false);
                        }
                    }
                    var mainSprite = spriteLib.GetSprite(newCategory, newEntry);
                    if (cacheEntry == null)
                    {
                        entries.InsertArrayElementAtIndex(newEntryIndex);
                        cacheEntry = entries.GetArrayElementAtIndex(newEntryIndex);
                        SetPropertyName(cacheEntry, newEntry);
                        cacheEntry.FindPropertyRelative(SpriteLibraryPropertyString.spriteOverride)
                            .objectReferenceValue = mainSprite;
                    }
                    
                    ++newEntryIndex;
                    if(!cacheEntry.FindPropertyRelative(SpriteLibraryPropertyString.fromMain).boolValue)
                        cacheEntry.FindPropertyRelative(SpriteLibraryPropertyString.fromMain).boolValue = true;
                    if(cacheEntry.FindPropertyRelative(SpriteLibraryPropertyString.sprite).objectReferenceValue != mainSprite)
                        cacheEntry.FindPropertyRelative(SpriteLibraryPropertyString.sprite).objectReferenceValue = mainSprite;
                }
            }
                
            // Remove any library or entry that is not in primary and not overridden
            for (int i = 0; i < library.arraySize; ++i)
            {
                var categoryProperty = library.GetArrayElementAtIndex(i);
                var categoryEntriesProperty = categoryProperty.FindPropertyRelative(SpriteLibraryPropertyString.overrideEntries);
                var categoryFromMainProperty = categoryProperty.FindPropertyRelative(SpriteLibraryPropertyString.fromMain);
                
                var categoryName = categoryProperty.FindPropertyRelative(SpriteLibraryPropertyString.name).stringValue;
                var categoryInPrimary = categories.Contains(categoryName);
                var entriesInPrimary = categoryInPrimary ? spriteLib.GetCategoryLabelNames(categoryName) : emptyStringArray;

                var categoryOverride = 0;
                for (int j = 0; j < categoryEntriesProperty.arraySize; ++j)
                {
                    var entry = categoryEntriesProperty.GetArrayElementAtIndex(j);
                    var entryName = entry.FindPropertyRelative(SpriteLibraryPropertyString.name).stringValue;
                    var entryInPrimary = entriesInPrimary.Contains(entryName);
                    var entryFromMainProperty = entry.FindPropertyRelative(SpriteLibraryPropertyString.fromMain);
                    var overrideSpriteProperty = entry.FindPropertyRelative(SpriteLibraryPropertyString.spriteOverride);
                    var spriteProperty = entry.FindPropertyRelative(SpriteLibraryPropertyString.sprite);
                    if (!entryInPrimary)
                    {
                        // Entry no longer in new primary.
                        // Check for override and set it to us
                        if (entryFromMainProperty.boolValue)
                        {
                            if (overrideSpriteProperty.objectReferenceValue == spriteProperty.objectReferenceValue)
                            {
                                categoryEntriesProperty.DeleteArrayElementAtIndex(j);
                                --j;
                                continue;
                            }                            
                        }


                        if(entryFromMainProperty.boolValue)
                            entryFromMainProperty.boolValue = false;
                        if(spriteProperty.objectReferenceValue != overrideSpriteProperty.objectReferenceValue)
                            spriteProperty.objectReferenceValue = overrideSpriteProperty.objectReferenceValue;
                        ++categoryOverride;
                    }
                    else
                    {
                        // Check if sprite has been override
                        if(spriteProperty.objectReferenceValue != overrideSpriteProperty.objectReferenceValue)
                            ++categoryOverride;    
                    }
                    
                }

                if (!categoryInPrimary && categoryEntriesProperty.arraySize == 0 && categoryFromMainProperty.boolValue)
                {
                    library.DeleteArrayElementAtIndex(i);
                    --i;
                    continue;
                }
                // since there is override, and we removed the main. This category now
                // belows to the library
                if (!categoryInPrimary)
                {
                    if(categoryFromMainProperty.boolValue)
                        categoryFromMainProperty.boolValue = false;
                }
                else
                {
                    if(categoryProperty.FindPropertyRelative(SpriteLibraryPropertyString.overrideEntryCount).intValue != categoryOverride)
                        categoryProperty.FindPropertyRelative(SpriteLibraryPropertyString.overrideEntryCount).intValue = categoryOverride;   
                }
            }
        }
        
        public void OnGUI()
        {
            m_Style.InitStyle(Event.current);
            m_LabelReorderableList.DoLayoutList();
            HandleCategoryCreateSpriteDragAndDrop();
        }

        void HandleCategoryCreateSpriteDragAndDrop()
        {
            bool dragAccepted = false;
            switch (Event.current.type)
            {
                case EventType.DragPerform:
                    foreach (var obj in DragAndDrop.objectReferences)
                    {
                        if (obj is Sprite || obj is Texture2D)
                        {
                            dragAccepted = true;
                        }
                    }

                    if (dragAccepted)
                    {
                        DragAndDrop.AcceptDrag();
                        foreach (var obj in DragAndDrop.objectReferences)
                        {
                            if (obj is Sprite)
                            {
                                var category = AddCategory(obj.name, false);
                                AddSpriteToCategory(category, (Sprite)obj);
                            }
                            else if (obj is Texture2D)
                            {
                                var sprites = AssetDatabase.LoadAllAssetsAtPath(AssetDatabase.GetAssetPath(obj))
                                    .Where(x => x is Sprite);
                                foreach (var s in sprites)
                                {
                                    var category = AddCategory(s.name, false);   
                                    AddSpriteToCategory(category, (Sprite)s);
                                }
                            }
                        }
                    }
                    Event.current.Use();
                    break;
                case EventType.DragUpdated:
                    if (DragAndDrop.objectReferences.Count(x => x is Sprite || x is Texture2D) > 0)
                        DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
                    else
                        DragAndDrop.visualMode = DragAndDropVisualMode.Rejected;
                    Event.current.Use();
                    break;
            }
        }

        SerializedProperty IsCategoryNameInUsed(string name)
        {
            if (m_Library.arraySize == 0)
                return null;
            var nameHash = SpriteLibraryUtility.GetStringHash(name);
            var sp = m_Library.GetArrayElementAtIndex(0);
            for (int i = 0; i < m_Library.arraySize; ++i)
            {
                var nameProperty = sp.FindPropertyRelative(SpriteLibraryPropertyString.name).stringValue;
                var hash = SpriteLibraryUtility.GetStringHash(nameProperty);
                if (nameProperty == name || nameHash == hash)
                    return sp;
                sp.Next(false);
            }

            return null;
        }
        
        void DrawElement(Rect rect, int index, bool selected, bool focused)
        {
            DrawCategory(rect, index);
        }

        void DrawCategory(Rect rect, int index)
        {
            var categorySerializedProperty = m_Library.GetArrayElementAtIndex(index);

            var catRect = new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight);
            var vaRect = new Rect(rect.x, rect.y + m_Style.categoryTextFieldHeight, rect.width, EditorGUIUtility.singleLineHeight);

            var categoryName = categorySerializedProperty.FindPropertyRelative(SpriteLibraryPropertyString.name).stringValue;
            var fromMain = categorySerializedProperty.FindPropertyRelative(SpriteLibraryPropertyString.fromMain).boolValue;
            using (new EditorGUI.DisabledScope(fromMain))
            {
                EditorGUI.BeginChangeCheck();
                var oldLabelWidth = EditorGUIUtility.labelWidth;
                EditorGUIUtility.labelWidth = m_Style.categoryLabelWidth;
                var newCatName = EditorGUI.TextField(catRect, m_Style.categoryLabel, categoryName);
                EditorGUIUtility.labelWidth = oldLabelWidth;
                if (EditorGUI.EndChangeCheck())
                {
                    newCatName = newCatName.Trim();
                    if (categoryName != newCatName)
                    {
                        // Check if this nameLabel is already taken
                        if (IsCategoryNameInUsed(newCatName) == null)
                            SetPropertyName(categorySerializedProperty, newCatName);
                        else
                            Debug.LogWarning(m_Style.duplicateWarningText.text);
                    }
                }
            }
            
            while(m_GridStates.Count <= index)
                m_GridStates.Add(new SpriteCategoryGridState());
            
            DrawCategorySpriteListHeader(vaRect, m_Style.newEntryText);
            vaRect.y += vaRect.height;
            vaRect.height = m_Style.gridHeight;
            DrawCategorySpriteList(vaRect, index, categorySerializedProperty);

            HandleSpriteDragAndDropToCategory(vaRect, categorySerializedProperty);            
        }

        void OnAddCallback(ReorderableList list)
        {
            AddCategory(m_Style.newCategoryText, true);
        }

        SerializedProperty AddCategory(string categoryName, bool createNew)
        {
            var intendedCategoryName = categoryName;
            int catNameIncrement = 1;
            while (true)
            {
                var catOverride = IsCategoryNameInUsed(categoryName);
                if (catOverride != null)
                {
                    if (!createNew)
                        return catOverride;
                    categoryName = string.Format("{0} {1}", intendedCategoryName, catNameIncrement++);   
                }
                else
                    break;
            }

            var oldSize = m_Library.arraySize;
            m_Library.arraySize += 1;
            var sp = m_Library.GetArrayElementAtIndex(oldSize);
            SetPropertyName(sp, categoryName);
            sp.FindPropertyRelative(SpriteLibraryPropertyString.fromMain).boolValue = false;
            sp.FindPropertyRelative(SpriteLibraryPropertyString.overrideEntries).arraySize = 0;
            return sp;
        }
        
        bool HandleSpriteDragAndDropToCategory(Rect rect, SerializedProperty category)
        {
            bool dragAccepted = false;
            if (rect.Contains(Event.current.mousePosition))
            {
                switch (Event.current.type)
                {
                    case EventType.DragPerform:
                        foreach (var obj in DragAndDrop.objectReferences)
                        {
                            if (obj is Sprite)
                            {
                                AddSpriteToCategory(category, (Sprite)obj);
                                dragAccepted = true;
                            }
                        }

                        if (dragAccepted)
                        {
                            DragAndDrop.AcceptDrag();
                        }
                        Event.current.Use();
                        break;
                    case EventType.DragUpdated:
                        if (DragAndDrop.objectReferences.Count(x => x is Sprite) > 0)
                            DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
                        else
                            DragAndDrop.visualMode = DragAndDropVisualMode.Rejected;
                        Event.current.Use();
                        break;
                }
            }

            return dragAccepted;
        }
        
        static void GetRowColumnCount(float drawWidth, float size, int contentCount, out int column, out int row, out float columnf)
        {
            columnf = (drawWidth) / size;
            column = (int)Mathf.Floor(columnf);
            if (column == 0)
                row = 0;
            else
                row = (int)Mathf.Ceil((contentCount + column-1) / column);
        }

        void DrawCategorySpriteList(Rect rect, int index, SerializedProperty category)
        {
            var spriteListProp = category.FindPropertyRelative(SpriteLibraryPropertyString.overrideEntries);
            var footerRect = rect;
            var gridState = m_GridStates[index];
            gridState.selectedIndex = (gridState.selectedIndex > spriteListProp.arraySize) ? 0 : gridState.selectedIndex;
            rect.height -= m_Style.gridFooterSize;
            if(Event.current.type == EventType.Repaint)
                m_Style.boxBackground.Draw(rect, "", false, false, false, false);
            
            rect.x += m_Style.gridPadding;
            rect.y += m_Style.gridPadding;
            rect.width -= m_Style.gridPadding * 2;
            rect.height -= m_Style.gridPadding * 2;

            float columnF;
            int columnCount, rowCount;
            GetRowColumnCount(rect.width, m_Style.gridSize.x, spriteListProp.arraySize, out columnCount, out rowCount, out columnF);
            bool canRemoveSelectedEntry = true;
            bool selectedEntryIsOverwrite = false;
            
            var overrideCountProperty = category.FindPropertyRelative(SpriteLibraryPropertyString.overrideEntryCount);
            if (spriteListProp.arraySize <= 0 && overrideCountProperty.intValue != 0)
                overrideCountProperty.intValue = 0;
            if (columnCount > 0 && rowCount > 0)
            {
                var spriteOverwrite = 0;
                var scrollViewRect = new Rect(rect.x, rect.y, columnCount * m_Style.gridSize.x,
                    rowCount * m_Style.gridSize.y);
                if (rowCount >= 2)
                    gridState.scrollPos = GUI.BeginScrollView(new Rect(rect.x, rect.y, rect.width, rect.height),gridState.scrollPos, scrollViewRect, GUI.skin.horizontalScrollbar, GUI.skin.verticalScrollbar);

                var viewPortMinY = rect.y + gridState.scrollPos.y;
                var viewPortMaxY = rect.y + rect.height + gridState.scrollPos.y;
                
                var spriteObjectFieldRect = new Rect(rect.x, rect.y, m_Style.spriteGridSize, m_Style.spriteGridSize);
                var labelTextfieldRect =  new Rect(rect.x, rect.y + m_Style.spriteGridSize + m_Style.lineSpacing, m_Style.spriteGridSize, EditorGUIUtility.singleLineHeight);
                var backgroundSelectedRect = new Rect(rect.x, rect.y, m_Style.gridSize.x, m_Style.gridSize.y);
                for (int i = 0, row = 0, column = 0; i < spriteListProp.arraySize; ++i, ++column)
                {
                    if (column >= columnCount)
                    {
                        column = 0;
                        ++row;
                    }
                    
                    backgroundSelectedRect.x = column * m_Style.gridSize.x + rect.x;
                    backgroundSelectedRect.y = row * m_Style.gridSize.y + rect.y;
                    spriteObjectFieldRect.x = column * m_Style.gridSize.x + rect.x + m_Style.gridPadding;
                    spriteObjectFieldRect.y = row * m_Style.gridSize.y + rect.y + m_Style.gridPadding;
                    labelTextfieldRect.x = column * m_Style.gridSize.x +rect.x + m_Style.gridPadding;
                    labelTextfieldRect.y = row * m_Style.gridSize.y + m_Style.spriteGridSize +rect.y + m_Style.gridPadding;
                    
                    var element = spriteListProp.GetArrayElementAtIndex(i);

                    var spriteProperty = element.FindPropertyRelative(SpriteLibraryPropertyString.sprite);
                    var spriteOverrideProperty = element.FindPropertyRelative(SpriteLibraryPropertyString.spriteOverride);
                    var spriteOverride = spriteProperty.objectReferenceValue != spriteOverrideProperty.objectReferenceValue;
                    var entryFromMain = element.FindPropertyRelative(SpriteLibraryPropertyString.fromMain).boolValue;
                    
                    if (!entryFromMain || spriteOverride)
                        ++spriteOverwrite;
                    
                    if(!IsLabelVisible(viewPortMinY, viewPortMaxY, backgroundSelectedRect))
                        continue;

                    DrawLabel(i, 
                        spriteListProp, 
                        spriteOverrideProperty,
                        gridState, 
                        spriteOverride,
                        entryFromMain,
                        backgroundSelectedRect,
                        spriteObjectFieldRect,
                        labelTextfieldRect,
                        ref canRemoveSelectedEntry,
                        ref selectedEntryIsOverwrite);
                } 
                
                if (overrideCountProperty.intValue != spriteOverwrite)
                {
                    overrideCountProperty.intValue = spriteOverwrite;
                    GUI.changed = true;
                }                
                if (rowCount >= 2)
                    GUI.EndScrollView();
            }
            
            
            footerRect = new Rect(footerRect.x, footerRect.y + footerRect.height - m_Style.gridFooterSize, footerRect.width, m_Style.gridFooterSize);

            Action<SerializedProperty , SpriteCategoryGridState> removeCallback = DeleteSpriteEntry;
            if (selectedEntryIsOverwrite)
                removeCallback = DeleteSpriteEntryOverride;
            DrawCategorySpriteListFooter(footerRect, category, gridState,canRemoveSelectedEntry, removeCallback);
        }

        static bool IsLabelVisible(float viewPortMinY, float viewPortMaxY, Rect labelRect)
        {
            return (labelRect.y + labelRect.height) >= viewPortMinY && labelRect.y <= viewPortMaxY;
        }

        void DrawLabel(int index, 
            SerializedProperty spriteListProp, 
            SerializedProperty spriteOverrideProperty,
            SpriteCategoryGridState gridState,
            bool spriteOverride,
            bool entryFromMain,
            Rect backgroundSelectedRect,
            Rect spriteObjectFieldRect,
            Rect labelTextfieldRect,
            ref bool canRemoveSelectedEntry,
            ref bool selectedEntryIsOverwrite)
        {
            var element = spriteListProp.GetArrayElementAtIndex(index);

            if (gridState.selectedIndex == index)
            {
                canRemoveSelectedEntry = entryFromMain && spriteOverride || !entryFromMain;
                selectedEntryIsOverwrite = entryFromMain && spriteOverride;
                if(Event.current.type == EventType.Repaint)
                    m_Style.gridList.Draw(backgroundSelectedRect, true, true, true, false);
            }
            spriteOverrideProperty.objectReferenceValue = EditorGUI.ObjectField(spriteObjectFieldRect, spriteOverrideProperty.objectReferenceValue, typeof(Sprite), false) as Sprite;
            if (Event.current.type == EventType.MouseUp &&
                backgroundSelectedRect.Contains(Event.current.mousePosition))
            {
                gridState.selectedIndex = index;
            }

            if (!entryFromMain || spriteOverride)
            {
                var overrideIconRect = spriteObjectFieldRect;
                overrideIconRect.x -= 12;
                overrideIconRect.y -= 12;
                overrideIconRect.width = 20;
                overrideIconRect.height = 20;
                GUI.Label(overrideIconRect, m_Style.overrideIcon);
            }

            //disable m_Name editing if the entry is from main
            using (new EditorGUI.DisabledScope(entryFromMain))
            {
                EditorGUI.BeginChangeCheck();
                var oldName = element.FindPropertyRelative(SpriteLibraryPropertyString.name).stringValue;
                if (string.IsNullOrEmpty(oldName) && spriteOverrideProperty.objectReferenceValue != null && entryFromMain)
                {
                    oldName = spriteOverrideProperty.name;
                    SetPropertyName(element, oldName);
                }
                var nameRect = labelTextfieldRect;
                bool nameDuplicate = IsEntryNameUsed(oldName, spriteListProp, 1);
                if (nameDuplicate)
                {
                    nameRect.width -= 20;
                }
            
                var newName = EditorGUI.TextField(
                    nameRect,
                    GUIContent.none, 
                    oldName);

                if (nameDuplicate)
                {
                    nameRect.x += nameRect.width;
                    nameRect.width = 20;
                    GUI.Label(nameRect, m_Style.duplicateWarning);
                }
                if (EditorGUI.EndChangeCheck() && !string.IsNullOrEmpty(newName))
                {
                    newName = newName.Trim();
                    SetPropertyName(element, newName);
                }   
            }            
        }

        bool IsEntryNameUsed(string name, SerializedProperty spriteList, int duplicateAllow)
        {
            if (spriteList.arraySize == 0)
                return false;
            var sp = spriteList.GetArrayElementAtIndex(0);
            var nameHash = SpriteLibraryUtility.GetStringHash(name);
            int count = 0;
            for (int i = 0; i < spriteList.arraySize; ++i, sp.Next(false))
            {
                var stringValue = sp.FindPropertyRelative(SpriteLibraryPropertyString.name).stringValue;
                var hash = SpriteLibraryUtility.GetStringHash(stringValue);
                if (stringValue == name || hash == nameHash)
                {
                    ++count;
                    if(count > duplicateAllow)
                        return true;
                }
            }

            return false;
        }
        
        static void DeleteSpriteEntryOverride(SerializedProperty spriteList, SpriteCategoryGridState gridState)
        {
            var sp = spriteList.GetArrayElementAtIndex(gridState.selectedIndex);
            sp.FindPropertyRelative(SpriteLibraryPropertyString.spriteOverride).objectReferenceValue = sp.FindPropertyRelative(SpriteLibraryPropertyString.sprite).objectReferenceValue;
        }
        
        static void DeleteSpriteEntry(SerializedProperty spriteList, SpriteCategoryGridState gridState)
        {
            spriteList.DeleteArrayElementAtIndex(gridState.selectedIndex);
            var count = spriteList.arraySize;
            gridState.selectedIndex = (gridState.selectedIndex >= count) ? count - 1 : gridState.selectedIndex;
        }
        
        void DrawCategorySpriteListHeader(Rect rect, string text)
        {
            if (Event.current.type == UnityEngine.EventType.Repaint)
                m_Style.headerBackground.Draw(rect, false, false, false, false);
            rect.x += 10;
            EditorGUI.LabelField(rect, text);
        }
        
        void DrawCategorySpriteListFooter(Rect rect, SerializedProperty category, SpriteCategoryGridState gridState, bool canRemove, Action<SerializedProperty, SpriteCategoryGridState> onRemove)
        {
            float num = rect.xMax - 10f;
            float x = num - 8f;
            x -= 25f;
            x -= 25f;
            rect = new Rect(x, rect.y, num - x, rect.height);
            Rect rect1 = new Rect(x + 4f, rect.y, 25f, 16f);
            Rect position = new Rect(num - 29f, rect.y, 25f, 16f);

            
            if (Event.current.type == UnityEngine.EventType.Repaint)
                m_Style.footerBackground.Draw(rect, false, false, false, false);
            
            if (GUI.Button(rect1, m_Style.iconToolbarPlus, m_Style.preButton)) 
                AddSpriteToCategory(category, null);

            var spriteList = category.FindPropertyRelative(SpriteLibraryPropertyString.overrideEntries);
            using (new EditorGUI.DisabledScope(!canRemove || gridState.selectedIndex < 0 || spriteList.arraySize <= gridState.selectedIndex))
            {
                if (GUI.Button(position, m_Style.iconToolbarMinus, m_Style.preButton))
                    onRemove(spriteList, gridState);
            }
        }

        string GetUniqueEntryName(SerializedProperty list,  string name)
        {
            var newName = name;
            var count = 0;
            while (IsEntryNameUsed(newName, list, 0))
            {
                newName = string.Format("{0}_{1}", name, count);
                ++count;
            }
            return newName;
        }
        
        void AddSpriteToCategory(SerializedProperty category, Sprite sprite)
        {
            var name = sprite == null ? m_Style.newEntryText : sprite.name;
            var spriteList = category.FindPropertyRelative(SpriteLibraryPropertyString.overrideEntries);
            name = GetUniqueEntryName(spriteList, name);
            spriteList.arraySize += 1;
            var sp = spriteList.GetArrayElementAtIndex(spriteList.arraySize - 1);
            SetPropertyName(sp, name);
            sp.FindPropertyRelative(SpriteLibraryPropertyString.sprite).objectReferenceValue = sprite;
            sp.FindPropertyRelative(SpriteLibraryPropertyString.spriteOverride).objectReferenceValue = sprite;
            sp.FindPropertyRelative(SpriteLibraryPropertyString.fromMain).boolValue = false;
        }

        static void SetPropertyName(SerializedProperty sp, string newName)
        {
            sp.FindPropertyRelative(SpriteLibraryPropertyString.name).stringValue = newName;
            sp.FindPropertyRelative(SpriteLibraryPropertyString.hash).intValue = SpriteLibraryUtility.GetStringHash(newName);
        }
        
        bool OnCanRemoveCallback(ReorderableList list)
        {
            bool canDelete = true;
            if (list.index >= 0 && list.index < list.count)
            {
                var item = list.serializedProperty.GetArrayElementAtIndex(list.index);
                canDelete = !item.FindPropertyRelative(SpriteLibraryPropertyString.fromMain).boolValue;
            }

            return canDelete;
        }

        public static int GetSpriteLibraryEntryCount(SerializedProperty library, int min = 100)
        {
            if (library == null || library.arraySize == 0)
                return 0;

            var entryCount = 0;
            var categoryCount = library.arraySize;
            for (var i = 0; i < categoryCount; i++)
            {
                var category = library.GetArrayElementAtIndex(i);
                if (category != null)
                {
                    var entries = category.FindPropertyRelative(SpriteLibraryPropertyString.overrideEntries);
                    if (entries != null)
                        entryCount += entries.arraySize;
                }
            }

            return Math.Max(entryCount, min);
        }
    }
}