/*
<copyright file="BGDatabaseMTManagerEditor.cs" company="BansheeGz">
    Copyright (c) 2018-2022 All Rights Reserved
</copyright>
*/


using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.Events;

namespace BansheeGz.BGDatabase.Editor
{
    [CustomEditor(typeof(BGDatabaseMTManager))]
    public class BGDatabaseMTManagerEditor : UnityEditor.Editor
    {
        private const int PopupWidth = 300;

        //offset for vertical scroll thumb
        private const int PopupWidthOffset = 20;
        private const int BoolFieldWidth = 30;

        // private BGDatabaseMTManager manager;
        private MetaWithNameIndexTable metaWithNameIndexTable;
        private PartialKeysTable partialKeysTable;

        private BGDatabaseMTManager Manager => (BGDatabaseMTManager)serializedObject.targetObject;

        public override void OnInspectorGUI()
        {
            if (!BGRepo.DefaultRepoLoaded) BGRepo.Load();

            if (metaWithNameIndexTable == null) metaWithNameIndexTable = new MetaWithNameIndexTable(Manager);
            if (partialKeysTable == null) partialKeysTable = new PartialKeysTable(Manager);

            BGEditorUtility.UniformStyle(() =>
            {
                var mode = (BGDatabaseMTManager.InitializationModeEnum)EditorGUILayout.EnumPopup("Execute on ", Manager.initializationMode, BGStyle.Editor_MiniPopup);
                if (mode != Manager.initializationMode)
                {
                    Undo.RecordObject(Manager, "mode changed");
                    Manager.initializationMode = mode;
                }

                //validate asset
                BGEditorUtility.EditorToggle(
                    new GUIContent("Validate loaded asset", "this parameter ensures, that the asset was indeed loaded if some path is stored inside database"),
                    Manager.validateLoadedAsset,
                    newValue =>
                    {
                        Undo.RecordObject(Manager, "enabled changed");
                        Manager.validateLoadedAsset = newValue;
                    }
                );

                //show benchmark
                BGEditorUtility.EditorToggle(new GUIContent("Print benchmarks", "show loading performance benchmark"), Manager.showBenchMark, newValue =>
                    {
                        Undo.RecordObject(Manager, "benchmark changed");
                        Manager.showBenchMark = newValue;
                    }
                );
                //show log GUI (for builds)
                BGEditorUtility.EditorToggle(new GUIContent("Show log GUI", "show performance log on screen (for builds)"), Manager.showLogGUI, newValue =>
                    {
                        Undo.RecordObject(Manager, "showLogGUI changed");
                        Manager.showLogGUI = newValue;
                    }
                );

                BGEditorUtility.EditorToggle(new GUIContent("Async assets loading", "Enable asynchronous assets loading"), Manager.asyncAssetsLoading, b =>
                {
                    Undo.RecordObject(Manager, "async mode changed");
                    Manager.asyncAssetsLoading = b;
                });
                BGEditorUtility.EditorToggle(new GUIContent("Disable VSync", "Disable VSync while assets loading, cause it seems like vSync affects performance badly"), 
                    Manager.disableVSync, b =>
                {
                    Undo.RecordObject(Manager, "disableVSync changed");
                    Manager.disableVSync = b;
                });
                BGEditorUtility.EditorToggle(new GUIContent("Test assets after loading", "Make sure the asset is loaded if database has some reference to the asset." +
                                                                                         " Test is executed on dedicated thread, not on the main thread"), 
                    Manager.testAccess, b =>
                {
                    Undo.RecordObject(Manager, "testAccess changed");
                    Manager.testAccess = b;
                });

                metaWithNameIndexTable.Gui();

                GUILayout.Space(4);

                partialKeysTable.Gui();

                GUILayout.Space(4);

                var tempObject = new SerializedObject(Manager);
                EditorGUI.BeginChangeCheck();
                EditorGUILayout.PropertyField(tempObject.FindProperty(nameof(Manager.onFinished)));
                if (EditorGUI.EndChangeCheck()) tempObject.ApplyModifiedProperties();
            });
        }

        private static float GetWidth() => EditorGUIUtility.currentViewWidth - 40;

        private static void ObjectsList2Ids<T>(List<string> ids, HashSet<T> objects, Func<T, string> getValue) where T : BGMetaObject
        {
            ids.Clear();
            if (objects == null || objects.Count <= 0) return;
            foreach (var obj in objects) ids.Add(getValue(obj));
        }


