using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.AddressableAssets.Build;
using UnityEditor.AddressableAssets.Settings;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using UnityEngine.Serialization;

namespace UnityEditor.AddressableAssets.GUI
{
    class ContentUpdatePreviewWindow : EditorWindow
    {
        private GUIContent m_ApplyChangesGUIContent = new GUIContent("Apply Changes", "Move assets to a new remote group in preparation for the content update build");

        internal static bool PrepareForContentUpdate(AddressableAssetSettings settings, string buildPath, Action applyChangesCallback = null)
        {
            var modifiedEntries = ContentUpdateScript.GatherModifiedEntriesWithDependencies(settings, buildPath);
            var previewWindow = GetWindow<ContentUpdatePreviewWindow>();
            previewWindow.Show(settings, modifiedEntries, applyChangesCallback);
            return true;
        }

        internal static void ShowUpdatePreviewWindow(AddressableAssetSettings settings, Dictionary<AddressableAssetEntry, List<AddressableAssetEntry>> modifiedEntries,
            Action applyChangesCallback = null)
        {
            var previewWindow = GetWindow<ContentUpdatePreviewWindow>();
            previewWindow.Show(settings, modifiedEntries, applyChangesCallback, true);
        }

        void OnEnable()
        {
            titleContent = new GUIContent("Assets with update issues");
        }

        class ContentUpdateTreeView : TreeView
        {
            class Item : TreeViewItem
            {
                internal AddressableAssetEntry entry;
                internal bool enabled;

                public Item(AddressableAssetEntry entry, int itemDepth = 1) : base(entry.guid.GetHashCode(), itemDepth)
                {
                    this.entry = entry;
                    enabled = true;
                }
            }

            ContentUpdatePreviewWindow m_Preview;

            public ContentUpdateTreeView(ContentUpdatePreviewWindow preview, TreeViewState state, MultiColumnHeaderState mchs) : base(state, new MultiColumnHeader(mchs))
            {
                m_Preview = preview;
            }

            internal List<AddressableAssetEntry> GetEnabledEntries()
            {
                var result = new HashSet<AddressableAssetEntry>();
                foreach (var i in GetRows())
                {
                    var item = i as Item;
                    if (item != null && item.enabled)
                    {
                        result.Add(item.entry);
                        if (item.hasChildren)
                        {
                            foreach (var child in i.children)
                            {
                                var childItem = child as Item;
                                if (childItem != null && !result.Contains(childItem.entry))
                                    result.Add(childItem.entry);
                            }
                        }
                    }
                }

                return result.ToList();
            }

            protected override TreeViewItem BuildRoot()
            {
                columnIndexForTreeFoldouts = 1;

                var root = new TreeViewItem(-1, -1);
                root.children = new List<TreeViewItem>();
                foreach (var k in m_Preview.m_DepEntriesMap.Keys)
                {
                    var mainItem = new Item(k, 0);
                    root.AddChild(mainItem);

                    foreach (var dep in m_Preview.m_DepEntriesMap[k])
                        mainItem.AddChild(new Item(dep, mainItem.depth + 1));
                }

                return root;
            }

            protected override void RowGUI(RowGUIArgs args)
            {
                var item = args.item as Item;
                if (item == null)
                {
                    base.RowGUI(args);
                    return;
                }

                for (int i = 0; i < args.GetNumVisibleColumns(); ++i)
                {
                    CellGUI(args.GetCellRect(i), item, args.GetColumn(i));
                }
            }

            private const int kToggleOffset = 5;
            private const int kMainAssetXOffset = 20;
            private const int kDependencyAssetXOffset = 40;

