package models.dataFlowModel;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import models.DirectedGraph;
import models.dataConstraintModel.ResourceHierarchy;
import models.dataConstraintModel.ResourcePath;

public class DataFlowGraph extends DirectedGraph {
	protected Set<ResourceNode> rootResourceNodes = null;
	protected Set<ChannelNode> rootChannelNodes = null;
	protected Map<DataTransferChannel, ChannelNode> channelNodeMap = null;
	protected Map<Integer, ResourceNode> resourceNodeMap = null;
	
	public DataFlowGraph() {
		super();
		rootResourceNodes = new HashSet<>();
		rootChannelNodes = new HashSet<>();
		channelNodeMap = new HashMap<>();
		resourceNodeMap = new HashMap<>();
	}
	
	public ResourceNode addResourceNode(ResourceNode parent, 			
										ResourcePath outSideResource,
										DataTransferChannel outSideChannel) {
		ResourceNode node = null;
		if (outSideResource.getNumberOfParameters() == 0) {
			// a global (possibly non-root) resource node
			Collection<ResourceNode> nodes = getResourceNodes(outSideResource.getResourceHierarchy());
			if (nodes.size() > 0) {
				node = nodes.iterator().next();
				if (outSideChannel != null) {
					// not a terminal node
					node.addOutSideResource(outSideChannel, outSideResource);
				}
			}
		} else {
			// a channel local resource node
			node = resourceNodeMap.get(System.identityHashCode(outSideResource));
		}
		if (node != null) return node;
		node = new ResourceNode(parent, outSideResource, outSideChannel);
		addNode(node);
		if (parent == null) {
			rootResourceNodes.add(node);
		} else {
			parent.addChild(node);
		}
		resourceNodeMap.put(System.identityHashCode(outSideResource), node);
		return node;
	}
	
	public ResourceNode addTerminalResourceNode(ResourceNode parent, 
												DataTransferChannel inSideChannel, 
												ResourcePath inSideResource) {
		ResourceNode node = null;
		if (inSideResource.getNumberOfParameters() == 0) {
			// a global (possibly non-root) terminal resource node
			Collection<ResourceNode> nodes = getResourceNodes(inSideResource.getResourceHierarchy());
			if (nodes.size() > 0) {
				node = nodes.iterator().next();
			}
		} else {
			// a channel local terminal resource node
			node = resourceNodeMap.get(System.identityHashCode(inSideResource));
		}
		if (node != null) return node;
		node = new ResourceNode(parent, inSideResource, inSideChannel, true);
		addNode(node);
		if (parent == null) {
			rootResourceNodes.add(node);
		} else {
			parent.addChild(node);
		}
		resourceNodeMap.put(System.identityHashCode(inSideResource), node);
		return node;
	}
	
	public ResourceNode addResourceNode(ResourceNode parent, 			
										Map<DataTransferChannel, ResourcePath> inSide, 
										Map<DataTransferChannel, ResourcePath> outSide) {
		ResourceNode node = null;
		for (ResourcePath outRes: outSide.values()) {
			node = resourceNodeMap.get(System.identityHashCode(outRes));
			if (node != null) return node;
		}
		for (ResourcePath inRes: inSide.values()) {
			node = resourceNodeMap.get(System.identityHashCode(inRes));
			if (node != null) return node;
		}
		node = new ResourceNode(parent, inSide, outSide);
		addNode(node);
		if (parent == null) {
			rootResourceNodes.add(node);
		} else {
			parent.addChild(node);
		}
		for (ResourcePath outRes: outSide.values()) {
			resourceNodeMap.put(System.identityHashCode(outRes), node);
		}
		for (ResourcePath inRes: inSide.values()) {
			resourceNodeMap.put(System.identityHashCode(inRes), node);
		}
		return node;
	}
		
	public ChannelNode addChannelNode(ChannelNode parent, DataTransferChannel ch) {
		ChannelNode node = channelNodeMap.get(ch);
		if (node != null) return node;
		node = new ChannelNode(parent, ch);
		addNode(node);
		if (parent == null) {
			rootChannelNodes.add(node);
		} else {
			parent.addChild(node);
		}
		channelNodeMap.put(ch, node);
		return node;
	}
	
	public ResourceNode getResourceNode(ResourcePath resPath) {
		return resourceNodeMap.get(System.identityHashCode(resPath));
	}
	
	public ChannelNode getChannelNode(DataTransferChannel channel) {
		return channelNodeMap.get(channel);
	}
	
	public Collection<ResourceNode> getResourceNodes() {
		HashSet<ResourceNode> result = new HashSet<>(resourceNodeMap.values());
		return result;
	}
	
	public Collection<ResourceNode> getResourceNodes(ResourceHierarchy resourceHierarchy) {
		Collection<ResourceNode> resourceNodes = new ArrayList<>();
		for (ResourceNode rn: resourceNodeMap.values()) {
			if (rn.getResourceHierarchy() == resourceHierarchy) {
				resourceNodes.add(rn);
			}
		}
		return resourceNodes;
	}

	
	public Collection<ChannelNode> getChannelNodes() {
		return channelNodeMap.values();
	}

	public Set<ResourceNode> getRootResourceNodes() {
		return rootResourceNodes;
	}

	public Set<ChannelNode> getRootChannelNodes() {
		return rootChannelNodes;
	}

	public DataFlowEdge addEdge(ResourceNode srcNode, ChannelNode dstNode) {
		DataFlowEdge edge =new DataFlowEdge(srcNode, dstNode);
		addEdge(edge);
		return edge;
	}

	public DataFlowEdge addEdge(ChannelNode srcNode, ResourceNode dstNode) {
		DataFlowEdge edge = new DataFlowEdge(srcNode, dstNode);
		addEdge(edge);
		return edge;
	}
}
