using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using UnityEditor.IMGUI.Controls;
using MBT;

namespace MBTEditor
{    
    public class BehaviourTreeWindow : EditorWindow, IHasCustomMenu
    {
        private MonoBehaviourTree currentMBT;
        private Editor currentMBTEditor;
        private Node[] currentNodes;
        private Node selectedNode;
        private bool nodeMoved = false;
        private Vector2 workspaceOffset;
        private NodeHandle currentHandle;
        private NodeHandle dropdownHandleCache;
        private bool snapNodesToGrid;
        private bool locked = false;

        private Rect nodeFinderActivatorRect;
        private NodeDropdown nodeDropdown;
        private Vector2 nodeDropdownTargetPosition;

        private readonly float _handleDetectionDistance = 8f;
        private readonly Color _editorBackgroundColor = new Color(0.16f, 0.19f, 0.25f, 1);
        private GUIStyle _lockButtonStyle;
        private GUIStyle _defaultNodeStyle;
        private GUIStyle _selectedNodeStyle;
        private GUIStyle _successNodeStyle;
        private GUIStyle _failureNodeStyle;
        private GUIStyle _runningNodeStyle;
        private GUIStyle _nodeContentBoxStyle;
        private GUIStyle _nodeLabelStyle;
        private GUIStyle _nodeBreakpointLabelStyle;

        private void OnEnable()
        {
            // Read snap option
            snapNodesToGrid = EditorPrefs.GetBool("snapNodesToGrid", true);
            // Setup events
            EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
            EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
            Undo.undoRedoPerformed -= UpdateSelection;
            Undo.undoRedoPerformed += UpdateSelection;
            // Node finder
            nodeDropdown = new NodeDropdown(new AdvancedDropdownState(), AddNode);
            // Standard node
            _defaultNodeStyle = new GUIStyle();
            _defaultNodeStyle.border = new RectOffset(10,10,10,10);
            _defaultNodeStyle.normal.background = Resources.Load("mbt_node_default", typeof(Texture2D)) as Texture2D;
            // Selected node
            _selectedNodeStyle = new GUIStyle();
            _selectedNodeStyle.border = new RectOffset(10,10,10,10);
            _selectedNodeStyle.normal.background = Resources.Load("mbt_node_selected", typeof(Texture2D)) as Texture2D;
            // Success node
            _successNodeStyle = new GUIStyle();
            _successNodeStyle.border = new RectOffset(10,10,10,10);
            _successNodeStyle.normal.background = Resources.Load("mbt_node_success", typeof(Texture2D)) as Texture2D;
            // Failure node
            _failureNodeStyle = new GUIStyle();
            _failureNodeStyle.border = new RectOffset(10,10,10,10);
            _failureNodeStyle.normal.background = Resources.Load("mbt_node_failure", typeof(Texture2D)) as Texture2D;
            // Running node
            _runningNodeStyle = new GUIStyle();
            _runningNodeStyle.border = new RectOffset(10,10,10,10);
            _runningNodeStyle.normal.background = Resources.Load("mbt_node_running", typeof(Texture2D)) as Texture2D;
            // Node content box
            _nodeContentBoxStyle = new GUIStyle();
            _nodeContentBoxStyle.padding = new RectOffset(0,0,15,15);
            // Node label
            _nodeLabelStyle = new GUIStyle();
            _nodeLabelStyle.normal.textColor = Color.white;
            _nodeLabelStyle.alignment = TextAnchor.MiddleCenter;
            _nodeLabelStyle.wordWrap = true;
            _nodeLabelStyle.margin = new RectOffset(10,10,10,10);
            _nodeLabelStyle.font = Resources.Load("mbt_Lato-Regular", typeof(Font)) as Font;
            _nodeLabelStyle.fontSize = 12;
            // Node label when breakpoint is set to true
            _nodeBreakpointLabelStyle = new GUIStyle(_nodeLabelStyle);
            _nodeBreakpointLabelStyle.normal.textColor = new Color(1f, 0.35f, 0.18f);
        }
    