            void CellGUI(Rect cellRect, Item item, int column)
            {
                if (column == 0)
                    cellRect.xMin = (cellRect.xMax / 2) - kToggleOffset;
                else //Only want this indent on every column that isn't 0
                {
                    if ((item.parent as Item) != null)
                        cellRect.xMin += kDependencyAssetXOffset;
                    else
                        cellRect.xMin += kMainAssetXOffset;
                }

                if (column == 0)
                {
                    if (item.entry != null)
                    {
                        if ((item.parent as Item) != null)
                            item.enabled = (item.parent as Item).enabled;
                        else
                            item.enabled = EditorGUI.Toggle(cellRect, item.enabled);
                    }
                }
                else if (column == 1)
                {
                    EditorGUI.LabelField(cellRect, item.entry.address);
                }
                else if (column == 2)
                {
                    EditorGUI.LabelField(cellRect, item.entry.AssetPath);
                }
                else if (column == 3)
                {
                    EditorGUI.LabelField(cellRect, item.entry.parentGroup.Name);
                }
            }

            internal static MultiColumnHeaderState CreateDefaultMultiColumnHeaderState()
            {
                var retVal = new MultiColumnHeaderState.Column[]
                {
                    new MultiColumnHeaderState.Column()
                    {
                        headerContent = new GUIContent("Include", "Include change in Update"),
                        minWidth = 50,
                        width = 50,
                        maxWidth = 50,
                        headerTextAlignment = TextAlignment.Left,
                        canSort = true,
                        autoResize = true
                    },
                    new MultiColumnHeaderState.Column()
                    {
                        headerContent = new GUIContent("Address", "Data Value"),
                        minWidth = 300,
                        width = 300,
                        maxWidth = 1000,
                        headerTextAlignment = TextAlignment.Left,
                        canSort = true,
                        autoResize = true
                    },
                    new MultiColumnHeaderState.Column()
                    {
                        headerContent = new GUIContent("Path", "Asset Path"),
                        minWidth = 300,
                        width = 300,
                        maxWidth = 1000,
                        headerTextAlignment = TextAlignment.Left,
                        canSort = true,
                        autoResize = true
                    },
                    new MultiColumnHeaderState.Column()
                    {
                        headerContent = new GUIContent("Modified Group", "The modified Addressable group"),
                        minWidth = 300,
                        width = 300,
                        maxWidth = 1000,
                        headerTextAlignment = TextAlignment.Left,
                        canSort = true,
                        autoResize = true
                    }
                };

                return new MultiColumnHeaderState(retVal);
            }
        }

        string m_GroupName = "Content Update";
        AddressableAssetSettings m_Settings;
        Dictionary<AddressableAssetEntry, List<AddressableAssetEntry>> m_DepEntriesMap;
        Action m_ApplyChangesCallback;
        Vector2 m_ScrollPosition;
        ContentUpdateTreeView m_Tree;

        [FormerlySerializedAs("treeState")]
        [SerializeField]
        TreeViewState m_TreeState;

        [FormerlySerializedAs("mchs")]
        [SerializeField]
        MultiColumnHeaderState m_Mchs;

        bool m_LogOutcomeAnalytics = false;

        public void Show(AddressableAssetSettings settings, Dictionary<AddressableAssetEntry, List<AddressableAssetEntry>> entryDependencies, Action applyChangesCallback = null,
            bool logAnalytics = false)
        {
            m_Settings = settings;
            m_DepEntriesMap = entryDependencies;
            m_ApplyChangesCallback = applyChangesCallback;
            m_LogOutcomeAnalytics = logAnalytics;
            Show();
        }

