using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using DynamicData; using DynamicData.Alias; using NodeNetwork.Utilities; using NodeNetwork.ViewModels; namespace NodeNetwork.Toolkit.Group { /// /// Used to provide nesting of networks by grouping nodes. /// public class NodeGrouper { /// /// Constructs a new node that represents a group of nodes. /// The parameter is the subnetwork (constructed with SubNetworkFactory) that contains the group member nodes. /// public Func GroupNodeFactory { get; set; } = subnet => new NodeViewModel(); /// /// Constructs a viewmodel for the subnetwork that will contain the group member nodes. /// public Func SubNetworkFactory { get; set; } = parentNet => new NetworkViewModel(); /// /// Constructs the node in the subnet that provides access to (mostly) inputs to the group /// public Func EntranceNodeFactory { get; set; } = () => new NodeViewModel(); /// /// Constructs the node in the subnet that provides access to (mostly) outputs of the group /// public Func ExitNodeFactory { get; set; } = () => new NodeViewModel(); /// /// Constructs a NodeGroupIOBinding from a group, entrance and exit node. /// public Func IOBindingFactory { get; set; } private bool CheckPropertiesValid() => GroupNodeFactory != null && SubNetworkFactory != null && EntranceNodeFactory != null && ExitNodeFactory != null && IOBindingFactory != null; /// /// Move the specified set of nodes to a new subnetwork, create a new group node that contains this subnet, /// restore inter- and intra-network connections. /// /// The parent network /// The nodes to group /// Returns the NodeGroupIOBinding that was constructed for this group using the IOBindingFactory. public NodeGroupIOBinding MergeIntoGroup(NetworkViewModel network, IEnumerable nodesToGroup) { if (!CheckPropertiesValid()) { throw new InvalidOperationException("All properties must be set before usage"); } else if (network == null) { throw new ArgumentNullException(nameof(network)); } else if (nodesToGroup == null) { throw new ArgumentNullException(nameof(nodesToGroup)); } var groupNodesSet = nodesToGroup is HashSet set ? set : new HashSet(nodesToGroup); // Check if nodesToGroup can be combined into a single group if (groupNodesSet.Count == 0)//[FT] allowed for now || !GraphAlgorithms.IsContinuousSubGraphSet(groupNodesSet)) { return null; } // Create new empty group var subnet = SubNetworkFactory(network); var groupNode = GroupNodeFactory(subnet); network.Nodes.Add(groupNode); var groupEntranceNode = EntranceNodeFactory(); var groupExitNode = ExitNodeFactory(); subnet.Nodes.AddRange(new []{groupEntranceNode, groupExitNode}); // Map from input on a group member node to group node input var groupNodeInputs = new Dictionary(); // Map from output on a group member node to group node output var groupNodeOutputs = new Dictionary(); // Move the new nodes to appropriate positions groupNode.Position = new Point( groupNodesSet.Average(n => n.Position.X), groupNodesSet.Average(n => n.Position.Y) ); double yCoord = groupNodesSet.Average(n => n.Position.Y); groupEntranceNode.Position = new Point( groupNodesSet.Min(n => n.Position.X) - 100, yCoord ); groupExitNode.Position = new Point( groupNodesSet.Max(n => n.Position.X) + 100, yCoord ); // Setup binding between entrance/exit inputs and outputs var ioBinding = IOBindingFactory(groupNode, groupEntranceNode, groupExitNode); // Calculate set of connections to replace var subnetConnections = new List(); var borderInputConnections = new List(); var borderOutputConnections = new List(); foreach (var con in network.Connections.Items) { bool inputIsInSubnet = groupNodesSet.Contains(con.Input.Parent); bool outputIsInSubnet = groupNodesSet.Contains(con.Output.Parent); if (inputIsInSubnet && outputIsInSubnet) { subnetConnections.Add(con); } else if (inputIsInSubnet) { borderInputConnections.Add(con); } else if (outputIsInSubnet) { borderOutputConnections.Add(con); } } // Construct inputs/outputs into/out of the group foreach (var borderInCon in borderInputConnections) { if (!groupNodeInputs.ContainsKey(borderInCon.Input)) { groupNodeInputs[borderInCon.Input] = ioBinding.AddNewGroupNodeInput(borderInCon.Output); } } foreach (var borderOutCon in borderOutputConnections) { if (!groupNodeOutputs.ContainsKey(borderOutCon.Output)) { groupNodeOutputs[borderOutCon.Output] = ioBinding.AddNewGroupNodeOutput(borderOutCon.Input); } } // Transfer nodes and inner connections to subnet network.Connections.Edit(l => { l.RemoveMany(subnetConnections); l.RemoveMany(borderInputConnections); l.RemoveMany(borderOutputConnections); }); network.Nodes.RemoveMany(groupNodesSet); subnet.Nodes.AddRange(groupNodesSet); subnet.Connections.AddRange(subnetConnections.Select(con => subnet.ConnectionFactory(con.Input, con.Output))); // Restore connections in/out of group network.Connections.AddRange(Enumerable.Concat( borderInputConnections.Select(con => network.ConnectionFactory(groupNodeInputs[con.Input], con.Output)), borderOutputConnections.Select(con => network.ConnectionFactory(con.Input, groupNodeOutputs[con.Output])) )); subnet.Connections.AddRange(Enumerable.Concat( borderInputConnections.Select(con => subnet.ConnectionFactory(con.Input, ioBinding.GetSubnetInlet(groupNodeInputs[con.Input]))), borderOutputConnections.Select(con => subnet.ConnectionFactory(ioBinding.GetSubnetOutlet(groupNodeOutputs[con.Output]), con.Output)) )); return ioBinding; } /// /// Reverses the grouping performed by MergeIntoGroup. /// Group members get moved back into the parent network and the group node is removed. /// /// The NodeGroupIOBinding of the group to dissolve. public void Ungroup(NodeGroupIOBinding nodeGroupInfo) { if (!CheckPropertiesValid()) { throw new InvalidOperationException("All properties must be set before usage"); } else if (nodeGroupInfo == null) { throw new ArgumentNullException(nameof(nodeGroupInfo)); } var supernet = nodeGroupInfo.SuperNetwork; var subnet = nodeGroupInfo.SubNetwork; // Calculate set of subnet connections to replace var borderInputConnections = new List>(); var borderOutputConnections = new List>(); var subnetConnections = new List(); foreach (var conn in subnet.Connections.Items) { if (conn.Input.Parent == nodeGroupInfo.EntranceNode || conn.Input.Parent == nodeGroupInfo.ExitNode) { var inputs = nodeGroupInfo.GetGroupNodeOutput(conn.Input).Connections.Items.Select(c => c.Input).ToArray(); if (inputs.Length > 0) { borderInputConnections.Add(Tuple.Create(conn.Output, inputs)); } } else if (conn.Output.Parent == nodeGroupInfo.EntranceNode || conn.Output.Parent == nodeGroupInfo.ExitNode) { var outputs = nodeGroupInfo.GetGroupNodeInput(conn.Output).Connections.Items.Select(c => c.Output).ToArray(); if (outputs.Length > 0) { borderOutputConnections.Add(Tuple.Create(conn.Input, outputs)); } } else { subnetConnections.Add(conn); } } // Calculate set of nodes to move var groupMemberNodes = subnet.Nodes.Items.Where(node => node != nodeGroupInfo.EntranceNode && node != nodeGroupInfo.ExitNode).ToArray(); // Calculate center of nodes var minX = groupMemberNodes.Min(n => n.Position.X); var minY = groupMemberNodes.Min(n => n.Position.Y); var maxX = groupMemberNodes.Max(n => n.Position.X); var maxY = groupMemberNodes.Max(n => n.Position.Y); var center = new Vector(minX + (maxX - minX)/2, minY + (maxY - minY)/2); // Remove connections and nodes from subnet subnet.Connections.Clear(); subnet.Nodes.Clear(); // Remove groupnode and connections from supernet var groupNodePos = new Vector(nodeGroupInfo.GroupNode.Position.X, nodeGroupInfo.GroupNode.Position.Y); supernet.Nodes.Remove(nodeGroupInfo.GroupNode); // Add nodes to supernet and move them to correct position supernet.Nodes.AddRange(groupMemberNodes); foreach (var node in groupMemberNodes) { node.Position = node.Position - center + groupNodePos; } // Add connections to supernet supernet.Connections.AddRange(subnetConnections); foreach (var connTuple in borderInputConnections) { var output = connTuple.Item1; var inputs = connTuple.Item2; var connections = inputs.Select(input => supernet.ConnectionFactory(input, output)); supernet.Connections.AddRange(connections); } foreach (var connTuple in borderOutputConnections) { var outputs = connTuple.Item2; var input = connTuple.Item1; var connections = outputs.Select(output => supernet.ConnectionFactory(input, output)); supernet.Connections.AddRange(connections); } } } }