        private void OnDisable()
        {
            EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
            Undo.undoRedoPerformed -= UpdateSelection;
        }

        [MenuItem("Window/Mono Behaviour Tree")]
        public static void OpenEditor()
        {
            BehaviourTreeWindow window = GetWindow<BehaviourTreeWindow>();
            window.titleContent = new GUIContent(
                "Behaviour Tree",
                Resources.Load("mbt_window_icon", typeof(Texture2D)) as Texture2D
            );
        }

        void OnGUI()
        {
            // Draw grid in background first
            PaintBackground();

            // If there is no tree to render just skip rest
            if (currentMBT == null) {
                // Keep toolbar rendered
                PaintWindowToolbar();
                return;
            }

            PaintConnections(Event.current);

            // Repaint nodes
            PaintNodes();

            PaintWindowToolbar();

            // Update selection and drag
            ProcessEvents(Event.current);

            if (GUI.changed) Repaint();
        }

        private void PaintConnections(Event e)
        {
            // Paint line when dragging connection
            if (currentHandle != null) {
                Handles.BeginGUI();
                Vector3 p1 = new Vector3(currentHandle.position.x, currentHandle.position.y, 0f);
                Vector3 p2 = new Vector3(e.mousePosition.x, e.mousePosition.y, 0f);
                Handles.DrawBezier(p1, p2, p1, p2, new Color(0.3f, 0.36f, 0.5f), null, 4f);
                Handles.EndGUI();
            }
            // Paint all current connections
            for (int i = 0; i < currentNodes.Length; i++)
            {
                Node n = currentNodes[i];
                Vector3 p1 = GetBottomHandlePosition(n.rect) + workspaceOffset;
                for (int j = 0; j < n.children.Count; j++)
                {
                    Handles.BeginGUI();
                    Vector3 p2 = GetTopHandlePosition(n.children[j].rect) + workspaceOffset;
                    Handles.DrawBezier(p1, p2, p1, p2, new Color(0.3f, 0.36f, 0.5f), null, 4f);
                    Handles.EndGUI();   
                }
            }
        }

        private void PaintWindowToolbar()
        {
            EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
                EditorGUI.BeginDisabledGroup(currentMBT == null);
                if (GUILayout.Toggle(snapNodesToGrid, "Snap Nodes", EditorStyles.toolbarButton) != snapNodesToGrid)
                {
                    snapNodesToGrid = !snapNodesToGrid;
                    // Store this setting
                    EditorPrefs.SetBool("snapNodesToGrid", snapNodesToGrid);
                }
                // TODO: Autolayout
                // if (GUILayout.Button("Auto Layout", EditorStyles.toolbarButton)){
                //     Debug.Log("Auto layout is not implemented.");
                // }
                EditorGUILayout.Space();
                if (GUILayout.Button("Focus Root", EditorStyles.toolbarButton)){
                    FocusRoot();
                }
                if (GUILayout.Button("Select In Hierarchy", EditorStyles.toolbarButton)){
                    if (currentMBT != null)
                    {
                        Selection.activeGameObject = currentMBT.gameObject;
                        EditorGUIUtility.PingObject(currentMBT.gameObject);
                    }
                }
                if (GUILayout.Button("Add Node", EditorStyles.toolbarDropDown)){
                    OpenNodeFinder(nodeFinderActivatorRect, false);
                }
                if (Event.current.type == EventType.Repaint) nodeFinderActivatorRect = GUILayoutUtility.GetLastRect();
                EditorGUI.EndDisabledGroup();
                GUILayout.FlexibleSpace();
                if (currentMBT != null)
                {
                    GUILayout.Label(string.Format("{0} {1}", currentMBT.name, -workspaceOffset));
                }
            EditorGUILayout.EndHorizontal();
        }

