package ua.ansymo.hintco

import org.eclipse.emf.common.util.BasicEList

type Int wraps int

abstract class IDed {
	String identifier
}

class HintConfiguration {
	contains RootCandidateScenario[] candidates
	contains CosimUnitDeclaration[] csuDeclarations
	refers VariantDiagram root
	contains VariantDiagram[] nodes
}

class VariantDiagram extends IDed {
	refers VariantDiagram[] children
	refers Alternative alternative
	derived String name get {
		val prefix = identifier+"_"
		if (alternative !== null){
			if (alternative instanceof RootCandidateScenario){
				prefix + "sce " + (alternative as RootCandidateScenario).name
			} else if (alternative instanceof XorPortAdaptation) {
				prefix + "xor " + (alternative as XorPortAdaptation).name
			} else if (alternative instanceof XorUnitAdaptation) {
				prefix + "xor " + (alternative as XorUnitAdaptation).name
			} else if (alternative instanceof ExtrapolationAdaptation) {
				prefix + "extra " + (alternative as ExtrapolationAdaptation).name
			} else if (alternative instanceof InterpolationAdaptation) {
				prefix + "intra " + (alternative as InterpolationAdaptation).name
			} else {
				prefix + "implement"
			}
		}
		else {
			prefix + "root"
		}
	}
}

abstract class PrecedenceNode {
	refers PrecedenceNode[] precedes opposite succeeds
	refers PrecedenceNode[] succeeds opposite precedes
	refers PrecedenceNode before opposite after
	refers PrecedenceNode after opposite before
}

class RootCandidateScenario extends IDed, Alternative, Scenario {
	derived String name get {
		identifier + "(w=" + weight + ")"
	}
	double stopTime
	double stepSize
	double outputStepSize
	int maxInitIterations = "10"
}

abstract class Scenario extends IDed {
	contains UnitInstance[] cosimunits opposite scenario
}

class HierarchicalCosimUnit extends Scenario,UnitInstance {
	op boolean valid() {
		ports.forall[p | p instanceof HierarchicalUnitPort]
	}
}

class CosimUnitDeclaration extends IDed {
	String path
	String guid
}

abstract class UnitInstance extends IDed,PrecedenceNode{
	refers Scenario scenario opposite cosimunits
	contains UnitAdaptation adaptation opposite unit
	contains PortInstance[] ports opposite unit
	
