/*           INFINITY CODE          */
/*     https://infinity-code.com    */

using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.EditorTools;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;

namespace InfinityCode.TreeTools.Editors
{
    [EditorTool("Tree Tool")]
    public class TreeTool : EditorTool
    {
        internal static int HandleHash = "TreeToolHash".GetHashCode();

        public static GameObject[] meshTargets;

        private static GUIContent activeContent;
        private static bool createUndo = true;
        private static GUIContent passiveContent;
        private static TreeItem[] treeItems;
        private static int selectedIndex = -1;
        private static TreeItem[] visibleItems;
        private static Quaternion lastCameraRotation;
        private static Vector3 lastCameraPosition;

        public static TreeItem activeItem
        {
            get
            {
                if (treeItems == null) return null;
                if (selectedIndex < 0 || selectedIndex >= treeItems.Length) return null;
                return treeItems[selectedIndex];
            }
        }

        public override GUIContent toolbarIcon
        {
            get
            {
#if UNITY_2020_2_OR_NEWER
                if (ToolManager.IsActiveTool(this))
#else
                if (EditorTools.IsActiveTool(this))
#endif
                {
                    if (activeContent == null) activeContent = new GUIContent(EditorGUIUtility.IconContent("TerrainInspector.TerrainToolTrees On").image, "Tree Tool");
                    return activeContent;
                }

                if (passiveContent == null) passiveContent = new GUIContent(EditorGUIUtility.IconContent("TerrainInspector.TerrainToolTrees On").image, "Tree Tool");
                return passiveContent;
            }
        }

        private void DrawMeshTargets(SceneView view)
        {
            if (meshTargets.Length == 0) return;

            Vector3 position = Tools.handlePosition;
            Quaternion rotation = Tools.handleRotation;
            Vector3 newPosition = Handles.PositionHandle(position, rotation);
            Vector3 offset = newPosition - position;
            if (offset.sqrMagnitude > 0)
            {
                UndoEx.RecordObjects(meshTargets.Select(t => (Object)t.transform).ToArray(), "Move GameObjects");
                foreach (GameObject t in meshTargets) t.transform.position += offset;
            }

            MeshTargetToolbar.Draw(view, newPosition);
        }

        private void DrawSelectedItem(SceneView view)
        {
            if (selectedIndex == -1) return;

            TreeItem item = treeItems[selectedIndex];
            Vector3 position = item.position;
            Quaternion rotation = Quaternion.Euler(0, item.rotation, 0);
            Vector3 scale = new Vector3(item.widthScale, item.heightScale, item.widthScale);

            if (Event.current.type == EventType.MouseUp) createUndo = true;

            Vector3 prefPosition = position;

            EditorGUI.BeginChangeCheck();
            Handles.TransformHandle(ref position, rotation, ref scale);
            if (EditorGUI.EndChangeCheck())
            {
                if (Prefs.snapToTerrain && prefPosition != position)
                {
                    position.y = item.SnapY(position);
                }

                if (createUndo)
                {
                    UndoEx.RecordObject(item.terrain.terrainData, "Update Tree");
                    createUndo = false;
                }

                item.Set(position, rotation, scale);
            }

            SelectedTreeToolbar.Draw(view, item, position); 
        }

        public static void FreeItems()
        {
            ResetVisibleItems();

            if (treeItems == null) return;

            foreach (TreeItem item in treeItems) item.Dispose();
            treeItems = null;
        }

        private void InitMeshTargets()
        {
            meshTargets = Selection.gameObjects.Where(g => g.scene.name != null && PrefabUtility.IsAnyPrefabInstanceRoot(g) && g.GetComponent<MeshRenderer>() != null).ToArray();
        }

        public static void InitTreeItems() 
        {
            FreeItems();

            List<TreeItem> items = new List<TreeItem>();

            Terrain[] terrains = FindObjectsOfType<Terrain>();
            foreach (Terrain terrain in terrains)
            {
                if (!terrain.gameObject.activeSelf) continue;

                TerrainData data = terrain.terrainData;
                TreeInstance[] instances = data.treeInstances;

                for (int i = 0; i < instances.Length; i++)
                {
                    TreeInstance instance = instances[i];
                    TreeItem item = new TreeItem(terrain, instance, i, items.Count);
                    items.Add(item);
                }
            }

            treeItems = items.ToArray();
        }

