package ua.ansymo.hintco

import java.util.Deque
import org.eclipse.core.runtime.Assert
import org.jgrapht.alg.interfaces.AStarAdmissibleHeuristic
import org.jgrapht.alg.shortestpath.AStarShortestPath
import org.jgrapht.graph.DirectedMultigraph
import org.jgrapht.util.SupplierUtil
import org.jgrapht.graph.DefaultWeightedEdge

class AlgebraicLoopVariantProcessor implements IVariantProcessor {
	
	ICosimRunner runner
	
	new(ICosimRunner r){
		runner = r
	}
	
	/*
	 * Transforms a scenario into a cosim unit graph. 
	 * The scenario already has some ordering constraints, as cosim units and ports are both precedence nodes.
	 * The precedence constraints are placed in a way that it is easy to know whether two cosim units A and B should precede each other: 
	 * 	whenever there is a precedence path that goes between them, of the form
	 *  A.out -> B.in -> B where A.out is an output port of A, and B.in is an input port of B, 
	 * then an edge should be created A --w-> B where w is the cost of extrapolating B.in.
	 * In every other case, there is no edge between A->B.
	 * This way, we preserve the extrapolation adaptations that have been defined by other hints.
	 */
	def transformToCosimUnitGraph(Scenario scenario) {
		val resultGraph = new DirectedMultigraph(SupplierUtil.createSupplier(CosimUnitInstance), SupplierUtil.createDefaultWeightedEdgeSupplier, true)
		
		val vertices = scenario.cosimunits.filter(CosimUnitInstance).toList
		
		vertices.forEach[u | Assert.isTrue(resultGraph.addVertex(u))]
		
		for (A : vertices) {
			for (Aout : A.outputPorts) {
				for (Bin : Aout.precedes) {
					if (Bin instanceof InputPortInstance) {
						if (Bin.unit !== A) {
							// Found path of the form A.out -> B.in
							for (B : Bin.precedes) {
								if (B == Bin.unit) {
									// Found path of the form A.out -> B.in -> B
									val ABEdge = resultGraph.addEdge(A, B as CosimUnitInstance)
									resultGraph.setEdgeWeight(ABEdge, Bin.extrapolationCost)
								}
							}
						}
					}
				}
			}
		}
		
		return resultGraph
	}
	
	/*
	 * Solves the optimization problem for a given strong component.
	 * 1. Builds the search graph.
	 * 2. Uses AStar algorithm (from JGraph lib) to find the optimal solution.
	 */
	def findOptimalTriggerSequenceStrongComponent(DirectedMultigraph<CosimUnitInstance, DefaultWeightedEdge> sc) {
		val searchGraph = new LazySearchTree(sc)
		val alg = new AStarShortestPath(searchGraph, new BasicHeuristic())
		alg.getPath(searchGraph.root, searchGraph.bottom)
	}
	
	/*
	 * Solves the optimization problem for each strong component in the given graph.
	 * 1. Compute strong components.
	 * 2. For each component SC:
	 * 2.1 Find the optimal trigger sequence
	 * 3. Join the trigger sequences found for each component, and return
	 */
	def findOptimalTriggerSequence(DirectedMultigraph<CosimUnitInstance, DefaultWeightedEdge> graph) {
		// TODO
	}
	
	/*
	 * This method will perform roughly the following steps:
	 * 1. Transform the scenario (assumed to be non-hierarchical) into a graph where each node represents a cosim unit, and each connection between cosim units represents the cost of executing the latter after executing the former.
	 * 2. Find the best way to break the algebraic loops, that is, find the optimal trigger sequence.
	 * 3. Define the operation schedule from the optimal trigger sequence. As this breaks the loop, it means that some semantic adaptations have to be overridden. 
	 * Since the last step is tricky when higher order approximations and power bond adaptations are used, these are not supported for now. 
	 */
	override process(Deque<VariantDiagram> ns, String variantID, ConstraintsStack constraints, HintConfiguration cs) {
		val scenario = ModelQuery.findRootScenario(ns)
		
		if (!scenario.eAllContents.filter(Scenario).empty) { 
			throw new UnsupportedOperationException("Algebraic loop optimization is not implemented for hierarchical co-simulation scenarios.")
		}
		scenario.eAllContents.filter(InterpolationAdaptation).forEach[adapt | Assert.isTrue(adapt.order == 0, "Only constant interpolations supported in algebraic loop optimizer. Adaptation " + adapt + " is not a constant interpolation.")]
		Assert.isTrue(scenario.eAllContents.filter(PowerBondAdaptation).empty, "Powerbond adaptations cannot be used with algebraic loop optimizer, as the optimal trigger sequence may override these.")
		
	}
	
}

class BasicHeuristic implements AStarAdmissibleHeuristic<LazySearchNode> {
	override getCostEstimate(LazySearchNode sourceVertex, LazySearchNode targetVertex) {
		0.0 // TODO Better heuristics might be to get the min weight of possible edges
	}
}