using UnityEngine;
using UnityEngine.Tilemaps;
using UnityEditorInternal;
using System.Collections.Generic;

namespace UnityEditor
{
    /// <summary>
    /// The Editor for a RuleOverrideTileEditor.
    /// </summary>
    [CustomEditor(typeof(RuleOverrideTile))]
    public class RuleOverrideTileEditor : Editor
    {
        private static class Styles
        {
            public static readonly GUIContent overrideTile = EditorGUIUtility.TrTextContent("Tile"
                , "The Rule Tile to override.");
        }

        /// <summary>
        /// The RuleOverrideTile being edited
        /// </summary>
        public RuleOverrideTile overrideTile => target as RuleOverrideTile;
        /// <summary>
        /// The RuleTileEditor for the overridden instance of the RuleTile
        /// </summary>
        public RuleTileEditor ruleTileEditor
        {
            get
            {
                if (m_RuleTileEditorTarget != overrideTile.m_Tile)
                {
                    DestroyImmediate(m_RuleTileEditor);
                    m_RuleTileEditor = Editor.CreateEditor(overrideTile.m_InstanceTile) as RuleTileEditor;
                    m_RuleTileEditorTarget = overrideTile.m_Tile;
                }
                return m_RuleTileEditor;
            }
        }

        RuleTileEditor m_RuleTileEditor;
        RuleTile m_RuleTileEditorTarget;

        /// <summary>
        /// List of Sprites and overriding Sprites
        /// </summary>
        public List<KeyValuePair<Sprite, Sprite>> m_Sprites = new List<KeyValuePair<Sprite, Sprite>>();
        /// <summary>
        /// List of GameObjects and overriding GameObjects
        /// </summary>
        public List<KeyValuePair<GameObject, GameObject>> m_GameObjects = new List<KeyValuePair<GameObject, GameObject>>();
        private ReorderableList m_SpriteList;
        private ReorderableList m_GameObjectList;
        private int m_MissingOriginalSpriteIndex;
        private int m_MissingOriginalGameObjectIndex;

        /// <summary>
        /// Height for a Sprite Element
        /// </summary>
        public static float k_SpriteElementHeight = 48;
        /// <summary>
        /// Height for a GameObject Element
        /// </summary>
        public static float k_GameObjectElementHeight = 16;
        /// <summary>
        /// Padding between Rule Elements
        /// </summary>
        public static float k_PaddingBetweenRules = 4;

        /// <summary>
        /// OnEnable for the RuleOverrideTileEditor
        /// </summary>
        public virtual void OnEnable()
        {
            if (m_SpriteList == null)
            {
                m_SpriteList = new ReorderableList(m_Sprites, typeof(KeyValuePair<Sprite, Sprite>), false, true, false, false);
                m_SpriteList.drawHeaderCallback = DrawSpriteListHeader;
                m_SpriteList.drawElementCallback = DrawSpriteElement;
                m_SpriteList.elementHeightCallback = GetSpriteElementHeight;
            }
            if (m_GameObjectList == null)
            {
                m_GameObjectList = new ReorderableList(m_GameObjects, typeof(KeyValuePair<Sprite, Sprite>), false, true, false, false);
                m_GameObjectList.drawHeaderCallback = DrawGameObjectListHeader;
                m_GameObjectList.drawElementCallback = DrawGameObjectElement;
                m_GameObjectList.elementHeightCallback = GetGameObjectElementHeight;
            }
        }

        /// <summary>
        /// OnDisable for the RuleOverrideTileEditor
        /// </summary>
        public virtual void OnDisable()
        {
            DestroyImmediate(ruleTileEditor);
            m_RuleTileEditorTarget = null;
        }