        void FocusRoot()
        {
            Root rootNode = null;
            for (int i = 0; i < currentNodes.Length; i++)
            {
                if (currentNodes[i] is Root) {
                    rootNode = currentNodes[i] as Root;
                    break;
                }
            }
            if (rootNode != null) {
                workspaceOffset = -rootNode.rect.center + new Vector2(this.position.width/2, this.position.height/2);
            }
        }

        private void OnPlayModeStateChanged(PlayModeStateChange state)
        {
            // Disable lock when changing state
            this.locked = false;
            UpdateSelection();
            Repaint();
        }

        void OnInspectorUpdate()
        {
            // OPTIMIZE: This can be optimized to call repaint once per second
            Repaint();
        }

        void OnSelectionChange() 
        {
            MonoBehaviourTree previous = currentMBT;
            UpdateSelection();
            // Reset workspace position only when selection changed
            if (previous != currentMBT)
            {
                workspaceOffset = Vector2.zero;
            }
            Repaint();
        }

        void OnFocus() 
        {
            UpdateSelection();
            Repaint();
        }

        void IHasCustomMenu.AddItemsToMenu(GenericMenu menu)
        {
            menu.AddItem(new GUIContent("Lock"), this.locked, () => {
                this.locked = !this.locked;
                UpdateSelection();
            });
        }

        // http://leahayes.co.uk/2013/04/30/adding-the-little-padlock-button-to-your-editorwindow.html
        private void ShowButton(Rect position)
        {
            // Cache style
            if (_lockButtonStyle == null) {
                _lockButtonStyle = "IN LockButton";
            }
            // Generic menu button
            GUI.enabled = currentMBT != null;
            this.locked = GUI.Toggle(position, this.locked, GUIContent.none, _lockButtonStyle);
            GUI.enabled = true;
        }

        // DeselectNode cannot be called here
        // void OnLostFocus()
        // {
        //     // DeselectNode();
        // }

        private void UpdateSelection()
        {
            MonoBehaviourTree prevMBT = currentMBT;
            if (!this.locked && Selection.activeGameObject != null)
            {
                currentMBT = Selection.activeGameObject.GetComponent<MonoBehaviourTree>();
                // If new selection is null then restore previous one
                if (currentMBT == null)
                {
                    currentMBT = prevMBT;
                }
            }
            if (currentMBT != prevMBT)
            {
                // Get new editor for new MBT
                Editor.CreateCachedEditor(currentMBT, null, ref currentMBTEditor);
            }
            if (currentMBT != null) {
                currentNodes = currentMBT.GetComponents<Node>();
                // Ensure there is no error when node script is missing
                for (int i = 0; i < currentNodes.Length; i++)
                {
                    currentNodes[i].children.RemoveAll(item => item == null);
                }
            } else {
                currentNodes = new Node[0];
                // Unlock when there is nothing to display
                this.locked = false;
            }
        }

