using UnityEngine.Experimental.UIElements;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace UnityEditor.PackageManager.UI
{
#if !UNITY_2018_3_OR_NEWER
    internal class PackageListFactory : UxmlFactory<PackageList>
    {
        protected override PackageList DoCreate(IUxmlAttributes bag, CreationContext cc)
        {
            return new PackageList();
        }
    }
#endif

    internal class PackageList : VisualElement
    {
#if UNITY_2018_3_OR_NEWER
        internal new class UxmlFactory : UxmlFactory<PackageList> { }
#endif

        public event Action<Package> OnSelected = delegate { };
        public event Action OnLoaded = delegate { };
        public event Action OnFocusChange = delegate { };

        private readonly VisualElement root;
        internal PackageItem selectedItem;
        private List<PackageGroup> Groups;

        public PackageList()
        {
            Groups = new List<PackageGroup>();

            root = Resources.GetTemplate("PackageList.uxml");
            Add(root);
            root.StretchToParentSize();

            UIUtils.SetElementDisplay(Empty, false);
            UIUtils.SetElementDisplay(NoResult, false);

            PackageCollection.Instance.OnPackagesChanged += SetPackages;

            RegisterCallback<AttachToPanelEvent>(OnEnterPanel);
            RegisterCallback<DetachFromPanelEvent>(OnLeavePanel);

            Reload();
        }

        public void GrabFocus()
        {
            if (selectedItem == null)
                return;
            
            selectedItem.Focus();
        }

        public void ShowResults(PackageItem item)
        {
            NoResultText.text = string.Empty;
            UIUtils.SetElementDisplay(NoResult, false);

            Select(item);

            EditorApplication.delayCall += ScrollIfNeeded;

            UpdateGroups();
        }

        public void ShowNoResults()
        {
            NoResultText.text = string.Format("No results for \"{0}\"", PackageSearchFilter.Instance.SearchText);
            UIUtils.SetElementDisplay(NoResult, true);
            foreach (var group in Groups)
            {
                UIUtils.SetElementDisplay(group, false);
            }
            Select(null);
            OnSelected(null);
        }

        private void UpdateGroups()
        {
            foreach (var group in Groups)
            {
                PackageItem firstPackage = null;
                PackageItem lastPackage = null;

                var listGroup = group.Query<PackageItem>().ToList();
                foreach (var item in listGroup)
                {
                    if (!item.visible)
                        continue;

                    if (firstPackage == null) firstPackage = item;
                    lastPackage = item;
                }

                if (firstPackage == null && lastPackage == null)
                {
                    UIUtils.SetElementDisplay(group, false);
                }
                else 
                {
                    UIUtils.SetElementDisplay(group, true);
                    group.firstPackage = firstPackage;
                    group.lastPackage = lastPackage;
                }
            }
        }

        private void OnEnterPanel(AttachToPanelEvent e)
        {
            panel.visualTree.RegisterCallback<KeyDownEvent>(OnKeyDownShortcut);
        }

        private void OnLeavePanel(DetachFromPanelEvent e)
        {
            panel.visualTree.UnregisterCallback<KeyDownEvent>(OnKeyDownShortcut);
        }

        private void ScrollIfNeeded()
        {
            EditorApplication.delayCall -= ScrollIfNeeded;
            
            if (selectedItem == null)
                return;

            var minY = List.worldBound.yMin;
            var maxY = List.worldBound.yMax;
            var itemMinY = selectedItem.worldBound.yMin;
            var itemMaxY = selectedItem.worldBound.yMax;
            var scroll = List.scrollOffset;

            if (itemMinY < minY)
            {
                scroll.y -= minY - itemMinY;
                if (scroll.y <= minY)
                    scroll.y = 0;
                List.scrollOffset = scroll;
            }
            else if (itemMaxY > maxY)
            {
                scroll.y += itemMaxY - maxY;
                List.scrollOffset = scroll;
            }
        }

        private void OnKeyDownShortcut(KeyDownEvent evt)
        {
            if (selectedItem == null)
                return;

            if (evt.keyCode == KeyCode.Tab)
            {
                OnFocusChange();
                evt.StopPropagation();
                return;
            }
            
            if (evt.keyCode == KeyCode.UpArrow)
            {
                if (selectedItem.previousItem != null)
                {
                    Select(selectedItem.previousItem);
                    ScrollIfNeeded();
                }
                else if (selectedItem.packageGroup.previousGroup != null && selectedItem.packageGroup.previousGroup.visible)
                {
                    Select(selectedItem.packageGroup.previousGroup.lastPackage);
                    ScrollIfNeeded();
                }
                evt.StopPropagation();
                return;
            }

            if (evt.keyCode == KeyCode.DownArrow)
            {
                if (selectedItem.nextItem != null)
                {
                    Select(selectedItem.nextItem);
                    ScrollIfNeeded();
                }
                else if (selectedItem.packageGroup.nextGroup != null && selectedItem.packageGroup.nextGroup.visible)
                {
                    Select(selectedItem.packageGroup.nextGroup.firstPackage);
                    ScrollIfNeeded();
                }
                evt.StopPropagation();
                return;
            }

            if (evt.keyCode == KeyCode.PageUp)
            {
                if (selectedItem.packageGroup != null)
                {
                    if (selectedItem == selectedItem.packageGroup.lastPackage && selectedItem != selectedItem.packageGroup.firstPackage)
                    {
                        Select(selectedItem.packageGroup.firstPackage);
                        ScrollIfNeeded();
                    }
                    else if (selectedItem == selectedItem.packageGroup.firstPackage && selectedItem.packageGroup.previousGroup != null && selectedItem.packageGroup.previousGroup.visible)
                    {
                        Select(selectedItem.packageGroup.previousGroup.lastPackage);
                        ScrollIfNeeded();
                    }
                    else if (selectedItem != selectedItem.packageGroup.lastPackage && selectedItem != selectedItem.packageGroup.firstPackage)
                    {
                        Select(selectedItem.packageGroup.firstPackage);
                        ScrollIfNeeded();
                    }
                }
                evt.StopPropagation();
                return;
            }

            if (evt.keyCode == KeyCode.PageDown)
            {
                if (selectedItem.packageGroup != null)
                {
                    if (selectedItem == selectedItem.packageGroup.firstPackage && selectedItem != selectedItem.packageGroup.lastPackage)
                    {
                        Select(selectedItem.packageGroup.lastPackage);
                        ScrollIfNeeded();
                    }
                    else if (selectedItem == selectedItem.packageGroup.lastPackage && selectedItem.packageGroup.nextGroup != null && selectedItem.packageGroup.nextGroup.visible)
                    {
                        Select(selectedItem.packageGroup.nextGroup.firstPackage);
                        ScrollIfNeeded();
                    }
                    else if (selectedItem != selectedItem.packageGroup.firstPackage && selectedItem != selectedItem.packageGroup.lastPackage)
                    {
                        Select(selectedItem.packageGroup.lastPackage);
                        ScrollIfNeeded();
                    }
                }
                evt.StopPropagation();
            }
        }

        private void Reload()
        {
            // Force a re-init to initial condition
            PackageCollection.Instance.UpdatePackageCollection();
            SelectLastSelection();
        }

        private void ClearAll()
        {
            List.Clear();
            Groups.Clear();

            UIUtils.SetElementDisplay(Empty, false);
            UIUtils.SetElementDisplay(NoResult, false);
        }

        private void SetPackages(IEnumerable<Package> packages)
        {
            if (PackageCollection.Instance.Filter == PackageFilter.Modules)
            {
                packages = packages.Where(pkg => pkg.IsBuiltIn);
            }
            else if (PackageCollection.Instance.Filter== PackageFilter.All)
            {
                packages = packages.Where(pkg => !pkg.IsBuiltIn);
            }
            else
            {
                packages = packages.Where(pkg => !pkg.IsBuiltIn);
                packages = packages.Where(pkg => pkg.Current != null);
            }

            OnLoaded();
            ClearAll();

            var packagesGroup = new PackageGroup(PackageGroupOrigins.Packages.ToString());
            Groups.Add(packagesGroup);
            List.Add(packagesGroup);
            packagesGroup.previousGroup = null;

            var builtInGroup = new PackageGroup(PackageGroupOrigins.BuiltInPackages.ToString());
            Groups.Add(builtInGroup);
            List.Add(builtInGroup);

            if ((PackageCollection.Instance.Filter & PackageFilter.Modules) == PackageFilter.Modules)
            {
                packagesGroup.nextGroup = builtInGroup;
                builtInGroup.previousGroup = packagesGroup;
                builtInGroup.nextGroup = null;
            }
            else
            {
                packagesGroup.nextGroup = null;
                UIUtils.SetElementDisplay(builtInGroup, false);
            }

            var lastSelection = PackageCollection.Instance.SelectedPackage;
            Select(null);

            PackageItem defaultSelection = null;

            foreach (var package in packages.OrderBy(pkg => pkg.Versions.FirstOrDefault() == null ? pkg.Name : pkg.Versions.FirstOrDefault().DisplayName))
            {
                var item = AddPackage(package);

                if (null == selectedItem && defaultSelection == null)
                    defaultSelection = item;

                if (null == selectedItem && !string.IsNullOrEmpty(lastSelection) && package.Name.Equals(lastSelection))
                    Select(item);
            }

            if (selectedItem == null)
                Select(defaultSelection);

            PackageFiltering.FilterPackageList(this);
        }

        public void SelectLastSelection()
        {
            var lastSelection = PackageCollection.Instance.SelectedPackage;
            if (lastSelection == null)
                return;
            
            var list = List.Query<PackageItem>().ToList();
            PackageItem defaultSelection = null;

            foreach (var item in list)
            {
                if (defaultSelection == null)
                    defaultSelection = item;

                if (!string.IsNullOrEmpty(lastSelection) && item.package.Name.Equals(lastSelection))
                {
                    defaultSelection = item;
                    break;
                }
            }

            selectedItem = null;
            Select(defaultSelection);
        }

        private PackageItem AddPackage(Package package)
        {
            var groupName = package.Latest != null ? package.Latest.Group : package.Current.Group;
            var group = GetOrCreateGroup(groupName);
            var packageItem = group.AddPackage(package);

            packageItem.OnSelected += Select;

            return packageItem;
        }

        private PackageGroup GetOrCreateGroup(string groupName)
        {
            foreach (var g in Groups)
            {
                if (g.name == groupName)
                    return g;
            }

            var group = new PackageGroup(groupName);
            var latestGroup = Groups.LastOrDefault();
            Groups.Add(group);
            List.Add(group);

            group.previousGroup = null;
            if (latestGroup != null)
            {
                latestGroup.nextGroup = group;
                group.previousGroup = latestGroup;
                group.nextGroup = null;
            }
            return group;
        }

        private void Select(PackageItem packageItem)
        {
            if (packageItem == selectedItem)
                return;

            var selectedPackageName = packageItem != null ? packageItem.package.Name : null;
            PackageCollection.Instance.SelectedPackage = selectedPackageName;

            if (selectedItem != null)
                selectedItem.SetSelected(false); // Clear Previous selection

            selectedItem = packageItem;
            if (selectedItem == null)
            {
                OnSelected(null);
                return;
            }

            selectedItem.SetSelected(true);
            ScrollIfNeeded();
            OnSelected(selectedItem.package);
        }

        private ScrollView List { get { return root.Q<ScrollView>("scrollView"); } }
        private VisualElement Empty { get { return root.Q<VisualElement>("emptyArea"); } }
        private VisualElement NoResult { get { return root.Q<VisualElement>("noResult"); } }
        private Label NoResultText { get { return root.Q<Label>("noResultText"); } }
    }
}
