using System;
using UnityEditor.U2D.Common;
using UnityEditorInternal;
using UnityEngine;

namespace UnityEditor.U2D
{
    internal class SpriteSelector
    {
        int m_SelectedSprite = 0;
        Sprite[] m_SpriteList = null;
        Texture[] m_Thumbnails;
        Vector2 m_ScrollPos;
        int m_WindowSize = 0;
        int m_ImageSize = 4; 
        bool m_Inspector = true;

        public bool inspector
        {
            get => m_Inspector;
            set => m_Inspector = value;
        }
        public int imageSize
        {
            get { return m_ImageSize; }
            set { m_ImageSize = value; }
        }
        public int windowSize
        {
            get { return m_WindowSize; }
            set { m_WindowSize = value; }
        }
        public int selectedIndex 
        {
            get { return m_SelectedSprite; } 
            set { m_SelectedSprite = value; }
        }
        public bool hasSprites
        {
            get { return (m_SpriteList != null) && (m_SpriteList.Length != 0); }
        }
        
        internal static class Styles
        {
            public static GUIStyle gridList = "GridList";
            public static GUIContent spriteList = EditorGUIUtility.TrTextContent("Sprite Variant");
            public static GUIContent missingSprites = EditorGUIUtility.TrTextContent("No brushes defined.");
            public static GUIStyle localGrid = null;
        }
        
        public SpriteSelector()
        {
            m_SpriteList = null;
        }

        public void SetCustomSize(int windowSizer, int imageSizer)
        {
            m_WindowSize = windowSizer;
            m_ImageSize = imageSizer;
            m_Inspector = false;
        }
        
        public void ResetSize()
        {
            m_WindowSize = 0;
            m_ImageSize = 64;
            m_Inspector = true;
        }
        
        public void UpdateSprites(Sprite[] sprites)
        {
            m_SpriteList = sprites;
            UpdateSelection(0);
        }

        public void UpdateSelection(int newSelectedBrush)
        {
            m_SelectedSprite = newSelectedBrush;
        }

        private static int CalcTotalHorizSpacing(int xCount, GUIStyle style, GUIStyle firstStyle, GUIStyle midStyle, GUIStyle lastStyle)
        {
            if (xCount < 2)
                return 0;
            if (xCount == 2)
                return Mathf.Max(firstStyle.margin.right, lastStyle.margin.left);

            int internalSpace = Mathf.Max(midStyle.margin.left, midStyle.margin.right);
            int horizSpace = Mathf.Max(firstStyle.margin.right, midStyle.margin.left) + Mathf.Max(midStyle.margin.right, lastStyle.margin.left) + internalSpace * (xCount - 3);
            return horizSpace;
        }

        // Helper function: Get all mouse rects
        private static Rect[] CalcMouseRects(Rect position, GUIContent[] contents, int xCount, float elemWidth, float elemHeight, GUIStyle style, GUIStyle firstStyle, GUIStyle midStyle, GUIStyle lastStyle, bool addBorders, GUI.ToolbarButtonSize buttonSize)
        {
            int count = contents.Length;
            int x = 0;
            float xPos = position.xMin, yPos = position.yMin;
            GUIStyle currentStyle = style;
            Rect[] retval = new Rect[count];
            if (count > 1)
                currentStyle = firstStyle;
            for (int i = 0; i < count; i++)
            {
                float w = 0;
                switch (buttonSize)
                {
                    case GUI.ToolbarButtonSize.Fixed:
                        w = elemWidth;
                        break;
                    case GUI.ToolbarButtonSize.FitToContents:
                        w = currentStyle.CalcSize(contents[i]).x;
                        break;
                }

                if (!addBorders)
                    retval[i] = new Rect(xPos, yPos, w, elemHeight);
                else
                    retval[i] = currentStyle.margin.Add(new Rect(xPos, yPos, w, elemHeight));

                //we round the values to the dpi-aware pixel grid
                retval[i] = GUIUtility.AlignRectToDevice(retval[i]);

                GUIStyle nextStyle = midStyle;
                if (i == count - 2 || i == xCount - 2)
                    nextStyle = lastStyle;

                xPos = retval[i].xMax + Mathf.Max(currentStyle.margin.right, nextStyle.margin.left);

                x++;
                if (x >= xCount)
                {
                    x = 0;
                    yPos += elemHeight + Mathf.Max(style.margin.top, style.margin.bottom);
                    xPos = position.xMin;
                    nextStyle = firstStyle;
                }

                currentStyle = nextStyle;
            }
            return retval;
        }

