package models.dataFlowModel;

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

import models.dataConstraintModel.Channel;
import models.dataConstraintModel.DataConstraintModel;
import models.dataConstraintModel.ResourceHierarchy;
import models.dataConstraintModel.ResourcePath;

public class DataTransferModel extends DataConstraintModel {	
	public DataFlowGraph getDataFlowGraph() {
		DataFlowGraph dataFlowGraph = new DataFlowGraph();
		Map<ResourceHierarchy, Map<Channel, Set<ResourceNode>>> channelLocalResMap = new HashMap<>();
		for (Channel channel: getChannels()) {
			addResourceToChannelEdges(dataFlowGraph, channel, null, channelLocalResMap);
		}
		for (Channel channel: getChannels()) {
			addChannelToResourceEdges(dataFlowGraph, channel, null, channelLocalResMap);
		}
		for (Channel channel: getInputChannels()) {
			addChannelToResourceEdges(dataFlowGraph, channel, null, channelLocalResMap);
		}
		return dataFlowGraph;
	}

	private void addResourceToChannelEdges(DataFlowGraph dataFlowGraph, Channel dstChannel, ChannelNode parentChannelNode, 
											Map<ResourceHierarchy, Map<Channel, Set<ResourceNode>>> channelLocalResMap) {
		DataTransferChannel dstDfChannel = (DataTransferChannel) dstChannel;
		ChannelNode dstChannelNode = dataFlowGraph.addChannelNode(parentChannelNode, dstDfChannel);
		for (ResourcePath srcRes: dstDfChannel.getInputResources()) {
			Map<Channel, Set<ResourceNode>> chLocalResNodes = channelLocalResMap.get(srcRes.getResourceHierarchy());
			if (srcRes.getNumberOfParameters() == 0) {
				// ResourcePath without parameter corresponds to a global ResourceNode.
				ResourceNode srcResNode = addResourceNodes(dataFlowGraph, srcRes, dstDfChannel, channelLocalResMap, false);
				dataFlowGraph.addEdge(srcResNode, dstChannelNode);
			} else {
				if (chLocalResNodes == null || chLocalResNodes.get(dstChannel) == null) {
					// There is no channel-local ResourcePath.
					// Then, create a new channel-local ResourceNode.
					ResourceNode srcResNode = addResourceNodes(dataFlowGraph, srcRes, dstDfChannel, channelLocalResMap, false);
					dataFlowGraph.addEdge(srcResNode, dstChannelNode);
				} else {
					// There already has been a channel-local ResourceNode.
					Set<ResourceNode> nodes = chLocalResNodes.get(dstChannel);		// channel-local ResourceNodes.
					boolean bExists = false;
					for (ResourceNode node: nodes) {
						ResourcePath r = node.getOutSideResource(dstDfChannel);
						if (r.toString().equals(srcRes.toString())) {
							bExists = true;		// There exists a textually identical ResourecPath within the same channel.
							break;
						}
					}
					if (!bExists) {
						// Create a new channel-local ResourceNode.
						ResourceNode srcResNode = addResourceNodes(dataFlowGraph, srcRes, dstDfChannel, channelLocalResMap, false);
						dataFlowGraph.addEdge(srcResNode, dstChannelNode);
					}
				}
			}
		}
		for (Channel childChannel: dstDfChannel.getChildren()) {
			addResourceToChannelEdges(dataFlowGraph, childChannel, dstChannelNode, channelLocalResMap);
		}
	}
	