        /// <summary>
        /// Draws the Inspector GUI for the RuleOverrideTileEditor
        /// </summary>
        public override void OnInspectorGUI()
        {
            serializedObject.UpdateIfRequiredOrScript();

            DrawTileField();
            DrawCustomFields();

            overrideTile.GetOverrides(m_Sprites, ref m_MissingOriginalSpriteIndex);
            overrideTile.GetOverrides(m_GameObjects, ref m_MissingOriginalGameObjectIndex);

            EditorGUI.BeginChangeCheck();
            m_SpriteList.DoLayoutList();
            if (EditorGUI.EndChangeCheck())
            {
                overrideTile.ApplyOverrides(m_Sprites);
                SaveTile();
            }

            EditorGUI.BeginChangeCheck();
            m_GameObjectList.DoLayoutList();
            if (EditorGUI.EndChangeCheck())
            {
                overrideTile.ApplyOverrides(m_GameObjects);
                SaveTile();
            }
        }

        /// <summary>
        /// Draws the header for the Sprite list
        /// </summary>
        /// <param name="rect">GUI Rect to draw the header at</param>
        public void DrawSpriteListHeader(Rect rect)
        {
            float xMax = rect.xMax;
            rect.xMax = rect.xMax / 2.0f;
            GUI.Label(rect, "Original Sprite", EditorStyles.label);
            rect.xMin = rect.xMax;
            rect.xMax = xMax;
            GUI.Label(rect, "Override Sprite", EditorStyles.label);
        }

        /// <summary>
        /// Draws the header for the GameObject list
        /// </summary>
        /// <param name="rect">GUI Rect to draw the header at</param>
        public void DrawGameObjectListHeader(Rect rect)
        {
            float xMax = rect.xMax;
            rect.xMax = rect.xMax / 2.0f;
            GUI.Label(rect, "Original GameObject", EditorStyles.label);
            rect.xMin = rect.xMax;
            rect.xMax = xMax;
            GUI.Label(rect, "Override GameObject", EditorStyles.label);
        }

        /// <summary>
        /// Gets the GUI element height for a Sprite element with the given index
        /// </summary>
        /// <param name="index">Index of the Sprite element</param>
        /// <returns>GUI element height for the Sprite element</returns>
        public float GetSpriteElementHeight(int index)
        {
            float height = k_SpriteElementHeight + k_PaddingBetweenRules;

            bool isMissing = index >= m_MissingOriginalSpriteIndex;
            if (isMissing)
                height += 16;

            return height;
        }

        /// <summary>
        /// Gets the GUI element height for a GameObject element with the given index
        /// </summary>
        /// <param name="index">Index of the GameObject element</param>
        /// <returns>GUI element height for the GameObject element</returns>
        public float GetGameObjectElementHeight(int index)
        {
            float height = k_GameObjectElementHeight + k_PaddingBetweenRules;

            bool isMissing = index >= m_MissingOriginalGameObjectIndex;
            if (isMissing)
                height += 16;

            return height;
        }

        /// <summary>
        /// Draws the Sprite element for the RuleOverride list
        /// </summary>
        /// <param name="rect">Rect to draw the Sprite Element in</param>
        /// <param name="index">Index of the Sprite Element to draw</param>
        /// <param name="active">Whether the Sprite Element is active</param>
        /// <param name="focused">Whether the Sprite Element is focused</param>
        public void DrawSpriteElement(Rect rect, int index, bool active, bool focused)
        {
            bool isMissing = index >= m_MissingOriginalSpriteIndex;
            if (isMissing)
            {
                EditorGUI.HelpBox(new Rect(rect.xMin, rect.yMin, rect.width, 16), "Original Sprite missing", MessageType.Warning);
                rect.yMin += 16;
            }

            Sprite originalSprite = m_Sprites[index].Key;
            Sprite overrideSprite = m_Sprites[index].Value;

            rect.y += 2;
            rect.height -= k_PaddingBetweenRules;

            rect.xMax = rect.xMax / 2.0f;
            using (new EditorGUI.DisabledScope(true))
                EditorGUI.ObjectField(new Rect(rect.xMin, rect.yMin, rect.height, rect.height), originalSprite, typeof(Sprite), false);
            rect.xMin = rect.xMax;
            rect.xMax *= 2.0f;

            EditorGUI.BeginChangeCheck();
            overrideSprite = EditorGUI.ObjectField(new Rect(rect.xMin, rect.yMin, rect.height, rect.height), overrideSprite, typeof(Sprite), false) as Sprite;
            if (EditorGUI.EndChangeCheck())
                m_Sprites[index] = new KeyValuePair<Sprite, Sprite>(originalSprite, overrideSprite);
        }