        static void DrawRectangleOutline(Rect rect, Color color)
        {
            Color currentColor = Handles.color;
            Handles.color = color;

            // Draw viewport outline
            Vector3[] points = new Vector3[5];
            points[0] = new Vector3(rect.x, rect.y, 0.0f);
            points[1] = new Vector3(rect.x + rect.width, rect.y, 0.0f);
            points[2] = new Vector3(rect.x + rect.width, rect.y + rect.height, 0.0f);
            points[3] = new Vector3(rect.x, rect.y + rect.height, 0.0f);
            points[4] = new Vector3(rect.x, rect.y, 0.0f);
            Handles.DrawPolyLine(points);

            Handles.color = currentColor;
        }

        // Make a button grid
        private static int DoButtonGrid(Rect position, int selected, GUIContent[] contents, string[] controlNames, int xCount, GUIStyle style, GUIStyle firstStyle, GUIStyle midStyle, GUIStyle lastStyle, GUI.ToolbarButtonSize buttonSize, bool[] contentsEnabled = null)
        {
            int count = contents.Length;
            if (count == 0)
                return selected;
            if (xCount <= 0)
            {
                Debug.LogWarning("You are trying to create a SelectionGrid with zero or less elements to be displayed in the horizontal direction. Set xCount to a positive value.");
                return selected;
            }

            if (contentsEnabled != null && contentsEnabled.Length != count)
                throw new ArgumentException("contentsEnabled");

            // Figure out how large each element should be
            int rows = count / xCount;
            if (count % xCount != 0)
                rows++;

            float totalHorizSpacing = CalcTotalHorizSpacing(xCount, style, firstStyle, midStyle, lastStyle);
            float totalVerticalSpacing = Mathf.Max(style.margin.top, style.margin.bottom) * (rows - 1);
            float elemWidth = (position.width - totalHorizSpacing) / xCount;
            float elemHeight = (position.height - totalVerticalSpacing) / rows;

            if (style.fixedWidth != 0)
                elemWidth = style.fixedWidth;
            if (style.fixedHeight != 0)
                elemHeight = style.fixedHeight;

            Rect[] buttonRects = CalcMouseRects(position, contents, xCount, elemWidth, elemHeight, style, firstStyle, midStyle, lastStyle, false, buttonSize);
            GUIStyle selectedButtonStyle = null;
            int selectedButtonID = 0;
            for (int buttonIndex = 0; buttonIndex < count; ++buttonIndex)
            {
                bool wasEnabled = GUI.enabled;
                GUI.enabled &= (contentsEnabled == null || contentsEnabled[buttonIndex]);
                var buttonRect = buttonRects[buttonIndex];
                var content = contents[buttonIndex];

                if (controlNames != null)
                    GUI.SetNextControlName(controlNames[buttonIndex]);
                var id = GUIUtility.GetControlID("ButtonGrid".GetHashCode(), FocusType.Passive, buttonRect);
                if (buttonIndex == selected)
                    selectedButtonID = id;

                switch (Event.current.GetTypeForControl(id))
                {
                    case EventType.MouseDown:
                        if (buttonRect.Contains(Event.current.mousePosition))
                        {
                            GUIUtility.hotControl = id;
                            Event.current.Use();
                        }
                        break;
                    case EventType.MouseDrag:
                        if (GUIUtility.hotControl == id)
                            Event.current.Use();
                        break;
                    case EventType.MouseUp:
                        if (GUIUtility.hotControl == id)
                        {
                            GUIUtility.hotControl = 0;
                            Event.current.Use();

                            GUI.changed = true;
                            return buttonIndex;
                        }
                        break;
                    case EventType.Repaint:
                        var buttonStyle = count == 1 ? style : (buttonIndex == 0 ? firstStyle : (buttonIndex == count - 1 ? lastStyle : midStyle));
                        var isSelected = selected == buttonIndex;

                        if (!isSelected)
                        {
                            GUI.DrawTexture(buttonRect, content.image, ScaleMode.ScaleToFit, true);
                            GUI.Label(new Rect(buttonRect.x, buttonRect.y, 32, 32), buttonIndex.ToString());
                        }
                        else
                            selectedButtonStyle = buttonStyle;
                        break;
                }

                GUI.enabled = wasEnabled;
            }

            // draw selected button at the end so it overflows nicer
            if (selectedButtonStyle != null)
            {
                var buttonRect = buttonRects[selected];
                var content = contents[selected];
                var wasEnabled = GUI.enabled;
                GUI.enabled &= (contentsEnabled == null || contentsEnabled[selected]);

                GUI.DrawTexture(new Rect(buttonRect.x + 4, buttonRect.y + 4, buttonRect.width - 8, buttonRect.height - 8), content.image, ScaleMode.ScaleToFit, true);
                DrawRectangleOutline(buttonRect, GUI.skin.settings.selectionColor);
                GUI.Label(new Rect(buttonRect.x, buttonRect.y, 32, 32), selected.ToString());
                GUI.enabled = wasEnabled;
            }

            return selected;
        }