	private void addChannelToResourceEdges(DataFlowGraph dataFlowGraph, Channel srcChannel, ChannelNode parentChannelNode, 
			Map<ResourceHierarchy, Map<Channel, Set<ResourceNode>>> channelLocalResMap) {
		DataTransferChannel srcDfChannel = (DataTransferChannel) srcChannel;
		ChannelNode srcChannelNode = dataFlowGraph.addChannelNode(parentChannelNode, srcDfChannel);		
		for (ResourcePath dstRes: srcDfChannel.getOutputResources()) {
			Map<Channel, Set<ResourceNode>> chLocalResNodes = channelLocalResMap.get(dstRes.getResourceHierarchy());		// ResourceNodes that have the same ResourceHierarchy.
			Set<ResourceNode> dstResSet = new HashSet<>();
			if (dstRes.getNumberOfParameters() == 0) {
				// ResourcePath without parameter corresponds to a global ResourceNode.
				if (chLocalResNodes == null) {
					// Create a new global terminal ResourceNode.
					ResourceNode dstResNode = addResourceNodes(dataFlowGraph, dstRes, srcDfChannel, channelLocalResMap, true);
					dstResSet.add(dstResNode);
				} else {
					// Select all (usually one) global ResourceNodes.
					for (Set<ResourceNode> resSet: chLocalResNodes.values()) {
						dstResSet.addAll(resSet);
					}
				}
			} else {
				if (chLocalResNodes == null) {
					// There is no corresponding ResourceNode.
					// Create a new channel-local terminal ResourceNode.
					ResourceNode dstResNode = addResourceNodes(dataFlowGraph, dstRes, srcDfChannel, channelLocalResMap, true);
					dstResSet.add(dstResNode);
				} else {
					if (chLocalResNodes.get(srcDfChannel) == null) {
						// Select all non-terminal ResourceNodes.
						for (Set<ResourceNode> resSet: chLocalResNodes.values()) {
							dstResSet.addAll(resSet);
						}
					} else {
						// There already has been a channel-local terminal ResourceNode.
						if (chLocalResNodes.size() > 0) {
							// There already has been a channel-local ResourceNode.
							for (Set<ResourceNode> resNodes: chLocalResNodes.values()) {
								for (ResourceNode localResNode: resNodes) {
									for (ResourcePath localResPath: localResNode.getInSideResources()) {
										if (localResPath.toString().equals(dstRes.toString())) {
											// Channel-local ResourcePath should be identical, and the identical ResourcePath is selected on top priority.
											dstResSet.add(localResNode);
										}
									}
								}
							}
						}
						// Search a common channel-local ancestor.
						if (dstResSet.size() == 0) {
							ResourcePath dstParent = dstRes.getParent();
							while (dstParent != null && dstParent.getNumberOfParameters() > 0) {
								Map<Channel, Set<ResourceNode>> chToLocalDstNodes = channelLocalResMap.get(dstParent.getResourceHierarchy());
								if (chToLocalDstNodes != null) {
									for (Channel dstCh: chToLocalDstNodes.keySet()) {
										for (ResourceNode localParentNode: chToLocalDstNodes.get(dstCh)) {
											for (ResourcePath localParentPath: localParentNode.getInSideResources()) {
												if (localParentPath.toString().equals(dstParent.toString())) {
													// There already has been a common channel-local ancestor.
													ResourceNode dstResNode = addResourceNodes(dataFlowGraph, dstRes, (DataTransferChannel)dstCh, channelLocalResMap, false);
													dstResSet.add(dstResNode);
													break;
												}
											}
											if (dstResSet.size() > 0) break;
										}
										if (dstResSet.size() > 0) break;
									}
									if (dstResSet.size() > 0) break;
								}
								dstParent = dstParent.getParent();
							}
						}
						if (dstResSet.size() == 0) {
							for (Set<ResourceNode> nodes: chLocalResNodes.values()) {
								// Select all corresponding ResourceNodes. 
								dstResSet.addAll(nodes);
							}
						}
						if (dstResSet.size() == 0) {
							// Otherwise create a new ResourceNode.
							ResourceNode dstResNode = addResourceNodes(dataFlowGraph, dstRes, srcDfChannel, channelLocalResMap, false);
							dstResSet.add(dstResNode);
						}
					}
				}
			}
			for (ResourceNode dstResNode: dstResSet) {
				dstResNode.addInSideResource(srcDfChannel, dstRes);
				dataFlowGraph.addEdge(srcChannelNode, dstResNode);		// Connect to each ResourceNode that has the same ResourceHierarchy.
			}
		}
		for (Channel childChannel: srcDfChannel.getChildren()) {
			addChannelToResourceEdges(dataFlowGraph, childChannel, srcChannelNode, channelLocalResMap);
		}
	}
	
