using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.U2D.Sprites;
using UnityEditorInternal;
using UnityEngine;
using UnityEngine.UIElements;
using Object = UnityEngine.Object;

namespace UnityEditor.U2D.Common
{
    internal class ImagePackerDebugEditor : EditorWindow
    {
        [MenuItem("internal:Window/2D/Common/Image Packer Debug Editor")]
        static void Launch()
        {
            var window = EditorWindow.GetWindow<ImagePackerDebugEditor>();
            var pos = window.position;
            pos.height = pos.width = 400;
            window.position = pos;
            window.Show();
        }

        ReorderableList m_ReorderableList;
        ImagePacker.ImagePackRect[] m_PackingRect = null;
        List<RectInt> m_PackRects = new List<RectInt>();
        RectInt[] m_PackResult = null;
        SpriteRect[] m_SpriteRects = null;
        Texture2D m_Texture;
        int m_TextureActualWidth = 0;
        int m_TextureActualHeight = 0;
        int m_PackWidth = 0;
        int m_PackHeight = 0;
        int m_Padding = 0;
        Vector2 m_ConfigScroll = Vector2.zero;
        float m_Zoom = 1;
        IMGUIContainer m_PackArea;
        int m_PackStep = -1;
        protected const float k_MinZoomPercentage = 0.9f;
        protected const float k_WheelZoomSpeed = 0.03f;
        protected const float k_MouseZoomSpeed = 0.005f;

        void OnEnable()
        {
            var visualContainer = new VisualElement()
            {
                name = "Container",
                style =
                {
                    flexGrow = 1,
                    flexDirection = FlexDirection.Row
                }
            };
            this.rootVisualElement.Add(visualContainer);

            var imgui = new IMGUIContainer(OnConfigGUI)
            {
                name = "Config",
                style =
                {
                    width = 300
                }
            };

            visualContainer.Add(imgui);

            m_PackArea = new IMGUIContainer(OnImagePackerGUI)
            {
                name = "ImagePacker",
                style =
                {
                    flexGrow = 1,
                }
            };
            visualContainer.Add(m_PackArea);
            SetupConfigGUI();
        }

        void SetupConfigGUI()
        {
            m_ReorderableList = new ReorderableList(m_PackRects, typeof(RectInt), false, false, true, true);
            m_ReorderableList.elementHeightCallback = (int index) =>
                {
                    return EditorGUIUtility.singleLineHeight * 2 + 6;
                };
            m_ReorderableList.drawElementCallback = DrawListElement;

            m_ReorderableList.onAddCallback = (list) =>
                {
                    m_PackRects.Add(new RectInt());
                };
            m_ReorderableList.onRemoveCallback = (list) =>
                {
                    m_PackRects.RemoveAt(list.index);
                };
        }

        void DrawListElement(Rect rect, int index, bool isactive, bool isfocused)
        {
            var rectInt = m_PackRects[index];
            var name = m_SpriteRects == null || index >= m_SpriteRects.Length ? index.ToString() : m_SpriteRects[index].
                name;
            rectInt.size = EditorGUI.Vector2IntField(rect, name, rectInt.size);
            m_PackRects[index] = rectInt;
        }

        void OnConfigGUI()
        {
            EditorGUILayout.BeginVertical();
            m_ConfigScroll = EditorGUILayout.BeginScrollView(m_ConfigScroll);
            m_ReorderableList.DoLayoutList();
            EditorGUILayout.EndScrollView();
            GUILayout.FlexibleSpace();

            m_PackStep = EditorGUILayout.IntSlider("Step", m_PackStep, 0, m_PackRects.Count);
            EditorGUI.BeginChangeCheck();
            m_Texture = EditorGUILayout.ObjectField(new GUIContent("Texture"), (Object)m_Texture, typeof(Texture2D), false) as Texture2D;
            if (EditorGUI.EndChangeCheck())
                UpdateSpriteRect();
            m_Padding = EditorGUILayout.IntField("Padding", m_Padding);
            EditorGUILayout.BeginHorizontal();
            if (GUILayout.Button("<<"))
            {
                m_PackStep = m_PackStep <= 0 ? 0 : m_PackStep - 1;
                Pack();
            }
            if (GUILayout.Button("Pack"))
                Pack();
            if (GUILayout.Button(">>"))
            {
                m_PackStep = m_PackStep > m_PackRects.Count ? m_PackRects.Count : m_PackStep + 1;
                Pack();
            }
            if (GUILayout.Button("Clear"))
            {
                m_PackRects.Clear();
                m_Texture = null;
                m_PackingRect = null;
                m_PackResult = null;
                m_SpriteRects = null;
            }

            EditorGUILayout.EndHorizontal();
            EditorGUILayout.EndVertical();
        }

