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


using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;

namespace BansheeGz.BGDatabase.Editor
{
    public static class BGDBUiToolkitBuilderIntegration
    {
        private static readonly List<VisualElement> Selected = new List<VisualElement>();
        private static MethodInfo selectionMethod;
        private static PropertyInfo builderActiveWindowProperty;
        private static PropertyInfo builderSelectionProperty;

        private static PropertyInfo selectionProperty;

        private static string InitReflection()
        {
            if (!GetType("Unity.UI.Builder.Builder", out var builderType)) return "UI Toolkit Builder package is not installed!";
            if (!GetType("Unity.UI.Builder.BuilderSelection", out var selectionType)) return "Unexpected error: can not find Unity.UI.Builder.BuilderSelection type!";

            string error = null;
            if (!GetProperty(builderType, "ActiveWindow", ref builderActiveWindowProperty, ref error)) return error;
            if (!GetProperty(builderType, "selection", ref builderSelectionProperty, ref error)) return error;
            // if (!GetProperty(builderType, "documentRootElement", ref documentRootProperty, ref error)) return error;

            if (!GetProperty(selectionType, "selection", ref selectionProperty, ref error)) return error;

            return null;
        }

        private static bool GetType(string name, out Type type)
        {
            type = BGUtil.GetType(name);
            return type != null;
        }

        private static bool GetProperty(Type type, string name, ref PropertyInfo property, ref string error)
        {
            error = null;
            property = type.GetProperty(name);
            if (property == null) error = $"Unexpected error: can not find {type.FullName}.{name} property!";
            return error == null;
        }

        public static void OnGUI(BGDBUiToolkitGoEditor editor)
        {
            var doc = editor.Toolkit.Doc;
            try
            {
                var error = InitReflection();
                if (error != null)
                {
                    ShowError(error);
                    return;
                }

                if (doc.visualTreeAsset == null)
                {
                    ShowError("Visual tree asset is not set on UIDocument");
                    return;
                }

                var activeWindow = builderActiveWindowProperty.GetValue(null);
                if (activeWindow == null) ShowError("UI Builder window is not opened");
                else
                {
                    var selection = builderSelectionProperty.GetValue(activeWindow);
                    if (selection == null) ShowError("Visual element is not selected");
                    else
                    {
                        Selected.Clear();
                        var selectedElements = (IEnumerable<VisualElement>)selectionProperty.GetValue(selection);
                        if (selectedElements != null) Selected.AddRange(selectedElements);

                        if (Selected.Count == 0) ShowError("Visual element is not selected");
                        else if (Selected.Count > 1) ShowError("Multiple elements are selected (should be single)");
                        else
                        {
                            ShowElement(editor.Toolkit, Selected[0]);
                        }

                        //there is no event fired on element change (17 preview package)
                        //this is pretty expensive (repainting every frame)
                        editor.Repaint();
                    }
                }
            }
            catch (Exception e)
            {
                Debug.LogException(e);
                ShowError(e.Message);
            }

            Selected.Clear();
        }

        private static void ShowElement(BGDataBinderUiToolkitGo toolkit, VisualElement element)
        {
            if (string.IsNullOrEmpty(element.name)) ShowError($"Selected: [No name!] ({element.GetType().Name})");
            else
            {
                if (!CanBeFound(toolkit, element)) ShowError($"Selected element #{element.name}({element.GetType().Name}) from a different tree");
                else
                {
                    BGEditorUtility.Horizontal(() =>
                    {
                        BGEditorUtility.Label("Selected element #");
                        GUILayout.TextField(element.name, BGStyle.Editor_textField);
                        BGEditorUtility.Label($"({element.GetType().Name})");
                        GUILayout.FlexibleSpace();
                        if (GUILayout.Button("Add binder", BGStyle.Button))
                        {
                            BGDBUiToolkitBindersTable.AddBuilder(toolkit, element);
                        }
                    });
                    var binders = toolkit.GetBinders(element.name);
                    if (binders != null && binders.Count > 0)
                    {
                        for (var i = 0; i < binders.Count; i++)
                        {
                            var binder = binders[i];
                            ShowBinder(i, toolkit, binder);
                        }
                    }
                }
            }
        }

        private static bool CanBeFound(BGDataBinderUiToolkitGo toolkit, VisualElement element)
        {
            var doc = toolkit.Doc;
            if (doc == null) return false;
            var root = doc.rootVisualElement;
            if (root == null) return false;
            var foundElement = root.Q(element.name);
            return foundElement != null;
        }