        private void ProcessEvents(Event e)
        {
            switch (e.type)
            {
                case EventType.MouseDown:
                    if (e.button == 0) {
                        // Reset flag
                        nodeMoved = false;
                        // Frist check if any node handle was clicked
                        NodeHandle handle = FindHandle(e.mousePosition);
                        if (handle != null)
                        {
                            currentHandle = handle;
                            e.Use();
                            break;
                        }
                        Node node = FindNode(e.mousePosition);
                        // Select node if contains point
                        if (node != null) {
                            DeselectNode();
                            SelectNode(node);
                            if (e.clickCount == 2 && node is SubTree) {
                                SubTree subTree = node as SubTree;
                                if (subTree.tree != null) {
                                    Selection.activeGameObject = subTree.tree.gameObject;
                                }
                            }
                        } else {
                            DeselectNode();
                        }
                        e.Use();
                    } else if (e.button == 1) {
                        Node node = FindNode(e.mousePosition);
                        // Open proper context menu
                        if (node != null) {
                            OpenNodeMenu(e.mousePosition, node);
                        } else {
                            DeselectNode();
                            OpenNodeFinder(new Rect(e.mousePosition.x, e.mousePosition.y, 1, 1));
                        }
                        e.Use();
                    }
                    break;
                case EventType.MouseDrag:
                    // Drag node, workspace or connection
                    if (e.button == 0) {
                        if (currentHandle != null) {
                            // Let PaintConnections draw lines
                        } else if (selectedNode != null) {
                            Undo.RecordObject(selectedNode, "Move Node");
                            selectedNode.rect.position += Event.current.delta;
                            // Move whole branch when Ctrl is pressed
                            if (e.control) {
                                List<Node> movedNodes = selectedNode.GetAllSuccessors();
                                for (int i = 0; i < movedNodes.Count; i++)
                                {
                                    Undo.RecordObject(movedNodes[i], "Move Node");
                                    movedNodes[i].rect.position += Event.current.delta;
                                }
                            }
                            nodeMoved = true;
                        } else {
                            workspaceOffset += Event.current.delta;
                        }
                        GUI.changed = true;
                        e.Use();
                    }
                    break;
                case EventType.MouseUp:
                    if (currentHandle != null) {
                        TryConnectNodes(currentHandle, e.mousePosition);
                    }
                    // Reorder or snap nodes in case any of them was moved
                    if (nodeMoved && selectedNode != null) {
                        // Snap nodes if option is enabled
                        if (snapNodesToGrid)
                        {
                            Undo.RecordObject(selectedNode, "Move Node");
                            selectedNode.rect.position = SnapPositionToGrid(selectedNode.rect.position);
                            // When control is pressed snap successors too
                            if (e.control) {
                                List<Node> movedNodes = selectedNode.GetAllSuccessors();
                                for (int i = 0; i < movedNodes.Count; i++)
                                {
                                    Undo.RecordObject(movedNodes[i], "Move Node");
                                    movedNodes[i].rect.position = SnapPositionToGrid(movedNodes[i].rect.position);
                                }
                            }
                        }
                        // Reorder siblings if selected node has parent
                        if (selectedNode.parent != null)
                        {
                            Undo.RecordObject(selectedNode.parent, "Move Node");
                            selectedNode.parent.SortChildren();
                        }
                    }
                    nodeMoved = false;
                    currentHandle = null;
                    GUI.changed = true;
                    break;
            }
        }

        Vector2 SnapPositionToGrid(Vector2 position)
        {
            return new Vector2(
                Mathf.Round(position.x / 20f) * 20f, 
                Mathf.Round(position.y / 20f) * 20f
            );
        }

        private void TryConnectNodes(NodeHandle handle, Vector2 mousePosition)
        {
            // Find hovered node and connect or open dropdown
            Node targetNode = FindNode(mousePosition);
            if (targetNode == null) {
                OpenNodeFinder(new Rect(mousePosition.x, mousePosition.y, 1, 1), true, handle);
                return;
            }
            // Check if they are not the same node
            if (targetNode == handle.node) {
                return;
            }
            Undo.RecordObject(targetNode, "Connect Nodes");
            Undo.RecordObject(handle.node, "Connect Nodes");
            // There is node, try to connect if this is possible
            if (handle.type == HandleType.Input && targetNode is IParentNode) {
                // Do not allow connecting descendants as parents
                if (targetNode.IsDescendantOf(handle.node)) {
                    return;
                }
                // Then add as child to new parent
                targetNode.AddChild(handle.node);
                // Update order of nodes
                targetNode.SortChildren();
            } else if (handle.type == HandleType.Output && targetNode is IChildrenNode) {
                // Do not allow connecting descendants as parents
                if (handle.node.IsDescendantOf(targetNode)) {
                    return;
                }
                // Then add as child to new parent
                handle.node.AddChild(targetNode);
                // Update order of nodes
                handle.node.SortChildren();
            }
        }

