/// <summary>
        /// Initializes a new instance of the <see cref="PasteSerializedDataCommand"/> class.
        /// </summary>
        /// <param name="undoString">The name of the paste operation (Paste, Duplicate, etc.).</param>
        /// <param name="delta">The delta to apply on the pasted elements position.</param>
        /// <param name="data">The elements to paste.</param>
        public PasteSerializedDataCommand(string undoString, Vector2 delta, CopyPasteData data) : this()
        {
            if (!string.IsNullOrEmpty(undoString))
            {
                UndoString = undoString;
            }

            Delta = delta;
            Data  = data;
        }
        internal static CopyPasteData GatherCopiedElementsData(IReadOnlyCollection <IGraphElementModel> graphElementModels)
        {
            var originalNodes = graphElementModels.OfType <INodeModel>().ToList();

            List <IVariableDeclarationModel> variableDeclarationsToCopy = graphElementModels
                                                                          .OfType <IVariableDeclarationModel>()
                                                                          .ToList();

            List <IStickyNoteModel> stickyNotesToCopy = graphElementModels
                                                        .OfType <IStickyNoteModel>()
                                                        .ToList();

            List <IPlacematModel> placematsToCopy = graphElementModels
                                                    .OfType <IPlacematModel>()
                                                    .ToList();

            List <IEdgeModel> edgesToCopy = graphElementModels
                                            .OfType <IEdgeModel>()
                                            .ToList();

            Vector2 topLeftNodePosition = Vector2.positiveInfinity;

            foreach (var n in originalNodes)
            {
                topLeftNodePosition = Vector2.Min(topLeftNodePosition, n.Position);
            }
            foreach (var n in stickyNotesToCopy)
            {
                topLeftNodePosition = Vector2.Min(topLeftNodePosition, n.PositionAndSize.position);
            }
            foreach (var n in placematsToCopy)
            {
                topLeftNodePosition = Vector2.Min(topLeftNodePosition, n.PositionAndSize.position);
            }
            if (topLeftNodePosition == Vector2.positiveInfinity)
            {
                topLeftNodePosition = Vector2.zero;
            }

            CopyPasteData copyPasteData = new CopyPasteData
            {
                topLeftNodePosition = topLeftNodePosition,
                nodes = originalNodes,
                edges = edgesToCopy,
                variableDeclarations = variableDeclarationsToCopy,
                stickyNotes          = stickyNotesToCopy,
                placemats            = placematsToCopy
            };

            return(copyPasteData);
        }
        /// <summary>
        /// Default command handler.
        /// </summary>
        /// <param name="graphToolState">The state.</param>
        /// <param name="command">The command.</param>
        public static void DefaultCommandHandler(GraphToolState graphToolState, PasteSerializedDataCommand command)
        {
            if (!command.Data.IsEmpty())
            {
                graphToolState.PushUndo(command);

                using (var graphViewUpdater = graphToolState.GraphViewState.UpdateScope)
                    using (var selectionUpdater = graphToolState.SelectionState.UpdateScope)
                    {
                        selectionUpdater.ClearSelection(graphToolState.GraphViewState.GraphModel);

                        CopyPasteData.PasteSerializedData(graphToolState.GraphViewState.GraphModel, command.Delta, graphViewUpdater, selectionUpdater, command.Data);
                    }
            }
        }
        internal static void PasteSerializedData(IGraphModel graph, Vector2 delta,
                                                 GraphViewStateComponent.StateUpdater graphViewUpdater,
                                                 SelectionStateComponent.StateUpdater selectionStateUpdater,
                                                 CopyPasteData copyPasteData)
        {
            var elementMapping = new Dictionary <string, IGraphElementModel>();

            if (copyPasteData.variableDeclarations.Any())
            {
                List <IVariableDeclarationModel> variableDeclarationModels =
                    copyPasteData.variableDeclarations.ToList();
                List <IVariableDeclarationModel> duplicatedModels = new List <IVariableDeclarationModel>();

                foreach (var sourceModel in variableDeclarationModels)
                {
                    duplicatedModels.Add(graph.DuplicateGraphVariableDeclaration(sourceModel));
                }

                graphViewUpdater?.MarkNew(duplicatedModels);
                selectionStateUpdater?.SelectElements(duplicatedModels, true);
            }

            var nodeMapping = new Dictionary <INodeModel, INodeModel>();

            foreach (var originalModel in copyPasteData.nodes)
            {
                if (!graph.Stencil.CanPasteNode(originalModel, graph))
                {
                    continue;
                }

                var pastedNode = graph.DuplicateNode(originalModel, delta);
                graphViewUpdater?.MarkNew(pastedNode);
                selectionStateUpdater?.SelectElements(new[] { pastedNode }, true);
                nodeMapping[originalModel] = pastedNode;
            }

            // PF FIXME we could do this in the foreach above
            foreach (var nodeModel in nodeMapping)
            {
                elementMapping.Add(nodeModel.Key.Guid.ToString(), nodeModel.Value);
            }

            foreach (var edge in copyPasteData.edges)
            {
                elementMapping.TryGetValue(edge.ToPort.NodeModel.Guid.ToString(), out var newInput);
                elementMapping.TryGetValue(edge.FromPort.NodeModel.Guid.ToString(), out var newOutput);

                var copiedEdge = graph.DuplicateEdge(edge, newInput as INodeModel, newOutput as INodeModel);
                if (copiedEdge != null)
                {
                    elementMapping.Add(edge.Guid.ToString(), copiedEdge);
                    graphViewUpdater?.MarkNew(copiedEdge);
                    selectionStateUpdater?.SelectElements(new[] { copiedEdge }, true);
                }
            }

            foreach (var stickyNote in copyPasteData.stickyNotes)
            {
                var newPosition      = new Rect(stickyNote.PositionAndSize.position + delta, stickyNote.PositionAndSize.size);
                var pastedStickyNote = graph.CreateStickyNote(newPosition);
                pastedStickyNote.Title    = stickyNote.Title;
                pastedStickyNote.Contents = stickyNote.Contents;
                pastedStickyNote.Theme    = stickyNote.Theme;
                pastedStickyNote.TextSize = stickyNote.TextSize;
                graphViewUpdater?.MarkNew(pastedStickyNote);
                selectionStateUpdater?.SelectElements(new[] { pastedStickyNote }, true);
                elementMapping.Add(stickyNote.Guid.ToString(), pastedStickyNote);
            }

            List <IPlacematModel> pastedPlacemats = new List <IPlacematModel>();

            // Keep placemats relative order
            foreach (var placemat in copyPasteData.placemats.OrderBy(p => p.ZOrder))
            {
                var newPosition    = new Rect(placemat.PositionAndSize.position + delta, placemat.PositionAndSize.size);
                var newTitle       = "Copy of " + placemat.Title;
                var pastedPlacemat = graph.CreatePlacemat(newPosition);
                pastedPlacemat.Title          = newTitle;
                pastedPlacemat.Color          = placemat.Color;
                pastedPlacemat.Collapsed      = placemat.Collapsed;
                pastedPlacemat.HiddenElements = placemat.HiddenElements;
                graphViewUpdater?.MarkNew(pastedPlacemat);
                selectionStateUpdater?.SelectElements(new[] { pastedPlacemat }, true);
                pastedPlacemats.Add(pastedPlacemat);
                elementMapping.Add(placemat.Guid.ToString(), pastedPlacemat);
            }

            // Update hidden content to new node ids.
            foreach (var pastedPlacemat in pastedPlacemats)
            {
                if (pastedPlacemat.Collapsed)
                {
                    List <IGraphElementModel> pastedHiddenContent = new List <IGraphElementModel>();
                    foreach (var elementGUID in pastedPlacemat.HiddenElements.Select(t => t.Guid.ToString()))
                    {
                        if (elementMapping.TryGetValue(elementGUID, out var pastedElement))
                        {
                            pastedHiddenContent.Add(pastedElement);
                        }
                    }

                    pastedPlacemat.HiddenElements = pastedHiddenContent;
                }
            }
        }