        private static void ShowBinder(int index, BGDataBinderUiToolkitGo toolkit, BGDBUiElementBinderA binder)
        {
            BGEditorUtility.Vertical(BGStyle.Pane, () =>
            {
                BGEditorUtility.Horizontal(() =>
                {
                    GUILayout.Label($"Binder #{index} ({binder.Title})", BGStyle.TableName);
                    GUILayout.FlexibleSpace();
                    if (!GUILayout.Button(new GUIContent("", "Delete a binder"), BGStyle.ButtonDelete, BGEditorUtility.GetOptions(19, 19))) return;
                    Undo.RecordObject(toolkit, "Removing a binder");
                    toolkit.Remove(binder);
                });

                BGEditorUtility.Vertical(BGStyle.TableBack, () =>
                {
                    const int labelWidth = 40;

                    BGEditorUtility.Horizontal(() =>
                    {
                        BGEditorUtility.Label("Live", labelWidth);

                        var newLiveUpdate = GUILayout.Toggle(binder.LiveUpdate, "", BGStyle.Editor_toggle);
                        if (newLiveUpdate != binder.LiveUpdate)
                        {
                            Undo.RecordObject(toolkit, "Changing binder's live parameter");
                            binder.LiveUpdate = newLiveUpdate;
                        }
                    });

                    BGEditorUtility.Horizontal(() =>
                    {
                        BGEditorUtility.Label("Target Field", labelWidth);
                        BGDBUiToolkitBindersTable.ShowElementFieldButton(toolkit, binder, null);
                    });


                    if (binder is BGDBUiElementBinderRowBasedA rowBasedBinder)
                    {
                        BGEditorUtility.Horizontal(() =>
                        {
                            BGEditorUtility.Label("Meta", labelWidth);
                            BGDBUiToolkitBindersTable.ShowMetaButton(toolkit, rowBasedBinder, null);
                        });
                        BGEditorUtility.Horizontal(() =>
                        {
                            using (BGUsingEditor.DisableGUI(rowBasedBinder.Meta == null))
                            {
                                BGEditorUtility.Label("Entity", labelWidth);
                                BGDBUiToolkitBindersTable.ShowEntityButton(rowBasedBinder, toolkit, null);
                            }
                        });
                    }

                    if (binder is BGDBUiElementBinderField fieldBinder)
                    {
                        BGEditorUtility.Horizontal(() =>
                        {
                            using (BGUsingEditor.DisableGUI(fieldBinder.Meta == null))
                            {
                                BGEditorUtility.Label("Field", labelWidth);
                                BGDBUiToolkitBindersTable.ShowFieldButton(toolkit, fieldBinder, null);
                            }
                        });
                    }

                    if (binder is BGDBUiElementBinderTemplate templateBinder)
                    {
                        var newTemplate = EditorGUILayout.TextArea(templateBinder.Template, new GUIStyle(BGStyle.Editor_textArea) { stretchWidth = true }, GUILayout.Height(80));
                        if (newTemplate != templateBinder.Template)
                        {
                            Undo.RecordObject(toolkit, "Changing binder template");
                            templateBinder.Template = newTemplate;
                        }
                    }
                    if (binder is BGDBUiElementBinderGraph graphBinder)
                    {
                        BGEditorUtility.Horizontal(() =>
                        {
                            BGEditorUtility.Label("Graph", (int)EditorGUIUtility.labelWidth);
                            if(BGEditorUtility.Button("Edit (" + graphBinder.Graph.UnitsCount +")"))
                            {
                                var graph = graphBinder.Graph;
                                graph.ClearOnAnyChange();
                                graph.OnAnyChange += () =>
                                {
                                    if (toolkit == null) return;
                                    Undo.RecordObject(toolkit, "Changing binder graph");
                                    graphBinder.graphContent = Convert.ToBase64String(graph.ToBytes());
                                };
                                BGCalcWindow.OpenWindow(new BGCalcGraphViewContext(() => toolkit==null?null:graph, graphBinder.TypeCode, BGCalcGraphTypeEnum.GraphBinder));
                            }
                        });
                    }

                    BGEditorUtility.Horizontal(() =>
                    {
                        BGEditorUtility.Label("Value", labelWidth);
                        BGDBUiToolkitBindersTable.ShowResult(binder);
                    });
                });
            });
        }

        private static void ShowError(string message) => BGEditorUtility.HelpBox(message, MessageType.Info);
    }
}