// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.

using System;
using System.Collections.Generic;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Tree;
using FlaxEditor.SceneGraph;
using FlaxEngine;
using FlaxEngine.GUI;

namespace FlaxEditor.Windows.Assets
{
    public sealed partial class PrefabWindow
    {
        /// <summary>
        /// The custom implementation of the root node for the scene graph.
        /// </summary>
        public class CustomRootNode : RootNode
        {
            private readonly PrefabWindow _window;

            /// <summary>
            /// Initializes a new instance of the <see cref="CustomRootNode"/> class.
            /// </summary>
            /// <param name="window">The window.</param>
            public CustomRootNode(PrefabWindow window)
            {
                _window = window;
            }

            /// <inheritdoc />
            public override void Spawn(Actor actor, Actor parent)
            {
                _window.Spawn(actor, parent);
            }

            /// <inheritdoc />
            public override Undo Undo => _window.Undo;
        }

        /// <summary>
        /// The prefab hierarchy tree control.
        /// </summary>
        /// <seealso cref="Tree" />
        public class PrefabTree : Tree
        {
            /// <summary>
            /// Initializes a new instance of the <see cref="PrefabTree"/> class.
            /// </summary>
            public PrefabTree()
            : base(true)
            {
            }
        }

        /// <summary>
        /// Occurs when prefab hierarchy panel wants to show the context menu. Allows to add custom options. Applies to all prefab windows.
        /// </summary>
        public static event Action<ContextMenu> ContextMenuShow;

        /// <summary>
        /// Creates the context menu for the current objects selection.
        /// </summary>
        /// <returns>The context menu.</returns>
        private ContextMenu CreateContextMenu()
        {
            // Prepare

            bool hasSthSelected = Selection.Count > 0;
            bool isSingleActorSelected = Selection.Count == 1 && Selection[0] is ActorNode;
            bool isRootSelected = isSingleActorSelected && Selection[0] == Graph.Main;

            // Create popup

            var contextMenu = new ContextMenu();
            contextMenu.MinimumWidth = 120;

            // Basic editing options

            var b = contextMenu.AddButton("Rename", Rename);
            b.Enabled = isSingleActorSelected;

            b = contextMenu.AddButton("Duplicate", Duplicate);
            b.Enabled = hasSthSelected && !isRootSelected;

            b = contextMenu.AddButton("Delete", Delete);
            b.Enabled = hasSthSelected && !isRootSelected;

            contextMenu.AddSeparator();
            b = contextMenu.AddButton("Copy", Copy);
            b.Enabled = hasSthSelected;

            b.Enabled = hasSthSelected;
            contextMenu.AddButton("Paste", Paste);

            b = contextMenu.AddButton("Cut", Cut);
            b.Enabled = hasSthSelected && !isRootSelected;

            b = contextMenu.AddButton("Set Root", SetRoot);
            b.Enabled = isSingleActorSelected && !isRootSelected;

            // Prefab options

            contextMenu.AddSeparator();

            b = contextMenu.AddButton("Create Prefab", Editor.Prefabs.CreatePrefab);
            b.Enabled = isSingleActorSelected &&
                        (Selection[0] as ActorNode).CanCreatePrefab &&
                        Editor.Windows.ContentWin.CurrentViewFolder.CanHaveAssets;

            bool hasPrefabLink = isSingleActorSelected && (Selection[0] as ActorNode).HasPrefabLink;

            b = contextMenu.AddButton("Select Prefab", Editor.Prefabs.SelectPrefab);
            b.Enabled = hasPrefabLink;

            // Spawning actors options

            contextMenu.AddSeparator();
            var spawnMenu = contextMenu.AddChildMenu("New");
            var newActorCm = spawnMenu.ContextMenu;
            for (int i = 0; i < SceneTreeWindow.SpawnActorsGroups.Length; i++)
            {
                var group = SceneTreeWindow.SpawnActorsGroups[i];

                if (group.Types.Length == 1)
                {
                    var type = group.Types[0].Value;
                    newActorCm.AddButton(group.Types[0].Key, () => Spawn(type));
                }
                else
                {
                    var groupCm = newActorCm.AddChildMenu(group.Name).ContextMenu;
                    for (int j = 0; j < group.Types.Length; j++)
                    {
                        var type = group.Types[j].Value;
                        groupCm.AddButton(group.Types[j].Key, () => Spawn(type));
                    }
                }
            }

            // Custom options

            ContextMenuShow?.Invoke(contextMenu);

            return contextMenu;
        }