        /// <summary>
        /// Draws the GameObject element for the RuleOverride list
        /// </summary>
        /// <param name="rect">Rect to draw the GameObject Element in</param>
        /// <param name="index">Index of the GameObject Element to draw</param>
        /// <param name="active">Whether the GameObject Element is active</param>
        /// <param name="focused">Whether the GameObject Element is focused</param>
        public void DrawGameObjectElement(Rect rect, int index, bool active, bool focused)
        {
            bool isMissing = index >= m_MissingOriginalGameObjectIndex;
            if (isMissing)
            {
                EditorGUI.HelpBox(new Rect(rect.xMin, rect.yMin, rect.width, 16), "Original GameObject missing", MessageType.Warning);
                rect.yMin += 16;
            }

            GameObject originalGameObject = m_GameObjects[index].Key;
            GameObject overrideGameObject = m_GameObjects[index].Value;

            rect.y += 2;
            rect.height -= k_PaddingBetweenRules;

            rect.xMax = rect.xMax / 2.0f;
            using (new EditorGUI.DisabledScope(true))
                EditorGUI.ObjectField(new Rect(rect.xMin, rect.yMin, rect.width, rect.height), originalGameObject, typeof(GameObject), false);
            rect.xMin = rect.xMax;
            rect.xMax *= 2.0f;

            EditorGUI.BeginChangeCheck();
            overrideGameObject = EditorGUI.ObjectField(new Rect(rect.xMin, rect.yMin, rect.width, rect.height), overrideGameObject, typeof(GameObject), false) as GameObject;
            if (EditorGUI.EndChangeCheck())
                m_GameObjects[index] = new KeyValuePair<GameObject, GameObject>(originalGameObject, overrideGameObject);
        }

        /// <summary>
        /// Draws a field for the RuleTile be overridden
        /// </summary>
        public void DrawTileField()
        {
            EditorGUI.BeginChangeCheck();
            RuleTile tile = EditorGUILayout.ObjectField(Styles.overrideTile, overrideTile.m_Tile, typeof(RuleTile), false) as RuleTile;
            if (EditorGUI.EndChangeCheck())
            {
                if (!LoopCheck(tile))
                {
                    overrideTile.m_Tile = tile;
                    SaveTile();
                }
                else
                {
                    Debug.LogWarning("Circular tile reference");
                }
            }

            bool LoopCheck(RuleTile checkTile)
            {
                if (!overrideTile.m_InstanceTile)
                    return false;

                HashSet<RuleTile> renferenceTils = new HashSet<RuleTile>();
                Add(overrideTile.m_InstanceTile);

                return renferenceTils.Contains(checkTile);

                void Add(RuleTile ruleTile)
                {
                    if (renferenceTils.Contains(ruleTile))
                        return;

                    renferenceTils.Add(ruleTile);

                    var overrideTiles = RuleTileEditor.FindAffectedOverrideTiles(ruleTile);

                    foreach (var overrideTile in overrideTiles)
                        Add(overrideTile.m_InstanceTile);
                }
            }
        }

        /// <summary>
        /// Draw editor fields for custom properties for the RuleOverrideTile
        /// </summary>
        public void DrawCustomFields()
        {
            if (ruleTileEditor)
            {
                ruleTileEditor.target.hideFlags = HideFlags.None;
                ruleTileEditor.DrawCustomFields(true);
                ruleTileEditor.target.hideFlags = HideFlags.NotEditable;
            }
        }

