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);
}
}
}
}