        /// <summary>
        /// Shows the context menu on a given location (in the given control coordinates).
        /// </summary>
        /// <param name="parent">The parent control.</param>
        /// <param name="location">The location (within a given control).</param>
        private void ShowContextMenu(Control parent, ref Vector2 location)
        {
            var contextMenu = CreateContextMenu();

            contextMenu.Show(parent, location);
        }

        private void Rename()
        {
            ((ActorNode)Selection[0]).TreeNode.StartRenaming();
        }

        /// <summary>
        /// Spawns the specified actor to the prefab (adds actor to root).
        /// </summary>
        /// <param name="actor">The actor.</param>
        public void Spawn(Actor actor)
        {
            // Link to parent
            Actor parentActor = null;
            if (Selection.Count > 0 && Selection[0] is ActorNode actorNode)
            {
                parentActor = actorNode.Actor;
                actorNode.TreeNode.Expand();
            }
            if (parentActor == null)
            {
                parentActor = Graph.MainActor;
            }
            if (parentActor != null)
            {
                // Use the same location
                actor.Transform = parentActor.Transform;

                // Rename actor to identify it easily
                actor.Name = StringUtils.IncrementNameNumber(actor.GetType().Name, x => parentActor.GetChild(x) == null);
            }

            // Spawn it
            Spawn(actor, parentActor);
        }

        /// <summary>
        /// Spawns the actor of the specified type to the prefab (adds actor to root).
        /// </summary>
        /// <param name="type">The actor type.</param>
        public void Spawn(Type type)
        {
            // Create actor
            Actor actor = (Actor)FlaxEngine.Object.New(type);

            // Spawn it
            Spawn(actor);
        }

        /// <summary>
        /// Spawns the specified actor.
        /// </summary>
        /// <param name="actor">The actor.</param>
        /// <param name="parent">The parent.</param>
        public void Spawn(Actor actor, Actor parent)
        {
            if (actor == null)
                throw new ArgumentNullException(nameof(actor));
            if (parent == null)
                throw new ArgumentNullException(nameof(parent));

            // Link it
            actor.Parent = parent;

            // Peek spawned node
            var actorNode = SceneGraphFactory.FindNode(actor.ID) as ActorNode ?? SceneGraphFactory.BuildActorNode(actor);
            if (actorNode == null)
                throw new InvalidOperationException("Failed to create scene node for the spawned actor.");
            var parentNode = SceneGraphFactory.FindNode(parent.ID) as ActorNode;
            if (parentNode == null)
                throw new InvalidOperationException("Missing scene graph node for the spawned parent actor.");
            actorNode.ParentNode = parentNode;

            // Call post spawn action (can possibly setup custom default values)
            actorNode.PostSpawn();

            // Create undo action
            var action = new CustomDeleteActorsAction(new List<SceneGraphNode>(1) { actorNode }, true);
            Undo.AddAction(action);
        }

        private void OnTreeRightClick(TreeNode node, Vector2 location)
        {
            // Skip if it's loading data
            if (Graph.Main == null)
                return;

            ShowContextMenu(node, ref location);
        }

        private void Update(ActorNode actorNode)
        {
            actorNode.TreeNode.OnNameChanged();
            actorNode.TreeNode.OnOrderInParentChanged();

            for (int i = 0; i < actorNode.ChildNodes.Count; i++)
            {
                if (actorNode.ChildNodes[i] is ActorNode child)
                    Update(child);
            }
        }
    }
}