	op UnitAdaptation[] selectedAdaptations() {
		if (adaptation === null){
			return new BasicEList(#[])
		}
		if (adaptation.selected){
			if (adaptation instanceof DecompositionUnitAdaptation){
				return (adaptation as DecompositionUnitAdaptation).selectedAdaptations()
			}
			return new BasicEList(#[adaptation])
		}
		
		return new BasicEList(#[])
	}
	
	op OutputPortInstance[] getOutputPorts(){
		ports.filter(OutputPortInstance).filter[!it.isInput].toEList
	}
	op InputPortInstance[] getInputPorts(){
		ports.filter(InputPortInstance).filter[it.isInput].toEList
	}
	
	op String toString(){
		identifier
	}
}

class CosimUnitInstance extends UnitInstance {
	refers CosimUnitDeclaration[1] declaration
}

abstract class PortInstance extends PrecedenceNode,IDed {
	refers UnitInstance unit opposite ports
	contains PortAdaptation adaptation opposite port
	
	op PortAdaptation[] selectedAdaptations() {
		if (adaptation === null){
			return new BasicEList(#[])
		}
		if (adaptation.selected){
			if (adaptation instanceof DecompositionPortAdaptation){
				return (adaptation as DecompositionPortAdaptation).selectedAdaptations()
			}
			return new BasicEList(#[adaptation])
		}
		
		return new BasicEList(#[])
	}
	
	op boolean isInput() {
		false
	}
	
	op boolean getsValueFrom(PortInstance p){
		return false
	}
	
	op String toString(){
		unit.toString()+"."+identifier
	}
}

class HierarchicalUnitPort extends InputPortInstance,OutputPortInstance{
	op boolean isInput() {
		// A port is an input if it gets its value from an external port.
		val auxIsInput = ! unit.eAllContents.filter(OutputPortInstance).exists[p | p == valueFrom]
		val auxAltIsInput = altIsInput()
		if ((auxIsInput && !auxAltIsInput) || (!auxIsInput && auxAltIsInput)){
			throw new RuntimeException("Something wrong with the implementation of isInput")
		}
		auxIsInput
	}
	op boolean altIsInput() {
		// A port is an input if it is connected to internal input ports or internal unit ports that are inputs themselves.
		if (valueTo.empty){
			false // A port connected to nothing must be an output
		} else {
			valueTo.forall[trgP | 
				if (trgP.unit === unit){
					false // We don't tolerate feedtrough connections on hierarchical units.
				} else {
					val internal = unit.eAllContents.filter(InputPortInstance).exists[ip | ip===trgP]
					if (! internal ){
						false
					} else if (trgP instanceof HierarchicalUnitPort){
						trgP.altIsInput
					} else {
						true					
					}
				}
			]
		}
	}
}

class InputPortInstance extends PortInstance {
	float extrapolationCost = "1.0"
	refers OutputPortInstance[] internalValueTo opposite internalValueFrom
	refers OutputPortInstance valueFrom opposite valueTo
	// Recursively ignore HierarchicalUnitPort and get the OutputPortInstance that generates this value
	op OutputPortInstance getRealValueFrom() {
		if (valueFrom instanceof HierarchicalUnitPort){
			(valueFrom as HierarchicalUnitPort).realValueFrom
		} else {
			valueFrom
		}
	}
	op boolean getsValueFrom(PortInstance p){
		if (valueFrom === p){
			return true
		} else if (valueFrom instanceof InputPortInstance){
			return (valueFrom as InputPortInstance).getsValueFrom(p)
		} else {
			return false
		}
	}
	
	op boolean isInput() {
		true
	}
}

class OutputPortInstance extends PortInstance {
	refers InputPortInstance[] valueTo opposite valueFrom
	refers InputPortInstance[] internalValueFrom opposite internalValueTo
	op boolean isInput() {
		false
	}
}

abstract class Alternative
{
	int weight
	boolean selected
	refers Alternative[] implies
}

abstract class Adaptation extends Alternative
{
	
}

abstract class PortAdaptation extends Adaptation
{
	refers PortInstance port opposite adaptation
	refers DecompositionPortAdaptation parent opposite children
	op PortInstance adapted() {
		if (port !== null){
			return port
		}
		if (parent === null){
			return null
		}
		return parent.adapted()
	}
}

abstract class DecompositionPortAdaptation extends PortAdaptation {
	contains PortAdaptation[] children opposite parent
	derived String name get {
		"(w=" + weight + ")"
	}
	op PortAdaptation[] selectedAdaptations() {
		val res = new BasicEList(children.size)
		for (c : children.filter[a | a.selected]){
			if (c instanceof DecompositionPortAdaptation){
				res.addAll(c.selectedAdaptations())
			} else {
				res.add(c)
			}
		}
		return res
	}
	
}

abstract class UnitAdaptation extends Adaptation {
	refers UnitInstance unit opposite adaptation
	refers DecompositionUnitAdaptation parent opposite children
	op UnitInstance adapted() {
		if (unit !== null){
			return unit
		}
		if (parent === null){
			return null
		}
		return parent.adapted()
	}
}

abstract class DecompositionUnitAdaptation extends UnitAdaptation {
	contains UnitAdaptation[] children opposite parent
	derived String name get {
		"(w=" + weight + ")"
	}
	op UnitAdaptation[] selectedAdaptations() {
		val res = new BasicEList(children.size)
		for (c : children.filter[a | a.selected]){
			if (c instanceof DecompositionUnitAdaptation){
				res.addAll(c.selectedAdaptations())
			} else {
				res.add(c)
			}
		}
		return res
	}
	
}

class XorUnitAdaptation extends DecompositionUnitAdaptation {
	
}

class XorPortAdaptation extends DecompositionPortAdaptation {
	
}

abstract class ApproximationAdaptation extends PortAdaptation {
	int order
}

class MultiRateAdaptation extends UnitAdaptation {
	int rate
	derived String name get {
		rate + "(w=" + weight + ")"
	}
}

class PowerBondAdaptation extends UnitAdaptation {
	refers PortInstance effort
	refers PortInstance flow
	refers OutputPortInstance pOut
	refers InputPortInstance pIn
	op PowerBondAdaptation getDual() {
		var PowerBondAdaptation res = null
		if (effort instanceof InputPortInstance && flow instanceof OutputPortInstance){
			res = unit.scenario.cosimunits
			.flatMap[u | u.eAllContents.filter(PowerBondAdaptation).toIterable]
			.filter[a | a.effort instanceof OutputPortInstance && a.flow instanceof InputPortInstance]
			.findFirst[a | (a.effort as OutputPortInstance).valueTo.contains(effort) && (a.flow as InputPortInstance).valueFrom === flow]
		} else if (effort instanceof OutputPortInstance && flow instanceof InputPortInstance) {
			res = unit.scenario.cosimunits
			.flatMap[u | u.eAllContents.filter(PowerBondAdaptation).toIterable]
			.filter[a | a.effort instanceof InputPortInstance && a.flow instanceof OutputPortInstance]
			.findFirst[a | (a.effort as InputPortInstance).valueFrom === effort && (a.flow as OutputPortInstance).valueTo.contains(flow)]
		} else {
			throw new RuntimeException("Invalid effort and flow in power bond adaptation")
		}
		if (res===null){
			throw new RuntimeException("Power bond adaptation has no dual")
		}
		res
	}
	derived String name get {
		if (effort!==null && flow !== null){
			"PBond_" + effort.identifier + "_" + flow.identifier + "(w=" + weight + ")"
		} else {
			"PBond(w=" + weight + ")"
		}
	}
}

class ExtrapolationAdaptation extends ApproximationAdaptation {
	derived String name get {
		order + "(w=" + weight + ")"
	}
}

class RollbackInterpolationAdaptation extends ExtrapolationAdaptation {
	
}

class InterpolationAdaptation extends ApproximationAdaptation {
	derived String name get {
		order + "(w=" + weight + ")"
	}
}