        public override void OnActivated()
        {
            base.OnActivated();

            Undo.undoRedoPerformed -= InitTreeItems;
            Undo.undoRedoPerformed += InitTreeItems;

            Selection.selectionChanged -= OnSelectionChanged;
            Selection.selectionChanged += OnSelectionChanged;

            EditorSceneManager.sceneClosed -= OnSceneClosed;
            EditorSceneManager.sceneClosed += OnSceneClosed;

            InitTreeItems();
            InitMeshTargets();

            Updater.CheckNewVersionAvailable();
        }

        private void OnSceneClosed(Scene scene)
        {
            FreeItems();
            selectedIndex = -1;
            EditorApplication.update += () => 
            {
                InitTreeItems();
                InitMeshTargets();
            };
        }

        private void OnSelectionChanged()
        {
            if (Selection.activeObject == null) return;
            
            InitMeshTargets();
            selectedIndex = -1;
            ResetVisibleItems();
        }

        public override void OnToolGUI(EditorWindow window)
        {
            if (treeItems == null) return;

            SceneView view = window as SceneView;
            if (view == null) return;

            Transform cameraTransform = view.camera.transform;
            Vector3 cameraPosition = cameraTransform.position;
            Quaternion cameraRotation = cameraTransform.rotation;

            ValidateSceneCamera(cameraPosition, cameraRotation);

            Event e = Event.current;
            EventType eventType = e.type;

            float selectionSize = Prefs.selectionSize;
            int nextCandidate = -1;

            Color defColor = Handles.color;

            if (visibleItems == null)
            {
                UpdateVisibleItems(cameraPosition);
            }

            bool noModifiers = e.modifiers == EventModifiers.None;
            bool isMouseDown = eventType == EventType.MouseDown && e.button == 0 && noModifiers;

            for (int i = 0; i < visibleItems.Length; i++)
            {
                TreeItem item = visibleItems[i];
                
                int controlId = GUIUtility.GetControlID(HandleHash, FocusType.Passive);
                bool isActive = HandleUtility.DistanceToRectangle(item.position, cameraRotation, selectionSize) == 0 && GUIUtility.hotControl == 0;
                Handles.color = isActive ? Color.red : Color.white;
                Handles.RectangleHandleCap(controlId, item.position, cameraRotation, selectionSize, eventType);

                if (isMouseDown && isActive) nextCandidate = item.globalIndex;
            }

            Handles.color = defColor;

            if (selectedIndex != -1) DrawSelectedItem(view);
            else if (meshTargets != null) DrawMeshTargets(view);

            if (nextCandidate != -1 && e.type != EventType.Used)
            {
                selectedIndex = nextCandidate;
                createUndo = true;
                ResetVisibleItems();
                Selection.activeGameObject = null;
                e.Use();
            }

            if (e.type == EventType.MouseDown && isMouseDown)
            {
                selectedIndex = -1;
                ResetVisibleItems();
            }
        }

        public override void OnWillBeDeactivated()
        {
            base.OnWillBeDeactivated();

            selectedIndex = -1;
            Undo.undoRedoPerformed -= InitTreeItems;
            Selection.selectionChanged -= OnSelectionChanged;

            FreeItems();
        }

        public static void ResetVisibleItems()
        {
            visibleItems = null;
        }

        public static void SetSelectedIndex(int index)
        {
            selectedIndex = index;
        }

        private void UpdateVisibleItems(Vector3 cameraPosition)
        {
            float sqrDist = Prefs.selectionDistance * Prefs.selectionDistance;
            List<TreeItem> temp = new List<TreeItem>();
            for (int i = 0; i < treeItems.Length; i++)
            {
                TreeItem item = treeItems[i];
                if ((cameraPosition - item.position).sqrMagnitude > sqrDist) continue;
                if (selectedIndex == i) continue;
                temp.Add(item);
            }

            visibleItems = temp.ToArray();
        }

        private void ValidateSceneCamera(Vector3 position, Quaternion rotation)
        {
            if (position != lastCameraPosition)
            {
                visibleItems = null;
                lastCameraPosition = position;
            }

            if (rotation != lastCameraRotation)
            {
                visibleItems = null;
                lastCameraRotation = rotation;
            }
        }
    }
}