        //==================================================================================
        //  name index
        //==================================================================================
        private class MetaWithNameIndexTable : BGTableView<BGMetaEntity>
        {
            private readonly BGDatabaseMTManager manager;
            protected readonly List<BGMetaEntity> list = new List<BGMetaEntity>();

            public MetaWithNameIndexTable(BGDatabaseMTManager manager) : base("Tables with 'name' index used", null)
            {
                Provider = ProviderMethod;
                this.manager = manager;
                Add(new ColumnString("Name", meta => meta.Name)
                {
                    GetWidth = GetWidth
                });
            }

            protected override void OnHeaderDraw()
            {
                if (!GUILayout.Button("Modify", BGStyle.Button, GUILayout.Width(60), GUILayout.Height(18))) return;
                var table = new MetaWithNameIndexTableEditable(manager);
                BGPopup.Popup("Modify tables with 'name' index", PopupWidth, 200, popup => table.Gui());
            }

            protected virtual List<BGMetaEntity> ProviderMethod()
            {
                list.Clear();
                list.AddRange(manager.MetasWithNameIndex);
                return list;
            }
        }

        private class MetaWithNameIndexTableEditable : MetaWithNameIndexTable
        {
            public MetaWithNameIndexTableEditable(BGDatabaseMTManager manager) : base(manager)
            {
                UseScrollView = true;
                GetColumn(0).GetWidth = () => PopupWidth - PopupWidthOffset - BoolFieldWidth;
                Add(new ColumnBool("I", meta => manager.MetasWithNameIndex.Contains(meta), (meta, newValue) =>
                {
                    Undo.RecordObject(manager, $"meta {meta.Name} is " + (newValue ? "included" : "excluded"));
                    var set = manager.MetasWithNameIndex;
                    if (newValue) set.Add(meta);
                    else set.Remove(meta);
                    ObjectsList2Ids(manager.NameIndexMetaIds, set, metaObj => metaObj.Id.ToString());
                })
                {
                    GetWidth = () => BoolFieldWidth
                });
            }

            protected override void OnHeaderDraw()
            {
            }

            protected override List<BGMetaEntity> ProviderMethod() => BGRepo.I.FindMetas(list);
        }

        //==================================================================================
        //  partial keys
        //==================================================================================
        private class PartialKeysTable : BGTableView<BGKey>
        {
            private readonly BGDatabaseMTManager manager;
            protected readonly List<BGKey> list = new List<BGKey>();

            public PartialKeysTable(BGDatabaseMTManager manager) : base("Partial keys used", null)
            {
                Provider = ProviderMethod;
                this.manager = manager;
                Add(new ColumnString("Name", key => key.FullName)
                {
                    GetWidth = GetWidth
                });
            }

            protected override void OnHeaderDraw()
            {
                if (!GUILayout.Button("Modify", BGStyle.Button, GUILayout.Width(60), GUILayout.Height(18))) return;
                var table = new PartialKeysTableEditable(manager);
                BGPopup.Popup("Partial access keys", PopupWidth, 200, popup => table.Gui());
            }

            protected virtual List<BGKey> ProviderMethod()
            {
                list.Clear();
                list.AddRange(manager.PartialAccessKeys);
                return list;
            }
        }

        private class PartialKeysTableEditable : PartialKeysTable
        {
            public PartialKeysTableEditable(BGDatabaseMTManager manager) : base(manager)
            {
                UseScrollView = true;
                GetColumn(0).GetWidth = () => PopupWidth - PopupWidthOffset - BoolFieldWidth;
                Add(new ColumnBool("I", key => manager.PartialAccessKeys.Contains(key), (key, newValue) =>
                {
                    Undo.RecordObject(manager, $"key {key.FullName} is " + (newValue ? "included" : "excluded"));
                    var set = manager.PartialAccessKeys;
                    if (newValue) set.Add(key);
                    else set.Remove(key);
                    ObjectsList2Ids(manager.PartialKeysIds, set, keyObj => keyObj.Meta.Id.ToString() + '.' + keyObj.Id.ToString());
                })
                {
                    GetWidth = () => BoolFieldWidth
                });
            }

            protected override void OnHeaderDraw()
            {
            }

            protected override List<BGKey> ProviderMethod()
            {
                list.Clear();
                BGRepo.I.ForEachMeta(meta => { meta.ForEachKey(key => list.Add(key), key => key.CountFields > 1); }, meta => meta.CountKeys > 0);
                return list;
            }
        }
    }
}