package ua.ansymo.hintco

import java.util.Deque
import java.util.HashMap
import java.util.List
import java.util.Map
import java.util.Set
import org.eclipse.core.runtime.Assert

class VariantProcessor implements IVariantProcessor {
	
	ICosimRunner runner
	
	new(ICosimRunner r){
		runner = r
	}
	
	def computeOperationOrder(RootCandidateScenario  scenario, Deque<VariantDiagram> ns, HintConfiguration cs){
		/*
		 * Computes feasible operation order. 
		 */
		 
		 val scenarioSortedNodesMap = computeHierarchicalTopologicalSort(scenario)
		 
		 // Set the path in the model.
		 for (sortedNodes: scenarioSortedNodesMap.values){
			 var prevNode = sortedNodes.head
			 for (n: sortedNodes.tail){
			 	prevNode.before = n
			 	prevNode = n
			 }
		 }
		 
		 return scenarioSortedNodesMap
	}
	
	def private computeHierarchicalTopologicalSort(RootCandidateScenario scenario){
		val Map<Scenario, List<PrecedenceNode>> result = newHashMap()
		
		result.put(scenario, computeTopologicalSort(scenario))
		
		for (innerScenario : scenario.eAllContents.filter(Scenario).toIterable){
			Assert.isTrue(!result.containsKey(innerScenario))
			result.put(innerScenario, computeTopologicalSort(innerScenario))
		}
		
		return result
	}
	
	def private computeTopologicalSort(Scenario scenario){
		/*
		 * Topological sort: 
		 * Base case - all nodes visited.
		 * Recursive case - Add to the list of visited the non visited node that does not come before any non-visited node. 
		 * 	And keep going.
		 */
		 
		 // Use child nodes of the scenario (and HierarchicalPorts only, because the grand child nodes will be topologically sorted recursively
		 val Set<PrecedenceNode> unSortedNodes = getScenarioNodesToBeSorted(scenario)
		 val Map<PrecedenceNode, Boolean> inSorted = new HashMap(unSortedNodes.size)
		 unSortedNodes.forEach[n | inSorted.put(n, false)]
		 val sortedNodes = newLinkedList()
		 val candidates = newLinkedList()
		 // The successors and predecessor sets must be restricted to the current scenario.
		 unSortedNodes.forEach[n |
		 	if (n.succeeds.filter[unSortedNodes.contains(it)].empty){
		 		candidates.push(n)
		 	}
		 ]
		 
		 Assert.isTrue(!candidates.empty)
		 
		 while (sortedNodes.size < unSortedNodes.size){
		 	val n = candidates.pop
		 	sortedNodes.add(n)
		 	inSorted.put(n, true)
		 	n.precedes.filter[unSortedNodes.contains(it)].forEach[s | 
		 		if (s.succeeds.filter[unSortedNodes.contains(it)].forall[ps | inSorted.get(ps)]){
		 			candidates.push(s)
		 		}
		 	]
		 }
		 
		 return sortedNodes
	}
	
	def getScenarioNodesToBeSorted(Scenario scenario) {
		val internal_nodes = scenario.cosimunits.flatMap[u | #[u as PrecedenceNode] + u.ports].toSet
		if (scenario instanceof HierarchicalCosimUnit){
			internal_nodes.addAll(scenario.ports)
		}
		return internal_nodes
	}
	
	override process(Deque<VariantDiagram> ns, String variantID, ConstraintsStack constraints, HintConfiguration cs) {
		if (runner !== null) {
			// Locate scenario where computation order should be generated. Search ns and it's parents for a CandidateScenario alternative.
			val scenario = ModelQuery.findRootScenario(ns)
			val order = computeOperationOrder(scenario, ns, cs)
			runner.run(scenario, order, variantID)
		}
	}
	
}