        // Get Temp Texture Contents for Sprites.
        private static GUIContent[] Temp(Texture[] images)
        {
            GUIContent[] retval = new GUIContent[images.Length];
            for (int i = 0; i < images.Length; i++)
            {
                retval[i] = new GUIContent(images[i]);
            }
            return retval;
        }

        public Sprite GetActiveSprite()
        {
            if (m_SelectedSprite >= m_SpriteList.Length)
                m_SelectedSprite = 0;
            return m_SpriteList[m_SelectedSprite];
        }

        public bool ShowGUI(int selectedIndex)
        {
            bool repaint = false;
            if (m_SpriteList == null || m_SpriteList.Length == 0)
                return false;
            int approxSize = 64;
            int approxHolderSize = approxSize + 2;

            if (Styles.localGrid == null)
            {
                Styles.localGrid = new GUIStyle(Styles.gridList);
            }
            // 64, 56, 36, 28
            int[] actualSizes = new int[] {0, 64, 48, 32, 24};
            int clamped = Math.Min(imageSize, 4);
            int actualSize = actualSizes[clamped];
            Styles.localGrid.fixedWidth = actualSize;
            Styles.localGrid.fixedHeight = actualSize;            
            m_SelectedSprite = (selectedIndex > m_SpriteList.Length) ? 0 : selectedIndex;
            int wwidth = m_WindowSize == 0 ? (int)InternalEditorBridge.GetEditorGUILayoutLastRect().width : (int)m_WindowSize;
            int viewwidth = (int)(wwidth - EditorGUIUtility.labelWidth - approxSize);
            int columns = (int)(viewwidth) / actualSize;
            columns = columns <= 0 ? 1 : columns;
            int rows = (int)Mathf.Ceil((m_SpriteList.Length + columns - 1) / columns);
            int lyColumns = inspector ? (approxHolderSize) : approxHolderSize;

            EditorGUILayout.BeginHorizontal("box");
            {
                GUILayout.Label(Styles.spriteList, EditorStyles.label, GUILayout.Width(EditorGUIUtility.labelWidth - 5));

                GUILayout.BeginVertical("box", new GUILayoutOption[] { GUILayout.Height(lyColumns) } );
                {
                    m_ScrollPos = EditorGUILayout.BeginScrollView(m_ScrollPos, new GUILayoutOption[] { GUILayout.Height(lyColumns) });
                    int newBrush = SpriteSelectionGrid(m_SelectedSprite, m_SpriteList, actualSize, Styles.localGrid, Styles.missingSprites, columns, rows);
                    if (newBrush != m_SelectedSprite)
                    {
                        UpdateSelection(newBrush);
                        repaint = true;
                    }
                    EditorGUILayout.EndScrollView();
                }
                GUILayout.EndVertical();
            }
            EditorGUILayout.EndHorizontal();
            EditorGUILayout.Space();
            return repaint;
        }

        int SpriteSelectionGrid(int selected, Sprite[] sprites, int approxSize, GUIStyle style, GUIContent emptyString, int columns, int rows)
        {            
            int retval = 0;
            if (sprites.Length != 0)
            {
                Rect r = GUILayoutUtility.GetRect((float)columns * approxSize, (float)rows * approxSize);
                Event evt = Event.current;
                if (evt.type == EventType.MouseDown && evt.clickCount == 2 && r.Contains(evt.mousePosition))
                    evt.Use();

                m_Thumbnails = PreviewTexturesFromSprites(sprites);
                retval = DoButtonGrid(r, selected, Temp(m_Thumbnails), null, (int)columns, style, style, style, style, GUI.ToolbarButtonSize.Fixed);
            }
            else
                GUILayout.Label(emptyString);
            return retval;
        }

        internal static Texture[] PreviewTexturesFromSprites(Sprite[] sprites)
        {
            Texture[] retval = new Texture[sprites.Length];
            for (int i = 0; i < sprites.Length; i++)
                retval[i] = AssetPreview.GetAssetPreview(sprites[i]) ?? Texture2D.whiteTexture;
            return retval;
        }
    }
} //namespace