namespace SRDebugger.UI.Tabs
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using Controls;
    using Controls.Data;
    using Internal;
    using Other;
    using Services;
    using SRF;
    using UnityEngine;
    using UnityEngine.UI;

    public class OptionsTabController : SRMonoBehaviourEx
    {
        private class CategoryInstance
        {
            public CategoryGroup CategoryGroup { get; private set; }
            public readonly List<OptionsControlBase> Options = new List<OptionsControlBase>();

            public CategoryInstance(CategoryGroup group)
            {
                CategoryGroup = group;
            }
        }

        private readonly List<OptionsControlBase> _controls = new List<OptionsControlBase>();
        private readonly List<CategoryInstance> _categories = new List<CategoryInstance>();

        private readonly Dictionary<OptionDefinition, OptionsControlBase> _options =
            new Dictionary<OptionDefinition, OptionsControlBase>();

        private bool _queueRefresh;
        private bool _selectionModeEnabled;
        private Canvas _optionCanvas;

        [RequiredField] public ActionControl ActionControlPrefab;

        [RequiredField] public CategoryGroup CategoryGroupPrefab;

        [RequiredField] public RectTransform ContentContainer;

        [RequiredField] public GameObject NoOptionsNotice;

        [RequiredField] public Toggle PinButton;

        [RequiredField] public GameObject PinPromptSpacer;

        [RequiredField] public GameObject PinPromptText;


        protected override void Start()
        {
            base.Start();

            PinButton.onValueChanged.AddListener(SetSelectionModeEnabled);

            PinPromptText.SetActive(false);
            //PinPromptSpacer.SetActive(false);

            Populate();

            _optionCanvas = GetComponent<Canvas>();

            Service.Options.OptionsUpdated += OnOptionsUpdated;
            Service.PinnedUI.OptionPinStateChanged += OnOptionPinnedStateChanged;
        }

        protected override void OnDestroy()
        {
            if (Service.Options != null)
            {
                Service.Options.OptionsUpdated -= OnOptionsUpdated;
            }

            if (Service.PinnedUI != null)
            {
                Service.PinnedUI.OptionPinStateChanged -= OnOptionPinnedStateChanged;
            }

            base.OnDestroy();
        }

        private void OnOptionPinnedStateChanged(OptionDefinition optionDefinition, bool isPinned)
        {
            if (_options.ContainsKey(optionDefinition))
            {
                _options[optionDefinition].IsSelected = isPinned;
            }
        }

        private void OnOptionsUpdated(object sender, EventArgs eventArgs)
        {
            Clear();
            Populate();
        }

        protected override void OnEnable()
        {
            base.OnEnable();

            Service.Panel.VisibilityChanged += PanelOnVisibilityChanged;
        }

        protected override void OnDisable()
        {
            // Always end pinning mode when tabbing away
            SetSelectionModeEnabled(false);

            if (Service.Panel != null)
            {
                Service.Panel.VisibilityChanged -= PanelOnVisibilityChanged;
            }

            base.OnDisable();
        }

        protected override void Update()
        {
            base.Update();

            if (_queueRefresh)
            {
                _queueRefresh = false;
                Refresh();
            }
        }

        private void PanelOnVisibilityChanged(IDebugPanelService debugPanelService, bool b)
        {
            // Always end pinning mode when panel is closed
            if (!b)
            {
                SetSelectionModeEnabled(false);

                // Refresh bindings for all pinned controls
                Refresh();
            }
            else if (b && CachedGameObject.activeInHierarchy)
            {
                // If the panel is visible, and this tab is active (selected), refresh all the data bindings
                Refresh();
            }

            if (_optionCanvas != null)
            {
                _optionCanvas.enabled = b;
            }
        }

        public void SetSelectionModeEnabled(bool isEnabled)
        {
            if (_selectionModeEnabled == isEnabled)
            {
                return;
            }

            _selectionModeEnabled = isEnabled;

            PinButton.isOn = isEnabled;
            PinPromptText.SetActive(isEnabled);
            //PinPromptSpacer.SetActive(isEnabled);

            foreach (var kv in _options)
            {
                kv.Value.SelectionModeEnabled = isEnabled;

                // Set IsSelected if entering selection mode.
                if (isEnabled)
                {
                    kv.Value.IsSelected = Service.PinnedUI.HasPinned(kv.Key);
                }
            }

            foreach (var cat in _categories)
            {
                cat.CategoryGroup.SelectionModeEnabled = isEnabled;
            }

            RefreshCategorySelection();

            // Return if entering selection mode
            if (isEnabled)
            {
                return;
            }
        }

        private void Refresh()
        {
            for (var i = 0; i < _options.Count; i++)
            {
                _controls[i].Refresh();
                _controls[i].IsSelected = Service.PinnedUI.HasPinned(_controls[i].Option);
            }
        }

        private void CommitPinnedOptions()
        {
            foreach (var kv in _options)
            {
                var control = kv.Value;

                if (control.IsSelected && !Service.PinnedUI.HasPinned(kv.Key))
                {
                    Service.PinnedUI.Pin(kv.Key);
                }
                else if (!control.IsSelected && Service.PinnedUI.HasPinned(kv.Key))
                {
                    Service.PinnedUI.Unpin(kv.Key);
                }
            }
        }

        private bool _isTogglingCategory;

        private void RefreshCategorySelection()
        {
            _isTogglingCategory = true;

            foreach (var cat in _categories)
            {
                var allSelected = true;

                for (var i = 0; i < cat.Options.Count; i++)
                {
                    if (!cat.Options[i].IsSelected)
                    {
                        allSelected = false;
                        break;
                    }
                }

                cat.CategoryGroup.IsSelected = allSelected;
            }

            _isTogglingCategory = false;
        }

        private void OnOptionSelectionToggle(bool selected)
        {
            if (!_isTogglingCategory)
            {
                RefreshCategorySelection();
                CommitPinnedOptions();
            }
        }

        /// <summary>
        /// When a category mode selection is changed.
        /// </summary>
        /// <param name="category"></param>
        /// <param name="selected"></param>
        private void OnCategorySelectionToggle(CategoryInstance category, bool selected)
        {
            _isTogglingCategory = true;

            for (var i = 0; i < category.Options.Count; i++)
            {
                category.Options[i].IsSelected = selected;
            }

            _isTogglingCategory = false;

            CommitPinnedOptions();
        }

        #region Initialisation

        protected void Populate()
        {
            var sortedOptions = new Dictionary<string, List<OptionDefinition>>();

            foreach (var option in Service.Options.Options)
            {
                // Find a properly list for that category, or create a new one
                List<OptionDefinition> memberList;

                if (!sortedOptions.TryGetValue(option.Category, out memberList))
                {
                    memberList = new List<OptionDefinition>();
                    sortedOptions.Add(option.Category, memberList);
                }

                memberList.Add(option);
            }

            var hasCreated = false;

            foreach (var kv in sortedOptions)
            {
                if (kv.Value.Count == 0)
                {
                    continue;
                }

                hasCreated = true;
                CreateCategory(kv.Key, kv.Value);
            }

            if (hasCreated)
            {
                NoOptionsNotice.SetActive(false);
            }
        }

        protected void CreateCategory(string title, List<OptionDefinition> options)
        {
            options.Sort((d1, d2) => d1.SortPriority.CompareTo(d2.SortPriority));

            var groupInstance = SRInstantiate.Instantiate(CategoryGroupPrefab);
            var categoryInstance = new CategoryInstance(groupInstance);

            _categories.Add(categoryInstance);

            groupInstance.CachedTransform.SetParent(ContentContainer, false);
            groupInstance.Header.text = title;
            groupInstance.SelectionModeEnabled = false;

            categoryInstance.CategoryGroup.SelectionToggle.onValueChanged.AddListener(
                b => OnCategorySelectionToggle(categoryInstance, b));

            foreach (var option in options)
            {
                var control = OptionControlFactory.CreateControl(option, title);

                if (control == null)
                {
                    Debug.LogError("[SRDebugger.OptionsTab] Failed to create option control for {0}".Fmt(option.Name));
                    continue;
                }

                categoryInstance.Options.Add(control);
                control.CachedTransform.SetParent(groupInstance.Container, false);
                control.IsSelected = Service.PinnedUI.HasPinned(option);
                control.SelectionModeEnabled = false;
                control.SelectionModeToggle.onValueChanged.AddListener(OnOptionSelectionToggle);

                _options.Add(option, control);
                _controls.Add(control);
            }
        }

        void Clear()
        {
            foreach (var categoryInstance in _categories)
            {
                Destroy(categoryInstance.CategoryGroup.gameObject);
            }

            _categories.Clear();
            _controls.Clear();
            _options.Clear();
        }

        #endregion
    }
}