        private void SelectNode(Node node)
        {
            currentMBT.selectedEditorNode = node;
            currentMBTEditor.Repaint();
            node.selected = true;
            selectedNode = node;
            GUI.changed = true;
        }

        private void DeselectNode(Node node)
        {
            currentMBT.selectedEditorNode = null;
            currentMBTEditor.Repaint();
            node.selected = false;
            selectedNode = null;
            GUI.changed = true;
        }

        private void DeselectNode()
        {
            currentMBT.selectedEditorNode = null;
            currentMBTEditor.Repaint();
            for (int i = 0; i < currentNodes.Length; i++)
            {
                currentNodes[i].selected = false;
            }
            selectedNode = null;
            GUI.changed = true;
        }

        private Node FindNode(Vector2 mousePosition)
        {
            for (int i = 0; i < currentNodes.Length; i++)
            {
                // Get correct position of node with offset
                Rect target = currentNodes[i].rect;
                target.position += workspaceOffset;
                if (target.Contains(mousePosition)) {
                    return currentNodes[i];
                }
            }
            return null;
        }

        private NodeHandle FindHandle(Vector2 mousePosition)
        {
            for (int i = 0; i < currentNodes.Length; i++)
            {
                Node node = currentNodes[i];
                // Get correct position of node with offset
                Rect targetRect = node.rect;
                targetRect.position += workspaceOffset;

                if (node is IChildrenNode) {
                    Vector2 handlePoint = GetTopHandlePosition(targetRect);
                    if (Vector2.Distance(handlePoint, mousePosition) < _handleDetectionDistance) {  
                        return new NodeHandle(node, handlePoint, HandleType.Input);
                    }
                }
                if (node is IParentNode) {
                    Vector2 handlePoint = GetBottomHandlePosition(targetRect);
                    if (Vector2.Distance(handlePoint, mousePosition) < _handleDetectionDistance) {  
                        return new NodeHandle(node, handlePoint, HandleType.Output);
                    }
                }
            }
            return null;
        }

        private void PaintNodes()
        {
            for (int i = currentNodes.Length - 1; i >= 0 ; i--)
            {
                Node node = currentNodes[i];
                Rect targetRect = node.rect;
                targetRect.position += workspaceOffset;
                // Draw node content
                GUILayout.BeginArea(targetRect, GetNodeStyle(node));
                    GUILayout.BeginVertical(_nodeContentBoxStyle);
                    if (node.breakpoint)
                    {
                        GUILayout.Label(node.title, _nodeBreakpointLabelStyle);
                    }
                    else
                    {
                        GUILayout.Label(node.title, _nodeLabelStyle);  
                    }
                    GUILayout.EndVertical();
                    if (Event.current.type == EventType.Repaint)
                    {
                        node.rect.height = GUILayoutUtility.GetLastRect().height;
                    }
                GUILayout.EndArea();

                // Paint warning icon
                if (!Application.isPlaying && !node.IsValid())
                {
                    GUI.Label(GetWarningIconRect(targetRect), EditorGUIUtility.IconContent("CollabConflict Icon"));
                }

                // Draw connection handles if needed
                if (node is IChildrenNode)
                {
                    Vector2 top = GetTopHandlePosition(targetRect);
                    GUI.DrawTexture(
                        new Rect(top.x-8, top.y-5, 16, 16),
                        Resources.Load("mbt_node_handle", typeof(Texture2D)) as Texture2D
                    );
                }
                if (node is IParentNode)
                {
                    Vector2 bottom = GetBottomHandlePosition(targetRect);
                    GUI.DrawTexture(
                        new Rect(bottom.x-8, bottom.y-11, 16, 16),
                        Resources.Load("mbt_node_handle", typeof(Texture2D)) as Texture2D
                    );
                }
            }
        }