	private ResourceNode addResourceNodes(DataFlowGraph dataFlowGraph, ResourcePath resPath, DataTransferChannel dfChannel, 
			Map<ResourceHierarchy, Map<Channel, Set<ResourceNode>>> channelLocalResMap, boolean isTerminal) {
		ResourceNode resNode = null;
		if (resPath.getParent() == null) {
			if (!isTerminal) {
				resNode = dataFlowGraph.addResourceNode(null, resPath, dfChannel);
			} else {
				resNode = dataFlowGraph.addTerminalResourceNode(null, dfChannel, resPath);				
			}
		} else {
			// Search an identical parent ResourceNode.
			ResourceNode parent = null;
			DataTransferChannel parentDfChannel = dfChannel;
			if (resPath.getResourceHierarchy().getParent().getNumParameters() == 0) {
				parentDfChannel = null;
			}
			if (channelLocalResMap.get(resPath.getResourceHierarchy().getParent()) != null) {
				Set<ResourceNode> chLocalNodes = channelLocalResMap.get(resPath.getResourceHierarchy().getParent()).get(parentDfChannel);
				if (chLocalNodes != null) {
					for (ResourceNode node: chLocalNodes) {
						for (ResourcePath r: node.getOutSideResources()) {
							if (r.toString().equals(resPath.getParent().toString())) {
								parent = node;
								break;
							}
						}
						if (parent != null) break;
						for (ResourcePath r: node.getInSideResources()) {
							if (r.toString().equals(resPath.getParent().toString())) {
								parent = node;
								break;
							}
						}
						if (parent != null) break;
					}
				}
			}
			if (parent == null) {
				parent = addResourceNodes(dataFlowGraph, resPath.getParent(), parentDfChannel, channelLocalResMap, false);
			}
			if (!isTerminal) {
				resNode = dataFlowGraph.addResourceNode(parent, resPath, dfChannel);
			} else {
				resNode = dataFlowGraph.addTerminalResourceNode(parent, dfChannel, resPath);
			}
		}
		Map<Channel, Set<ResourceNode>> chToLocalNodes = channelLocalResMap.get(resPath.getResourceHierarchy());
		if (chToLocalNodes == null) {
			chToLocalNodes = new HashMap<>();
			channelLocalResMap.put(resPath.getResourceHierarchy(), chToLocalNodes);					
		}
		Set<ResourceNode> chLocalNodes = chToLocalNodes.get(dfChannel);
		if (chLocalNodes == null) {
			chLocalNodes = new HashSet<>();
			chToLocalNodes.put(dfChannel, chLocalNodes);					
		}
		chLocalNodes.add(resNode);
		return resNode;
	}

//	private ResourceNode addResourceNodes(DataFlowGraph dataFlowGraph, ResourcePath resPath, DataTransferChannel dfChannel,
//															Map<ResourceHierarchy, Map<Channel, Set<ResourceNode>>> resourceMap) {
//		if (resPath.getNumberOfParameters() == 0) {
//			// ResourcePath without parameter corresponds to a global ResourceNode.
//			dfChannel = null;
//		}
//		if (resourceMap.get(resPath.getResourceHierarchy()) != null && resourceMap.get(resPath.getResourceHierarchy()).get(dfChannel) != null) {
//			Set<ResourceNode> nodes = resourceMap.get(resPath.getResourceHierarchy()).get(dfChannel);
//			for (ResourceNode node: nodes) {
//				if (node.getOutSideResource().toString().equals(resPath.toString())) {
//					return node;
//				}
//				for (ResourcePath res: node.getInSideResources()) {
//					if (res.toString().toString().equals(resPath.toString())) {
//						return node;
//					}
//				}
//			}
//		}
//		ResourceNode resNode = null;
//		if (resPath.getParent() == null) {
//			resNode = dataFlowGraph.addResourceNode(null, dfChannel, resPath);			
//		} else {
//			ResourceNode parent = addResourceNodes(dataFlowGraph, resPath.getParent(), dfChannel, resourceMap);				
//			resNode = dataFlowGraph.addResourceNode(parent, dfChannel, resPath);
//		}
//		Map<Channel, Set<ResourceNode>> chToNodes = resourceMap.get(resPath.getResourceHierarchy());
//		if (chToNodes == null) {
//			chToNodes = new HashMap<>();
//			resourceMap.put(resPath.getResourceHierarchy(), chToNodes);					
//		}
//		Set<ResourceNode> nodes = chToNodes.get(dfChannel);
//		if (nodes == null) {
//			nodes = new HashSet<>();
//			chToNodes.put(dfChannel, nodes);					
//		}
//		nodes.add(resNode);
//		return resNode;
//	}
}