        private void SaveInstanceTileAsset()
        {
            bool assetChanged = false;

            if (overrideTile.m_InstanceTile)
            {
                if (!overrideTile.m_Tile || overrideTile.m_InstanceTile.GetType() != overrideTile.m_Tile.GetType())
                {
                    DestroyImmediate(overrideTile.m_InstanceTile, true);
                    overrideTile.m_InstanceTile = null;
                    assetChanged = true;
                }
            }
            if (!overrideTile.m_InstanceTile)
            {
                if (overrideTile.m_Tile)
                {
                    var t = overrideTile.m_Tile.GetType();
                    RuleTile instanceTile = ScriptableObject.CreateInstance(t) as RuleTile;
                    instanceTile.hideFlags = HideFlags.NotEditable;
                    AssetDatabase.AddObjectToAsset(instanceTile, overrideTile);
                    overrideTile.m_InstanceTile = instanceTile;
                    assetChanged = true;
                }
            }

            if (overrideTile.m_InstanceTile)
            {
                string instanceTileName = overrideTile.m_Tile.name + " (Override)";
                if (overrideTile.m_InstanceTile.name != instanceTileName)
                {
                    overrideTile.m_InstanceTile.name = instanceTileName;
                    assetChanged = true;
                }
            }

            if (assetChanged)
            {
                EditorUtility.SetDirty(overrideTile.m_InstanceTile);
#if UNITY_2021_1       
                AssetDatabase.SaveAssets();
#else
                AssetDatabase.SaveAssetIfDirty(overrideTile.m_InstanceTile);
#endif   
            }
        }

        /// <summary>
        /// Saves any changes to the RuleOverrideTile
        /// </summary>
        public void SaveTile()
        {
            EditorUtility.SetDirty(target);
            SceneView.RepaintAll();

            SaveInstanceTileAsset();
        
            if (overrideTile.m_InstanceTile)
            {
                overrideTile.Override();
                RuleTileEditor.UpdateAffectedOverrideTiles(overrideTile.m_InstanceTile);
            }

            if (ruleTileEditor && ruleTileEditor.m_PreviewTilemaps != null)
            {
                foreach (var tilemap in ruleTileEditor.m_PreviewTilemaps)
                    tilemap.RefreshAllTiles();
            }
        }

        /// <summary>
        /// Renders a static preview Texture2D for a RuleOverrideTile asset
        /// </summary>
        /// <param name="assetPath">Asset path of the RuleOverrideTile</param>
        /// <param name="subAssets">Arrays of assets from the given Asset path</param>
        /// <param name="width">Width of the static preview</param>
        /// <param name="height">Height of the static preview </param>
        /// <returns>Texture2D containing static preview for the RuleOverrideTile asset</returns>
        public override Texture2D RenderStaticPreview(string assetPath, Object[] subAssets, int width, int height)
        {
            if (ruleTileEditor)
                return ruleTileEditor.RenderStaticPreview(assetPath, subAssets, width, height);

            return base.RenderStaticPreview(assetPath, subAssets, width, height);
        }

        /// <summary>
        /// Whether the RuleOverrideTile has a preview GUI
        /// </summary>
        /// <returns>True if RuleOverrideTile has a preview GUI. False if not.</returns>
        public override bool HasPreviewGUI()
        {
            if (ruleTileEditor)
                return ruleTileEditor.HasPreviewGUI();

            return false;
        }

        /// <summary>
        /// Updates preview settings for the RuleOverrideTile.
        /// </summary>
        public override void OnPreviewSettings()
        {
            if (ruleTileEditor)
                ruleTileEditor.OnPreviewSettings();
        }

        /// <summary>
        /// Draws the preview GUI for the RuleTile
        /// </summary>
        /// <param name="rect">Rect to draw the preview GUI</param>
        /// <param name="background">The GUIStyle of the background for the preview</param>
        public override void OnPreviewGUI(Rect rect, GUIStyle background)
        {
            if (ruleTileEditor)
                ruleTileEditor.OnPreviewGUI(rect, background);
        }
    }
}