        public void OnGUI()
        {
            if (m_DepEntriesMap == null)
                return;

            Rect toolbarRect = new Rect(16, 5, position.width - 32, 70);
            Rect contentRect = new Rect(16, 75, position.width - 32, position.height - 55);

            if (m_Tree == null)
            {
                if (m_TreeState == null)
                    m_TreeState = new TreeViewState();

                var headerState = ContentUpdateTreeView.CreateDefaultMultiColumnHeaderState();
                if (MultiColumnHeaderState.CanOverwriteSerializedFields(m_Mchs, headerState))
                    MultiColumnHeaderState.OverwriteSerializedFields(m_Mchs, headerState);
                m_Mchs = headerState;

                m_Tree = new ContentUpdateTreeView(this, m_TreeState, m_Mchs);
                m_Tree.Reload();
            }

            if (m_DepEntriesMap.Count == 0)
            {
                Rect emptyContentRect = new Rect(0, 0, position.width, position.height - 50);
                GUILayout.BeginArea(emptyContentRect);
                GUILayout.BeginVertical();

                GUILayout.Label("No Addressable groups with a BundledAssetGroupSchema and ContentUpdateGroupSchema (with Prevent Updates enabled) appear to have been modified.");

                GUILayout.EndVertical();
                GUILayout.EndArea();
            }
            else
            {
                GUILayout.BeginArea(toolbarRect);
                GUILayout.BeginVertical();
                EditorGUILayout.HelpBox("Modified assets that are part of a group with Prevent Update enabled have been detected during this content update build. " +
                                        "Applying the changes moves all selected items into a new group that has Prevent Updates disabled.", MessageType.Info);

                GUILayout.Space(12f);
                GUILayout.BeginHorizontal();
                GUILayout.Label(new GUIContent("New Group Name: ", "This value is used to set the name of the new group that is created as part of applying the changes from " +
                                                                   "this tool. If the group already exists, a number is appended to the group name."));

                m_GroupName = GUILayout.TextArea(m_GroupName, GUILayout.MinWidth(400f));
                GUILayout.FlexibleSpace();
                GUILayout.EndHorizontal();
                GUILayout.EndVertical();
                GUILayout.EndArea();

                m_Tree.OnGUI(contentRect);
            }

            GUILayout.BeginArea(new Rect(0, position.height - 50, position.width, 50));
            GUILayout.BeginHorizontal();
            bool hasPostApplyCallback = m_ApplyChangesCallback != null;

            string cancelButtonName = hasPostApplyCallback ? "Cancel build" : "Cancel";
            if (GUILayout.Button(cancelButtonName))
            {
                if (m_LogOutcomeAnalytics)
                    AddressableAnalytics.ReportUsageEvent(AddressableAnalytics.UsageEventType.ContentUpdateCancelled);
                Close();
            }

            bool showApplyChanges = m_Tree.GetEnabledEntries().Count != 0;
            if (showApplyChanges)
            {
                string buttonName = hasPostApplyCallback ? "Apply and Continue" : "Apply Changes";
                m_ApplyChangesGUIContent.text = buttonName;

                if (GUILayout.Button(m_ApplyChangesGUIContent))
                {
                    if (m_LogOutcomeAnalytics)
                        AddressableAnalytics.ReportUsageEvent(AddressableAnalytics.UsageEventType.ContentUpdateHasChangesInUpdateRestrictionWindow);
                    string groupName = string.IsNullOrEmpty(m_GroupName) ? "Content Update" : m_GroupName;
                    var enabledEntries = m_Tree.GetEnabledEntries();
                    HashSet<AddressableAssetGroup> clearedGroups = new HashSet<AddressableAssetGroup>();
                    foreach (var entry in enabledEntries)
                    {
                        if (clearedGroups.Contains(entry.parentGroup))
                            continue;
                        entry.parentGroup.FlaggedDuringContentUpdateRestriction = false;
                        clearedGroups.Add(entry.parentGroup);
                    }
                    ContentUpdateScript.CreateContentUpdateGroup(m_Settings, enabledEntries, groupName);
                    m_ApplyChangesCallback?.Invoke();

                    Close();
                }
            }
            else
            {
                using (new EditorGUI.DisabledScope(m_ApplyChangesCallback == null))
                {
                    string buttonName = m_ApplyChangesCallback == null ? "Apply Changes" : "Continue without changes";
                    if (GUILayout.Button(buttonName))
                    {
                        if (m_LogOutcomeAnalytics)
                            AddressableAnalytics.ReportUsageEvent(AddressableAnalytics.UsageEventType.ContentUpdateContinuesWithoutChanges);
                        m_ApplyChangesCallback?.Invoke();
                        Close();
                    }
                }
            }

            GUILayout.EndHorizontal();
            GUILayout.EndArea();
        }
    }
}