        void UpdateSpriteRect()
        {
            var dataProvider = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(m_Texture)) as ISpriteEditorDataProvider;
            if (dataProvider == null)
                return;
            dataProvider.InitSpriteEditorDataProvider();
            dataProvider.GetDataProvider<ITextureDataProvider>().GetTextureActualWidthAndHeight(out m_TextureActualWidth, out m_TextureActualHeight);
            m_SpriteRects = dataProvider.GetDataProvider<ISpriteEditorDataProvider>().GetSpriteRects();
            m_PackRects.Clear();
            m_PackRects.AddRange(m_SpriteRects.Select(x => new RectInt((int)x.rect.x, (int)x.rect.y, (int)x.rect.width, (int)x.rect.height)));
            m_PackResult = null;
            m_PackStep = m_PackRects.Count;
        }

        void Pack()
        {
            int count = m_PackStep > 0 && m_PackStep < m_PackRects.Count ? m_PackStep : m_PackRects.Count;
            m_PackingRect = new ImagePacker.ImagePackRect[m_PackRects.Count];
            for (int i = 0; i < m_PackRects.Count; ++i)
            {
                m_PackingRect[i] = new ImagePacker.ImagePackRect()
                {
                    rect = m_PackRects[i],
                    index = i
                };
            }
            Array.Sort(m_PackingRect);
            ImagePacker.Pack(m_PackingRect.Take(count).Select(x => x.rect).ToArray(), m_Padding, out m_PackResult, out m_PackWidth, out m_PackHeight);
        }

        void DrawLabel(Rect rect, string label)
        {
            rect.position = Handles.matrix.MultiplyPoint(rect.position);
            GUI.Label(rect, label);
        }