        private GUIStyle GetNodeStyle(Node node)
        {
            if (node.selected) {
                return _selectedNodeStyle;
            }
            switch (node.status)
            {
                case Status.Success:
                    return _successNodeStyle;
                case Status.Failure:
                    return _failureNodeStyle;
                case Status.Running:
                    return _runningNodeStyle;
            }
            return _defaultNodeStyle;
        }

        private Vector2 GetTopHandlePosition(Rect rect)
        {
            return new Vector2(rect.x + rect.width/2, rect.y);
        }

        private Vector2 GetBottomHandlePosition(Rect rect)
        {
            return new Vector2(rect.x + rect.width/2, rect.y + rect.height);
        }

        private Rect GetWarningIconRect(Rect rect)
        {
            // return new Rect(rect.x - 10, rect.y + rect.height/2 - 10 , 20, 20);
            return new Rect(rect.x + rect.width - 2, rect.y - 1, 20, 20);
        }

        private void OpenNodeFinder(Rect rect, bool useRectPosition = true, NodeHandle handle = null)
        {
            // Store handle to connect later (null by default)
            dropdownHandleCache = handle;
            // Store real clicked position including workspace offset
            if (useRectPosition) {
                nodeDropdownTargetPosition = rect.position - workspaceOffset;
            } else {
                nodeDropdownTargetPosition = new Vector2(this.position.width/2, this.position.height/2) - workspaceOffset;
            }
            // Open dropdown
            nodeDropdown.Show(rect);
        }

        private void OpenNodeMenu(Vector2 mousePosition, Node node)
        {
            GenericMenu genericMenu = new GenericMenu();
            genericMenu.AddItem(new GUIContent("Breakpoint"), node.breakpoint, () => ToggleNodeBreakpoint(node));
            genericMenu.AddItem(new GUIContent("Duplicate"), false, () => DuplicateNode(node));
            genericMenu.AddItem(new GUIContent("Disconnect Children"), false, () => DisconnectNodeChildren(node)); 
            genericMenu.AddItem(new GUIContent("Disconnect Parent"), false, () => DisconnectNodeParent(node)); 
            genericMenu.AddItem(new GUIContent("Delete Node"), false, () => DeleteNode(node)); 
            genericMenu.ShowAsContext();
        }

        void AddNode(ClassTypeDropdownItem item)
        {
            // In case there is nothing to add
            if (currentMBT == null || item.classType == null) {
                return;
            }
            // Allow only one root
            if (item.classType.IsAssignableFrom(typeof(Root)) && currentMBT.GetComponent<Root>() != null) {
                Debug.LogWarning("You can not add more than one Root node.");
                return;
            }
            Undo.SetCurrentGroupName("Create Node");
            Node node = (Node)Undo.AddComponent(currentMBT.gameObject, item.classType);
            node.title = item.name;
            node.hideFlags = HideFlags.HideInInspector;
            node.rect.position = nodeDropdownTargetPosition - new Vector2(node.rect.width/2, 0);
            UpdateSelection();
            if (dropdownHandleCache != null) {
                // Add additonal offset (3,3) to be sure that point is inside rect
                TryConnectNodes(dropdownHandleCache, nodeDropdownTargetPosition + workspaceOffset + new Vector2(3,3));
            }
        }

        private void ToggleNodeBreakpoint(Node node)
        {
            // Toggle breakpoint flag
            // Undo.RecordObject(node, "Toggle Breakpoint");
            node.breakpoint = !node.breakpoint;
        }

        private void DeleteNode(Node node)
        {
            if (currentMBT == null) {
                return;
            }
            DeselectNode();
            // Disconnect all children and parent
            Undo.SetCurrentGroupName("Delete Node");
            DisconnectNodeChildren(node);
            DisconnectNodeParent(node);
            Undo.DestroyObjectImmediate(node);
            // DestroyImmediate(node, true);
            UpdateSelection();
        }

