// Copyright (c) Wojciech Figat. All rights reserved.

using System;
using FlaxEditor.GUI;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.Utilities;

namespace FlaxEditor.CustomEditors.Editors
{
    /// <summary>
    /// Base implementation of the inspector used to edit properties of the given abstract or interface type that contain a setter to assign a derived implementation type.
    /// </summary>
    public abstract class ObjectSwitcherEditor : CustomEditor
    {
        /// <summary>
        /// Defines type that can be assigned to the modified property.
        /// </summary>
        public struct OptionType
        {
            /// <summary>
            /// The type name used to show in the type selector dropdown menu (for user interface).
            /// </summary>
            public string Name;

            /// <summary>
            /// The type.
            /// </summary>
            public Type Type;

            /// <summary>
            /// The creator function that spawns the object of the given type.
            /// </summary>
            public Func<Type, object> Creator;

            /// <summary>
            /// Initializes a new instance of the <see cref="OptionType"/> struct.
            /// </summary>
            /// <param name="type">The type.</param>
            public OptionType(Type type)
            {
                Name = type.Name;
                Type = type;
                Creator = Activator.CreateInstance;
            }

            /// <summary>
            /// Initializes a new instance of the <see cref="OptionType"/> struct.
            /// </summary>
            /// <param name="name">The name.</param>
            /// <param name="type">The type.</param>
            public OptionType(string name, Type type)
            {
                Name = name;
                Type = type;
                Creator = Activator.CreateInstance;
            }

            /// <summary>
            /// Initializes a new instance of the <see cref="OptionType"/> struct.
            /// </summary>
            /// <param name="name">The name.</param>
            /// <param name="type">The type.</param>
            /// <param name="creator">The instance creation function.</param>
            public OptionType(string name, Type type, Func<Type, object> creator)
            {
                Name = name;
                Type = type;
                Creator = creator;
            }
        }

        /// <summary>
        /// Gets the options collection for the property value assignment.
        /// </summary>
        protected abstract OptionType[] Options { get; }

        /// <summary>
        /// Gets the name of the type ComboBox property name for the object type picking.
        /// </summary>
        protected virtual string TypeComboBoxName => "Type";

        private OptionType[] _options;
        private ScriptType _type;
        private Elements.PropertiesListElement _typeItem;

        private ScriptType Type => Values[0] == null ? Values.Type : TypeUtils.GetObjectType(Values[0]);

        /// <inheritdoc />
        public override bool RevertValueWithChildren => false; // Always revert value for a whole object

        /// <inheritdoc />
        public override void Initialize(LayoutElementsContainer layout)
        {
            // Get the target options
            _options = Options;
            if (_options == null)
                throw new ArgumentNullException();

            int selectedIndex = -1;
            bool hasDifferentTypes = Values.HasDifferentTypes;
            var type = Type;
            _type = type;

            // Type
            _typeItem = layout.AddPropertyItem(TypeComboBoxName, "Type of the object value. Use it to change the object.");
            var typeEditor = _typeItem.ComboBox();
            for (int i = 0; i < _options.Length; i++)
            {
                typeEditor.ComboBox.AddItem(_options[i].Name);
                selectedIndex = _options[i].Type == type.Type ? i : selectedIndex;
            }
            typeEditor.ComboBox.SupportMultiSelect = false;
            typeEditor.ComboBox.SelectedIndex = hasDifferentTypes ? -1 : selectedIndex;
            typeEditor.ComboBox.SelectedIndexChanged += OnSelectedIndexChanged;

            // Editing different types is not supported
            if (Values.HasDifferentTypes)
            {
                var property = layout.AddPropertyItem("Value");
                property.Label("Different Values");
                return;
            }

            // Nothing to edit
            if (Values.HasNull)
            {
                var property = layout.AddPropertyItem("Value");
                property.Label("<null>");
                return;
            }

            // Value
            var values = new CustomValueContainer(type, (instance, index) => instance);
            if (Values.HasReferenceValue)
                values.SetReferenceValue(Values.ReferenceValue);
            values.AddRange(Values);
            var editor = CustomEditorsUtil.CreateEditor(type);
            var style = editor.Style;
            if (style == DisplayStyle.InlineIntoParent)
            {
                layout.Object(values, editor);
            }
            else if (style == DisplayStyle.Group)
            {
                var group = layout.Group("Value", true);
                group.Panel.Open(false);
                group.Object(values, editor);

                // Remove empty group
                if (group.ContainerControl.ChildrenCount == 0)
                {
                    layout.Children.Remove(group);
                    group.Panel.Dispose();
                }
            }
            else
            {
                layout.AddPropertyItem("Value").Object(values, editor);
            }
        }

        private void OnSelectedIndexChanged(ComboBox comboBox)
        {
            object value = null;
            if (comboBox.SelectedIndex != -1)
            {
                var option = _options[comboBox.SelectedIndex];
                if (option.Type != null)
                    value = option.Creator(option.Type);
            }
            SetValue(value);
            RebuildLayoutOnRefresh();
        }

        /// <inheritdoc />
        public override void Refresh()
        {
            base.Refresh();

            // Show prefab diff when reference value type is different
            var color = Color.Transparent;
            if (Values.HasReferenceValue && CanRevertReferenceValue && Values[0]?.GetType() != Values.ReferenceValue?.GetType())
                color = FlaxEngine.GUI.Style.Current.BackgroundSelected;
            _typeItem.Labels[0].HighlightStripColor = color;

            // Check if type has been modified outside the editor (eg. from code)
            if (Type != _type)
            {
                if (ParentEditor != null)
                    ParentEditor.RebuildLayout();
                else
                    RebuildLayout();
            }
        }
    }
}