        void OnImagePackerGUI()
        {
            if (m_PackResult == null)
                return;
            HandleZoom();
            var oldMatrix = Handles.matrix;
            SetupHandlesMatrix();
            Handles.DrawSolidRectangleWithOutline(new Rect(0, 0, m_PackWidth, m_PackHeight), Color.gray, Color.black);
            DrawLabel(new Rect(0, 0, m_PackWidth, m_PackHeight), m_PackWidth + "x" + m_PackHeight);

            int index = 0;

            foreach (var rect in m_PackResult)
            {
                Handles.DrawSolidRectangleWithOutline(new Rect(rect.x, rect.y, rect.width, rect.height), Color.white, Color.black);
                var rect1 = new Rect(rect.x, rect.y + rect.height * 0.5f, rect.width, EditorGUIUtility.singleLineHeight);
                DrawLabel(rect1, m_PackingRect[index].index.ToString());
                ++index;
            }

            index = 0;
            if (m_Texture != null && m_SpriteRects != null)
            {
                var material = new Material(Shader.Find("Sprites/Default"));
                material.mainTexture = m_Texture;
                material.SetPass(0);

                int mouseOverIndex = -1;
                GL.PushMatrix();
                GL.LoadIdentity();
                GL.MultMatrix(GUI.matrix * Handles.matrix);
                GL.Begin(GL.QUADS);
                for (int i = 0; i < m_PackResult.Length; ++i)
                {
                    index = m_PackingRect[i].index;
                    if (index >= m_SpriteRects.Length)
                        continue;
                    var rect = m_PackResult[i];
                    GL.TexCoord(new Vector3(m_SpriteRects[index].rect.x / m_TextureActualWidth, m_SpriteRects[index].rect.y / m_TextureActualHeight, 0));
                    GL.Vertex(new Vector3(rect.x, rect.y, 0));
                    GL.TexCoord(new Vector3(m_SpriteRects[index].rect.xMax / m_TextureActualWidth, m_SpriteRects[index].rect.y / m_TextureActualHeight, 0));
                    GL.Vertex(new Vector3(rect.x + rect.width, rect.y, 0));
                    GL.TexCoord(new Vector3(m_SpriteRects[index].rect.xMax / m_TextureActualWidth, m_SpriteRects[index].rect.yMax / m_TextureActualHeight, 0));
                    GL.Vertex(new Vector3(rect.x + rect.width, rect.y + rect.height, 0));
                    GL.TexCoord(new Vector3(m_SpriteRects[index].rect.x / m_TextureActualWidth, m_SpriteRects[index].rect.yMax / m_TextureActualHeight, 0));
                    GL.Vertex(new Vector3(rect.x, rect.y + rect.height, 0));
                    var m = Handles.matrix.inverse.MultiplyPoint(Event.current.mousePosition);
                    if (rect.Contains(new Vector2Int((int)m.x, (int)m.y)))
                    {
                        mouseOverIndex = index;
                    }
                    ++index;
                }

                GL.End();
                GL.PopMatrix();
                if (mouseOverIndex >= 0)
                {
                    var text = new GUIContent(m_SpriteRects[mouseOverIndex].name + " " + index);
                    var length = EditorStyles.textArea.CalcSize(text);
                    var rect1 = new Rect(m_PackResult[mouseOverIndex].x, m_PackResult[mouseOverIndex].y + m_PackResult[mouseOverIndex].height * 0.5f, length.x, length.y);
                    rect1.position = Handles.matrix.MultiplyPoint(rect1.position);
                    if (Event.current.type == EventType.Repaint)
                        EditorStyles.textArea.Draw(rect1, text, false, false, false, false);
                }
            }

            Handles.matrix = oldMatrix;
        }

        void SetupHandlesMatrix()
        {
            Vector3 handlesPos = new Vector3(0, m_PackHeight * m_Zoom, 0f);
            Vector3 handlesScale = new Vector3(m_Zoom, -m_Zoom, 1f);
            Handles.matrix = Matrix4x4.TRS(handlesPos, Quaternion.identity, handlesScale);
        }

        protected void HandleZoom()
        {
            bool zoomMode = Event.current.alt && Event.current.button == 1;
            if (zoomMode)
            {
                EditorGUIUtility.AddCursorRect(m_PackArea.worldBound, MouseCursor.Zoom);
            }

            if (
                ((Event.current.type == EventType.MouseUp || Event.current.type == EventType.MouseDown) && zoomMode) ||
                ((Event.current.type == EventType.KeyUp || Event.current.type == EventType.KeyDown) && Event.current.keyCode == KeyCode.LeftAlt)
                )
            {
                Repaint();
            }

            if (Event.current.type == EventType.ScrollWheel || (Event.current.type == EventType.MouseDrag && Event.current.alt && Event.current.button == 1))
            {
                float zoomMultiplier = 1f - Event.current.delta.y * (Event.current.type == EventType.ScrollWheel ? k_WheelZoomSpeed : -k_MouseZoomSpeed);

                // Clamp zoom
                float wantedZoom = m_Zoom * zoomMultiplier;

                float currentZoom = Mathf.Clamp(wantedZoom, GetMinZoom(), 1);

                if (currentZoom != m_Zoom)
                {
                    m_Zoom = currentZoom;
                    Event.current.Use();
                }
            }
        }

        protected float GetMinZoom()
        {
            if (m_Texture == null)
                return 1.0f;
            return Mathf.Min(m_PackArea.worldBound.width / m_PackWidth, m_PackArea.worldBound.height / m_PackHeight, 0.05f) * k_MinZoomPercentage;
        }
    }
}