        private void DisconnectNodeParent(Node node)
        {
            if (node.parent != null) {
                Undo.RecordObject(node, "Disconnect Parent");
                Undo.RecordObject(node.parent, "Disconnect Parent");
                node.parent.RemoveChild(node);
            }
        }

        private void DisconnectNodeChildren(Node node)
        {
            Undo.RecordObject(node, "Disconnect Children");
            for (int i = node.children.Count - 1; i >= 0 ; i--)
            {
                Undo.RecordObject(node.children[i], "Disconnect Children");
                node.RemoveChild(node.children[i]);
            }
        }

        private void DuplicateNode(Node contextNode)
        {
            // NOTE: This code is mostly copied from AddNode()
            // Check if there is MBT
            if (currentMBT == null) {
                return;
            }
            System.Type classType = contextNode.GetType();
            // Allow only one root
            if (classType.IsAssignableFrom(typeof(Root)) && currentMBT.GetComponent<Root>() != null) {
                Debug.LogWarning("You can not add more than one Root node.");
                return;
            }
            Undo.SetCurrentGroupName("Duplicate Node");
            Node node = (Node)Undo.AddComponent(currentMBT.gameObject, classType);
            // Copy values
            EditorUtility.CopySerialized(contextNode, node);
            // Set flags anyway to ensure it is not visible in inspector
            node.hideFlags = HideFlags.HideInInspector;
            node.rect.position = contextNode.rect.position + new Vector2(20, 20);
            // Remove all connections or graph gonna break
            node.parent = null;
            node.children.Clear();
            UpdateSelection();
        }

        /// It is quite unique, but https://stackoverflow.com/questions/2920696/how-generate-unique-integers-based-on-guids
        private int GenerateId()
        {
            return System.Guid.NewGuid().GetHashCode();
        }

        private void PaintBackground()
        {
            // Background
            Handles.BeginGUI();
            Handles.DrawSolidRectangleWithOutline(new Rect(0, 0, position.width, position.height), _editorBackgroundColor, Color.gray);
            Handles.EndGUI();
            // Grid lines
            DrawBackgroundGrid(20, 0.1f, new Color(0.3f, 0.36f, 0.5f));
            DrawBackgroundGrid(100, 0.2f, new Color(0.3f, 0.36f, 0.5f));
        }

        /// Method copied from https://gram.gs/gramlog/creating-node-based-editor-unity/
        private void DrawBackgroundGrid(float gridSpacing, float gridOpacity, Color gridColor)
        {
            int widthDivs = Mathf.CeilToInt(position.width / gridSpacing);
            int heightDivs = Mathf.CeilToInt(position.height / gridSpacing);
    
            Handles.BeginGUI();

            Handles.color = new Color(gridColor.r, gridColor.g, gridColor.b, gridOpacity);
    
            Vector3 newOffset = new Vector3(workspaceOffset.x % gridSpacing, workspaceOffset.y % gridSpacing, 0);
    
            for (int i = 0; i <= widthDivs; i++)
            {
                Handles.DrawLine(new Vector3(gridSpacing * i, -gridSpacing, 0) + newOffset, new Vector3(gridSpacing * i, position.height+gridSpacing, 0f) + newOffset);
            }
    
            for (int j = 0; j <= heightDivs; j++)
            {
                Handles.DrawLine(new Vector3(-gridSpacing, gridSpacing * j, 0) + newOffset, new Vector3(position.width+gridSpacing, gridSpacing * j, 0f) + newOffset);
            }

            Handles.color = Color.white;
            Handles.EndGUI();
        }

        private class NodeHandle
        {
            public Node node;
            public Vector2 position;
            public HandleType type;

            public NodeHandle(Node node, Vector2 position, HandleType type)
            {
                this.node = node;
                this.position = position;
                this.type = type;
            }
        }

        private enum HandleType
        {
            Input, Output
        }
    }
}
