/*
 * This file has been developed at the University of Munich, Chair for Programming & Software Engineering.
 * 
 * This file is licensed under the Eclipse Public License (EPL) 1.0
 * 
 */
package eu.uml4soa.utbm.u2m.semantics;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.emf.common.util.EList;

import eu.mdd4soa.smm.behaviour.Compensate;
import eu.mdd4soa.smm.behaviour.CompensateAll;
import eu.mdd4soa.smm.behaviour.CompensationHandler;
import eu.mdd4soa.smm.behaviour.DataHandling;
import eu.mdd4soa.smm.behaviour.Decision;
import eu.mdd4soa.smm.behaviour.EventHandler;
import eu.mdd4soa.smm.behaviour.ExceptionHandler;
import eu.mdd4soa.smm.behaviour.Handler;
import eu.mdd4soa.smm.behaviour.Loop;
import eu.mdd4soa.smm.behaviour.Parallel;
import eu.mdd4soa.smm.behaviour.Path;
import eu.mdd4soa.smm.behaviour.PathBasedPartition;
import eu.mdd4soa.smm.behaviour.Receive;
import eu.mdd4soa.smm.behaviour.Reply;
import eu.mdd4soa.smm.behaviour.Send;
import eu.mdd4soa.smm.behaviour.SendAndReceive;
import eu.mdd4soa.smm.behaviour.ServiceActivity;
import eu.mdd4soa.smm.behaviour.ServiceElement;
import eu.mdd4soa.smm.behaviour.ServiceInteraction;
import eu.mdd4soa.smm.behaviour.Terminate;
import eu.mdd4soa.smm.behaviour.Throw;
import eu.uml4soa.utbm.u2m.MathUtil;
import eu.uml4soa.utbm.u2m.SemanticsException;
import eu.uml4soa.utbm.u2m.model.CAutomaton;
import eu.uml4soa.utbm.u2m.model.CDirection;
import eu.uml4soa.utbm.u2m.model.CState;
import eu.uml4soa.utbm.u2m.model.CTType;
import eu.uml4soa.utbm.u2m.model.CTransition;

/**
 * This class converts IOM orchestrations into MIOs.
 * 
 * @author Philip Mayer, mayer@pst.ifi.lmu.de
 * 
 */
public class OrchestrationSemanticsFunction {

	/**
	 * Converts one service activity into a MIO.
	 * 
	 * @param serviceActivity
	 * @return
	 * @throws SemanticsException
	 */
	public static CAutomaton transform(ServiceActivity serviceActivity) throws SemanticsException {
		OrchestrationSemanticsFunction sem= new OrchestrationSemanticsFunction(serviceActivity);
		sem.transform();
		return sem.getSemantics();
	}

	/**
	 * The input service activity to transform.
	 */
	private ServiceActivity fInputServiceActivity;

	/**
	 * The end result of the tranformation.
	 */
	private CAutomaton fResultingAutomaton;

	/**
	 * A counter for ensuring that all states have different names in the
	 * automaton.
	 */
	private int i;

	private OrchestrationSemanticsFunction(ServiceActivity sa) {
		fInputServiceActivity= sa;
	}

	private void transform() throws SemanticsException {
		fResultingAutomaton= mio(fInputServiceActivity);
	}

	private CAutomaton getSemantics() {
		return fResultingAutomaton;
	}


	/**
	 * A service activity is a container.
	 * 
	 * @param sa
	 * @return
	 * @throws SemanticsException
	 */
	private CAutomaton mio(ServiceActivity sa) throws SemanticsException {

		CAutomaton innerAutomaton= mio(sa.getChildren());

		// Add interrupting receives.
		handleInterruptingReceives(innerAutomaton, sa.getInterruptingReceives());

		// Can we handle some exceptions?
		List<ExceptionHandler> exHandlers= getExceptionHandlers(sa.getHandlers());
		for (ExceptionHandler eh : exHandlers) {

			List<CState> eends= new ArrayList<CState>(innerAutomaton.getErrorEndStates());

			for (CState errorEnd : eends) {

				String faultName= eh.getExceptionType().getName();
				List<CTransition> incoming= errorEnd.getIncoming();

				CTransition transition= incoming.iterator().next();
				String exceptionThrown= getThrownException(transition);

				if ( (exceptionThrown != null) && (exceptionThrown.equals(faultName))) {

					CAutomaton handler= encloseHandler(eh);
					if (!handler.getErrorEndStates().isEmpty())
						throw new SemanticsException("The exception handler " + eh.getName()
								+ " has error end states. This is not currently supported.");

					innerAutomaton.addStatesAndTransitions(handler);

					/*
					 * Interspacing is not necessary here, as every transition
					 * before an errorEnd is a throw() (and thus an error hand
					 * has no backlink).
					 */
					innerAutomaton.mergeStates(errorEnd, handler.getStart());

					innerAutomaton.removeErrorEndState(errorEnd);

					// Add non-error ends.
					innerAutomaton.addNonErrorEnds(handler.getNonErrorEndStates());
				}
			}
		}

		// Are there any event handlers?
		List<EventHandler> eventHandlers= getEventHandlers(sa.getHandlers());
		for (EventHandler eventHandler : eventHandlers) {

			CAutomaton handlerMio= mio(eventHandler.getChildren());
			innerAutomaton= handleEventHandler(innerAutomaton, handlerMio, eventHandler.getName());
		}

		/*
		 * Check whether we have compensation edges inside which we can use. Do
		 * this BEFORE attaching the compensation handler of the current scope
		 * to avoid triggering that compensation.
		 */
		List<CTransition> compensationCallTransitions= innerAutomaton.getCompensationCallTransitions();
		for (CTransition cTransition : compensationCallTransitions) {

			String scopeToCompensate= getScopeToCompensate(cTransition);

			/*
			 * Get all comp. handler installations backwards from the current
			 * location. Only those may be compensated.
			 */
			List<CTransition> installSites= innerAutomaton.getWhatsInstalledHere(cTransition);

			// Cannot compensate?
			if (installSites == null || installSites.isEmpty())
				continue;

			String compsHandled= "";

			/*
			 * Create a string of compensation handlers.
			 */
			CAutomaton compTomaton= new CAutomaton();
			boolean found= false;
			for (CTransition cInstallTransition : installSites) {

				String compensatedScopeName= cInstallTransition.getReferencedActivity();

				if (!compsHandled.isEmpty())
					compsHandled+= ",";
				compsHandled+= compensatedScopeName;

				// If a scope to compensate is given and it is the wrong
				// one, continue.
				if (scopeToCompensate != null && !scopeToCompensate.equals(compensatedScopeName))
					continue;

				found= true;

				// We need to invoke this compensation handler.
				CAutomaton tom= innerAutomaton.getCompensationHandlerOfScope(compensatedScopeName);

				/*
				 * GOTCHA We do allow comp.handler installations in compensation
				 * handlers. However, they are no longer reachable once the
				 * comp. handler is exited. So we can simply combine all non-err
				 * ends and remove the comp.installs.
				 */
				tom.clearAllCompensationInstallations();

				// Reduce to one end.
				tom.mergeEnds("AddingCompHandler");

				if (innerAutomaton.isInLoop(cInstallTransition)) {

					CState oldStart= tom.getStart();
					for (CState nonErrorEnd : tom.getNonErrorEndStates()) {
						tom.addTransition("redoComp", CTType.MUST, CDirection.INT, nonErrorEnd.getName(), oldStart.getName());
					}
				}

				if (compTomaton.getStart() == null) {
					// First one.
					compTomaton.addStatesAndTransitions(tom);
					compTomaton.setStart(tom.getStart().getName());
					compTomaton.addNonErrorEnds(tom.getNonErrorEndStates());
					compTomaton.addErrorEnds(tom.getErrorEndStates());
					// Ignore everything else.
				} else {
					// not the first one.
					int j= 0;
					for (CState endState : compTomaton.getNonErrorEndStates()) {
						j++;
						CAutomaton copy= tom.createSuffixCopy(j);
						compTomaton.removeNonErrorEndState(endState);
						compTomaton.addStatesAndTransitions(copy);
						compTomaton.mergeStates(endState, copy.getStart());
						compTomaton.addNonErrorEnds(copy.getNonErrorEndStates());
						compTomaton.addErrorEnds(copy.getErrorEndStates());
					}
				}
			}


			// No compensation possible?
			if (!found)
				continue;

			// OK, we can compensate.
			boolean success= innerAutomaton.removeTransition(cTransition);
			if (!success)
				throw new SemanticsException("Could not remove transition " + cTransition);

			// Ends:
			List<CState> nonErrorEndStates= compTomaton.getNonErrorEndStates();

			// We have merged ends above - this automaton therefore does not
			// have more than one non-error state.
			if (nonErrorEndStates.size() > 1)
				throw new SemanticsException("BUG: Compensation handler with more than one non-error end.");

			CState endOfCompensationHandler= compTomaton.getNonErrorEndStates().iterator().next();

			// We have successfully compensated a scope and need to note this
			// fact (the comp. handlers are now gone on this path)
			CState newEnd= compTomaton.addNonErrorEnd("compensatedEndOf" + endOfCompensationHandler.getName());
			compTomaton.removeNonErrorEndState(endOfCompensationHandler);
			compTomaton.addTransition("compHandled(" + compsHandled + ")", CTType.MUST, CDirection.INT, endOfCompensationHandler.getName(),
					newEnd.getName());

			// This here is an instance of consecutive execution.
			// Interspacing might be necessary.
			CState currentStart= compTomaton.getStart();
			if (!cTransition.getFrom().getOutgoing().isEmpty() && !currentStart.getIncoming().isEmpty()) {
				currentStart= compTomaton.addPreSpacing(currentStart);
			}

			// Add the comptomaton to the innerautomaton...
			innerAutomaton.addStatesAndTransitions(compTomaton);

			// Merge...
			innerAutomaton.mergeStates(cTransition.getFrom(), currentStart);

			CState end= innerAutomaton.mergeStates(cTransition.getTo(), newEnd);

			// This end might be an ... end!
			if (innerAutomaton.getNonErrorEndStates().contains(cTransition.getTo())) {
				innerAutomaton.getNonErrorEndStates().remove(cTransition.getTo());
				innerAutomaton.getNonErrorEndStates().add(end);
			}
		}

		/*
		 * Compensation handlers? Do this AFTER looking for compensation edges.
		 * Otherwise, a new compensateAll (from an exception handler, for
		 * example) will trigger compensation of the current scope which is not
		 * allowed.
		 * 
		 * GOTCHA: Compensation Calls inside comp.handlers must have been
		 * handled there - they are no longer valid out here. In fact,
		 * comp.installations were removed as the comp. handler was weaved.
		 */
		List<CompensationHandler> compHandlers= getCompensationHandlers(sa.getHandlers());
		for (CompensationHandler compensationHandler : compHandlers) {
			CAutomaton compMio= mio(compensationHandler.getChildren());

			// Store mio
			innerAutomaton.addCompensationHandler(sa.getName(), compMio);

			// Add info
			List<CState> nonErrorEndStates= innerAutomaton.getNonErrorEndStates();
			for (CState cState : nonErrorEndStates) {

				/*
				 * Add a new end. Reason: The end of the activity might get
				 * merged with another node which is the target of a backloop
				 * (for example by an event handler). In this case it is not
				 * decidable whether the activity comp.install was in a loop.
				 * 
				 * See test case "testEventTwoEventsOneWithThrowAndCatch"
				 */
				i++;
				String s= "ic of " + cState.getName() + "(" + i + ")";
				innerAutomaton.addState(s);
				innerAutomaton.addTransition("compInstalled(" + sa.getName() + ")", CTType.MUST, CDirection.INT, cState.getName(), s);
				innerAutomaton.removeNonErrorEndState(cState);
				innerAutomaton.addNonErrorEnd(s);
			}
		}

		// Due to exception handlers, we may have several non-error ends which
		// can be combined here.
		innerAutomaton.mergeEnds("end sa " + sa.getName());

		return innerAutomaton;
	}

	private String getThrownException(CTransition transition) {
		String actionLabel= transition.getActionLabel();
		if (actionLabel.startsWith("throw")) {
			int first= actionLabel.indexOf("(");
			int last= actionLabel.indexOf(")");
			if (first != -1 && last != -1)
				return actionLabel.substring(first + 1, last);
		}
		return null;
	}

	private void handleInterruptingReceives(CAutomaton innerAutomaton, EList<Receive> children) throws SemanticsException {

		for (Receive recv : children) {
			// Remove all existing non-error ends - a ServiceActivity with
			// an interrupting edge may not have those in UML4SOA.
			List<CState> nonErrorEndStates= new ArrayList<CState>(innerAutomaton.getNonErrorEndStates());
			for (CState cState : nonErrorEndStates) {
				innerAutomaton.removeNonErrorEndState(cState);
			}

			String name= recv.getOperation().getName();

			/*
			 * Create one new end for each state in the base automaton. Combine
			 * later on as usual. This doubles the state space shortly, but it
			 * is so convenient... :)
			 */

			List<CState> states= new ArrayList<CState>(innerAutomaton.getStates());
			for (CState cState : states) {
				if (!innerAutomaton.getNonErrorEndStates().contains(cState) && !innerAutomaton.getErrorEndStates().contains(cState)) {

					i++;
					String end= "interruptingEnd(" + i + ")";
					innerAutomaton.addState(end);
					innerAutomaton.addNonErrorEnd(end);

					innerAutomaton.addTransition(name, CTType.MUST, CDirection.IN, cState.getName(), end);
				}
			}
		}
		innerAutomaton.mergeEnds("interruption");
	}

	private String getScopeToCompensate(CTransition cTransition) throws SemanticsException {

		String actionLabel= cTransition.getActionLabel();
		if (!actionLabel.startsWith("compensate "))
			return null;

		return actionLabel.substring("compensate ".length());
	}

	/**
	 * Encloses the children of this handler in a wrapper which indicates where
	 * the handler began and ended. This is only done for readability reasons of
	 * the MIO created.
	 * 
	 * @param exceptionHandler
	 * @return
	 * @throws SemanticsException
	 */
	private CAutomaton encloseHandler(ExceptionHandler exceptionHandler) throws SemanticsException {

		CAutomaton handler= new CAutomaton();
		i++;
		CState start= handler.addState("start exc handler " + exceptionHandler.getName() + "(" + i + ")");

		CAutomaton children= mio(exceptionHandler.getChildren());

		handler.addStatesAndTransitions(children);
		start= handler.mergeStates(start, children.getStart());
		handler.setStart(start.getName());

		handler.addErrorEnds(children.getErrorEndStates());

		List<CState> nonErrorEndStates= children.getNonErrorEndStates();
		for (CState cState : nonErrorEndStates) {
			CState end= handler.addState("end exchandler " + exceptionHandler.getName() + "(" + i + ")");
			CState newEnd= handler.mergeStates(end, cState);
			handler.addNonErrorEnd(newEnd.getName());
		}
		return handler;
	}

	private List<ExceptionHandler> getExceptionHandlers(EList<Handler> handlers) {
		List<ExceptionHandler> hhs= new ArrayList<ExceptionHandler>();
		for (Handler handler : handlers) {
			if (handler instanceof ExceptionHandler)
				hhs.add((ExceptionHandler) handler);
		}

		return hhs;
	}

	private List<EventHandler> getEventHandlers(EList<Handler> handlers) {
		List<EventHandler> hhs= new ArrayList<EventHandler>();
		for (Handler handler : handlers) {
			if (handler instanceof EventHandler)
				hhs.add((EventHandler) handler);
		}
		return hhs;
	}

	private List<CompensationHandler> getCompensationHandlers(EList<Handler> handlers) {
		List<CompensationHandler> hhs= new ArrayList<CompensationHandler>();
		for (Handler handler : handlers) {
			if (handler instanceof CompensationHandler)
				hhs.add((CompensationHandler) handler);
		}

		return hhs;
	}


	/**
	 * Deal with sequential behaviour. This means attaching one MIO after the
	 * other, merging the end- and start states as appropriate.
	 * 
	 * 
	 * @param elements
	 * @return
	 * @throws SemanticsException
	 */
	private CAutomaton mio(EList<ServiceElement> elements) throws SemanticsException {

		CAutomaton finalResult= new CAutomaton();
		CAutomaton current= null;

		boolean first= true;
		for (ServiceElement serviceElement : elements) {

			current= mio(serviceElement);

			if (current.getStart() == null)
				throw new SemanticsException("Sequential: Got a mio without start node from " + serviceElement);

			// Start is the first one of all of them.
			if (first) {
				finalResult.addStatesAndTransitions(current);
				finalResult.setStart(current.getStart().getName());
				// Copy end states.
				finalResult.addNonErrorEnds(current.getNonErrorEndStates());
				finalResult.addErrorEnds(current.getErrorEndStates());
			}

			if (!first) {

				// Normal Ends: Attach (a copy of) the next automaton.
				List<CState> endStates= new ArrayList<CState>(finalResult.getNonErrorEndStates());
				for (CState cState : endStates) {

					i++;
					CAutomaton copy= endStates.size() == 1 ? current : current.createSuffixCopy(i);
					finalResult.addStatesAndTransitions(copy);
					finalResult.removeNonErrorEndState(cState);

					// Copy end states.
					finalResult.addNonErrorEnds(copy.getNonErrorEndStates());
					finalResult.addErrorEnds(copy.getErrorEndStates());

					// Interspacing necessary?
					if (!cState.getOutgoing().isEmpty() && !copy.getStart().getIncoming().isEmpty()) {
						cState= finalResult.addPostSpacing(cState);
					}

					// Merge.
					finalResult.mergeStates(cState, copy.getStart());
				}

				// Error ends: Just add them.
				finalResult.addErrorEnds(current.getErrorEndStates());
			}

			first= false;
		}

		return finalResult;
	}

	private CAutomaton mio(ServiceElement serviceElement) throws SemanticsException {

		// Service Interaction
		if (serviceElement instanceof ServiceInteraction)
			return mio((ServiceInteraction) serviceElement);

		// Structured nodes
		if (serviceElement instanceof PathBasedPartition)
			return mio((PathBasedPartition) serviceElement);

		// Service Activity (nested)
		if (serviceElement instanceof ServiceActivity)
			return mio((ServiceActivity) serviceElement);

		if (serviceElement instanceof Throw)
			return mio((Throw) serviceElement);

		if (serviceElement instanceof Compensate)
			return mio((Compensate) serviceElement);

		if (serviceElement instanceof CompensateAll)
			return mio((CompensateAll) serviceElement);

		if (serviceElement instanceof DataHandling)
			return mio((DataHandling) serviceElement);

		if (serviceElement instanceof Terminate)
			return mio((Terminate) serviceElement);

		throw new SemanticsException("Wrong type for ServiceInteraction " + i);
	}

	private CAutomaton mio(ServiceInteraction i) throws SemanticsException {

		if (i instanceof SendAndReceive)
			return mio((SendAndReceive) i);

		if (i instanceof Send)
			return mio((Send) i);

		if (i instanceof Receive)
			return mio((Receive) i);

		if (i instanceof Reply)
			return mio((Reply) i);

		throw new SemanticsException();
	}

	private CAutomaton mio(PathBasedPartition pbp) throws SemanticsException {

		if (pbp instanceof Decision)
			return mio((Decision) pbp);

		if (pbp instanceof Parallel)
			return mio((Parallel) pbp);

		if (pbp instanceof Loop)
			return mio((Loop) pbp);

		throw new SemanticsException("Wrong type for PathBasedPartition: " + pbp);
	}

	/**
	 * 
	 * Loops.
	 * 
	 * The problem is that we want to remember comp-installed-states through the
	 * loop. In each loop, we get n non-error states and m error states.
	 * 
	 * We do not rewire error states - these are throws, need to be handled
	 * somewhere else, loop is aborted in this case anyway.
	 * 
	 * For non-error states: Each end state corresponds to a set of installed
	 * comp. handlers (might be []). Once we have reached such an end state, we
	 * need to carry this information (i.e. the installed handlers) through ALL
	 * following loop runs.
	 * 
	 * This can be done as follows. Create a) an initial copy of the loop body
	 * for starters and b) a copy of the loop body for each possible set of
	 * installed handlers. These are the sets of the initial end states of the
	 * body (say, A and B), and their combinations (here: AB).
	 * 
	 * Merge them all together.
	 * 
	 * Now, as soon as we reach an end state in one of those copies, we know a)
	 * which comp.handlers have been installed in THIS copy, and b) as each copy
	 * corresponds to a set already, the complete set of installed handlers. We
	 * can use this to wire the end state a) to a different copy corresponding
	 * to the set of states (re-loop), and b) to create an end state (loop end),
	 * which will automatically be merged later on with others of its kind.
	 */
	private CAutomaton mio(Loop d) throws SemanticsException {

		ServiceElement path0= d.getChildren().get(0);
		if (! (path0 instanceof Path))
			throw new SemanticsException("Was expecting child of loop path to be a Path.");

		CAutomaton loop= mio( ((Path) path0).getChildren());

		// CASE WHERE NONERRORENDS == 0 (no loop)
		if (loop.getNonErrorEndStates().isEmpty())
			return loop;

		loop.mergeEnds("beforeLoop");

		// CASE WHERE NONERRORENDS.SIZE == 1 (just one loop)
		if (loop.getNonErrorEndStates().size() == 1) {

			CState oldStart= loop.getStart();
			CState oldEnd= loop.getNonErrorEndStates().iterator().next();

			// The loop.
			loop.addTransition("loop", CTType.MUST, CDirection.INT, oldEnd.getName(), oldStart.getName());
			return loop;
		}

		// CASE WHERE NONERRORENDS > 1

		/*
		 * We have one initial loop body, which has several non-error ends. Each
		 * of these non-error ends corresponds to a certain set of installed
		 * comp. handlers. Once the initial loop body hits one of these, this
		 * set is installed. This information needs to be stored across all
		 * subsequent loop runs. We do this by creating one new loop for each of
		 * the end states, to which traverse once we hit the end. Now, consider
		 * two subsequent runs in which different sets are installed. This needs
		 * to be stored, too, so we need one automaton for each combination of
		 * sets, too (powerset).
		 */

		Map<CState, Set<String>> installedCompHandlers= new LinkedHashMap<CState, Set<String>>();
		Set<Set<String>> sets= new LinkedHashSet<Set<String>>();

		List<CState> nonErrorEndStates= loop.getNonErrorEndStates();
		for (CState cState : nonErrorEndStates) {
			// Note that the set might be empty. That's okay.
			Set<String> whatsInstalledHere= loop.getWhatsInstalledHere(cState);
			installedCompHandlers.put(cState, whatsInstalledHere);
			sets.add(whatsInstalledHere);
		}

		Map<Set<String>, CAutomaton> automatons= new LinkedHashMap<Set<String>, CAutomaton>();
		Map<CAutomaton, Set<String>> automatons2= new LinkedHashMap<CAutomaton, Set<String>>();

		CAutomaton finalAutomaton= new CAutomaton();
		finalAutomaton.addStatesAndTransitions(loop);
		finalAutomaton.setStart(loop.getStart().getName());
		finalAutomaton.addNonErrorEnds(loop.getNonErrorEndStates());
		finalAutomaton.addErrorEnds(loop.getErrorEndStates());

		Set<Set<String>> allSets= MathUtil.powerset(sets);
		for (Set<String> set : allSets) {

			// Create an automaton.
			i++;
			CAutomaton tomForThisSet= loop.createSuffixCopy(i);
			automatons.put(set, tomForThisSet);
			automatons2.put(tomForThisSet, set);

			// Add to loop.
			finalAutomaton.addStatesAndTransitions(tomForThisSet);
		}

		// Rewire initial loop:
		for (CState nonErrorEndState : new ArrayList<CState>(nonErrorEndStates)) {
			Set<String> whatsInstalledHere= finalAutomaton.getWhatsInstalledHere(nonErrorEndState);
			CAutomaton cAutomaton= automatons.get(whatsInstalledHere);
			CState start= cAutomaton.getStart();

			// Loop
			finalAutomaton.addTransition("loop", CTType.MUST, CDirection.INT, nonErrorEndState.getName(), start.getName());

			// We know that we need spacing anyway.
			finalAutomaton.addPostSpacing(nonErrorEndState);
		}

		// Rewire the rest.
		for (CAutomaton tom : automatons2.keySet()) {

			Set<String> setThisAutomatonStandsFor= automatons2.get(tom);
			for (CState endState : tom.getNonErrorEndStates()) {

				// Get complete set
				Set<String> completeSet= new LinkedHashSet<String>();
				completeSet.addAll(setThisAutomatonStandsFor);
				completeSet.addAll(tom.getWhatsInstalledHere(endState));

				CAutomaton targetTom= automatons.get(completeSet);

				// Cross
				finalAutomaton.addTransition("loop", CTType.MUST, CDirection.INT, endState.getName(), targetTom.getStart().getName());

				// We know that we need spacing anyway.
				CState newEnd= finalAutomaton.addPostSpacing(endState);

				// Set the new end as a non-error end.
				finalAutomaton.addNonErrorEnd(newEnd.getName());
			}
		}

		return finalAutomaton;
	}

	private CAutomaton mio(Decision d) throws SemanticsException {
		i++;
		CAutomaton finalResult= new CAutomaton();
		CState theStart= finalResult.addState("begin " + "decision" + " " + d.getName() + "(" + i + ")");

		finalResult.setStart(theStart.getName());

		EList<ServiceElement> paths= d.getChildren();
		for (ServiceElement supposedPath : paths) {

			if (! (supposedPath instanceof Path))
				throw new SemanticsException("Was expecting the child of a decision to be a Path");

			Path path= (Path) supposedPath;

			EList<ServiceElement> children= path.getChildren();

			CAutomaton currentAutomaton= mio(children);

			// We need to check for interspace:
			CState currentStart= currentAutomaton.getStart();
			if (!currentStart.getIncoming().isEmpty()) {
				currentStart= currentAutomaton.addPreSpacing(currentStart);
			}

			finalResult.addStatesAndTransitions(currentAutomaton);

			// Merge the starts!
			CState mergeStates= finalResult.mergeStates(theStart, currentStart);
			finalResult.setStart(mergeStates.getName());
			theStart= finalResult.getStart();

			// Ends: Add all errors.
			finalResult.addErrorEnds(currentAutomaton.getErrorEndStates());

			// Ends: Normal ends: We CAN combine here, but only if the
			// compensation handlers installed at each end are identical.
			finalResult.addNonErrorEnds(currentAutomaton.getNonErrorEndStates());
		}

		// Interspacing necessary?
		List<CState> ends= new ArrayList<CState>(finalResult.getNonErrorEndStates());
		for (CState cState : ends) {
			if (!cState.getOutgoing().isEmpty()) {
				// We have a loop
				finalResult.addPostSpacing(cState);
			}
		}

		/*
		 * GOTCHA: Concious decision to use "mergeEnds" here instead of merging
		 * the end directly to ensure that compensation handlers are dealt with
		 * correctly.
		 */
		finalResult.mergeEnds("DecisionEnd");

		return finalResult;
	}

	/**
	 * Implementation of parallel behaviour.
	 * 
	 * The intuition of parallel behaviour is that multiple threads of the
	 * original UML4SOA specification occur in parallel. Each of these threads,
	 * however, must keep their own sequential behaviour.
	 * 
	 * All threads may have multiple endings already, and they get multiplied as
	 * usual.
	 * 
	 * @param f
	 * @return
	 * @throws SemanticsException
	 */
	private CAutomaton mio(Parallel f) throws SemanticsException {

		List<CAutomaton> toInterleave= new ArrayList<CAutomaton>();

		EList<ServiceElement> paths= f.getChildren();
		for (ServiceElement supposedPath : paths) {

			if (! (supposedPath instanceof Path))
				throw new SemanticsException("Was expecting the children of a parallel partition to be paths.");

			Path path= (Path) supposedPath;

			EList<ServiceElement> children= path.getChildren();
			toInterleave.add(mio(children));
		}

		CAutomaton left= null;
		for (CAutomaton right : toInterleave) {
			if (left == null)
				left= right;
			else {
				left= interleave(left, right, false);
			}
		}

		left.mergeEnds("End of interleave");
		return left;
	}

	private CAutomaton interleave(CAutomaton left, CAutomaton right, boolean rightIsHandler) {
		CState start1= left.getStart();
		CState start2= right.getStart();

		Map<String, StateCombo> comboMap= new LinkedHashMap<String, StateCombo>();

		CAutomaton combined= new CAutomaton();
		CState comboStart= combined.addState(start1.getName() + "<X>" + start2.getName());
		combined.setStart(comboStart.getName());

		StateCombo newStart= new StateCombo(start1, start2, comboStart);
		comboMap.put(newStart.combined.getName(), newStart);

		Set<StateCombo> visited= new LinkedHashSet<StateCombo>();
		List<StateCombo> queue= new ArrayList<StateCombo>();

		queue.add(newStart);
		while (!queue.isEmpty()) {

			List<StateCombo> queue2= new ArrayList<StateCombo>();

			for (StateCombo sc : queue) {

				if (visited.contains(sc))
					continue;

				visited.add(sc);

				// We have reached a combined end state which we need to
				// mark as an end. We go further, though, as there might
				// be outgoing links from the end states.
				if (left.getNonErrorEndStates().contains(sc.s1) && (right.getNonErrorEndStates().contains(sc.s2))) {
					combined.getNonErrorEndStates().add(sc.combined);
				}

				// We have reached an error end. Do not continue.
				if (left.getErrorEndStates().contains(sc.s1) || (right.getErrorEndStates().contains(sc.s2))) {
					combined.getErrorEndStates().add(sc.combined);
					continue;
				}

				/*
				 * The difference to normal parallel handling.
				 * 
				 * We have reached a state in which normal is at an end and the
				 * event handler is at the start. We can still get event
				 * receipts there, but THIS HERE IS AN END STATE.
				 * 
				 * GOTCHA Do not abort processing here. When aborting, the event
				 * handler cannot be started again in-between the last action of
				 * the scope and the first action of what comes after it; this
				 * is undesirable.
				 */
				if (rightIsHandler && left.getNonErrorEndStates().contains(sc.s1) && (right.getStart().equals(sc.s2))) {
					combined.getNonErrorEndStates().add(sc.combined);
				}

				List<CTransition> outgoings1= sc.s1.getOutgoing();
				for (CTransition cTransition : outgoings1) {

					// Move in s1, stay in s2
					String nextStateName= cTransition.getTo().getName() + "<X>" + sc.s2.getName();
					StateCombo nextCombo= comboMap.get(nextStateName);
					CState nextState= null;
					if (nextCombo == null) {
						nextState= combined.addState(nextStateName);
						nextCombo= new StateCombo(cTransition.getTo(), sc.s2, nextState);
						comboMap.put(nextStateName, nextCombo);
						queue2.add(nextCombo);
					} else {
						nextState= nextCombo.combined;
					}

					combined.addTransition(cTransition.getActionLabel(), cTransition.getTransitionType(), cTransition.getActionDirection(),
							sc.combined.getName(), nextState.getName());
				}

				List<CTransition> outgoings2= sc.s2.getOutgoing();
				for (CTransition cTransition : outgoings2) {
					// Move in s2, stay in s1
					String nextStateName= sc.s1.getName() + "<X>" + cTransition.getTo().getName();
					StateCombo nextCombo= comboMap.get(nextStateName);
					CState nextState= null;
					if (nextCombo == null) {
						nextState= combined.addState(nextStateName);
						nextCombo= new StateCombo(sc.s1, cTransition.getTo(), nextState);
						comboMap.put(nextStateName, nextCombo);
						queue2.add(nextCombo);
					} else {
						nextState= nextCombo.combined;
					}
					combined.addTransition(cTransition.getActionLabel(), cTransition.getTransitionType(), cTransition.getActionDirection(),
							sc.combined.getName(), nextState.getName());
				}
			}

			queue= queue2;
		}
		return combined;
	}

	static class StateCombo {

		public CState s1;

		public CState s2;

		public CState combined;

		public StateCombo(CState s1, CState s2, CState combined) {
			this.s1= s1;
			this.s2= s2;
			this.combined= combined;
		}
	}

	/**
	 * 
	 * Weaves an event handler into an existing automaton.
	 * 
	 * Weaving event handlers is different from normal interleaving:
	 * <ul>
	 * <li>An event handler is a loop which must be added before it is weaved.</li>
	 * <li>An event handler is optional, i.e. it can be skipped altogether.</li>
	 * </ul>
	 * 
	 * The second issue is of course the interesting one. We solve it by tagging
	 * the merged state of (end of normal automaton) X (start of event handler)
	 * as a non-error end. Thus, event handlers MAY be repeated there (after the
	 * final action of the scope) but are not NECESSARY for completion.
	 * 
	 * Unfortunately we get transition duplication in the non-error end state
	 * after the main automata when we have two event handlers (3x loop, 2x the
	 * (single) action of the first handler)...
	 * 
	 * Transitions after first run:
	 * <ul>
	 * <li>(end normal/end h1) ->loop ->(end normal/end h1)
	 * <li>(end normal/end h1) ->rcv1 ->(end normal/end h1)
	 * </ul>
	 * 
	 * Run #2 visits this state and adds the new state (end normal/end h1) (X)
	 * (end h2), with new transitions:
	 * <ul>
	 * <li>(end normal/end h1) (X) (end h2) -> loop (existing, again) -> (end
	 * normal/end h1) (X) (end h2)
	 * <li>(end normal/end h1) (X) (end h2) -> loop (the new one) -> (end
	 * normal/end h1) (X) (end h2)
	 * <li>(end normal/end h1) (X) (end h2) -> rcv1 (existing, again) -> (end
	 * normal/end h1) (X) (end h2)
	 * </ul>
	 * 
	 * Then, everything is merged to one end state and we have the mess we need
	 * to clean up.
	 * 
	 * @param left
	 * @param right
	 * @param handlerName
	 * @return
	 * @throws SemanticsException
	 */
	private CAutomaton handleEventHandler(CAutomaton left, CAutomaton right, String handlerName) throws SemanticsException {

		for (CState neEndState : right.getNonErrorEndStates()) {
			right.addTransition("loop", CTType.MUST, CDirection.INT, neEndState.getName(), right.getStart().getName());
		}

		CAutomaton l= interleave(left, right, true);
		l.mergeEnds("EndOfEventHandling");

		// Clean up duplicates in the end states
		List<CState> nonErrorEndStates= l.getNonErrorEndStates();
		for (CState cState : nonErrorEndStates) {
			List<CTransition> outgoing= cState.getOutgoing();
			for (CTransition cTransition : outgoing) {

				List<CTransition> transitions= l.getTransitions();
				boolean first= false;
				for (Iterator<CTransition> iterator= transitions.iterator(); iterator.hasNext();) {
					CTransition cur= (CTransition) iterator.next();
					if (cur.isSameAs(cTransition)) {
						if (!first)
							first= true;
						else
							iterator.remove();
					}
				}
			}
		}


		return l;
	}

	// **** END FORK

	public CAutomaton mio(Throw act) {

		String fName= act.getExceptionType().getName();

		i++;
		String begin= "start throw " + fName + "(" + i + ")";
		String end= "end throw " + fName + "(" + i + ")";

		CAutomaton throwAct= new CAutomaton();
		throwAct.addTransition("throw (" + fName + ")", CTType.MUST, CDirection.INT, begin, end);

		throwAct.setStart(begin);
		throwAct.addErrorEnd(end);

		return throwAct;
	}

	public CAutomaton mio(Send act) throws SemanticsException {

		if (act == null)
			throw new SemanticsException("Was about to handle a send; unfortunately it was null");

		if (act.getOperation() == null)
			throw new SemanticsException("Found a send without an operation: " + act);

		String name= act.getOperation().getName();
		i++;

		String begin= "start snd " + name + "(" + i + ")";
		String end= "end snd " + name + "(" + i + ")";

		CAutomaton send= new CAutomaton();
		send.addTransition(name, CTType.MUST, CDirection.OUT, begin, end);

		send.setStart(begin);
		send.addNonErrorEnd(end);

		return send;
	}

	public CAutomaton mio(Receive act) {

		String name= act.getOperation().getName();
		i++;

		String begin= "start rcv " + name + "(" + i + ")";
		String end= "end rcv " + name + "(" + i + ")";

		CAutomaton receive= new CAutomaton();
		receive.addTransition(name, CTType.MUST, CDirection.IN, begin, end);

		receive.setStart(begin);
		receive.addNonErrorEnd(end);

		return receive;
	}

	public CAutomaton mio(DataHandling act) {

		String name= act.getName();
		i++;

		String begin= "start data " + name + "(" + i + ")";
		String end= "end data " + name + "(" + i + ")";

		CAutomaton receive= new CAutomaton();
		receive.addTransition(name, CTType.MUST, CDirection.INT, begin, end);

		receive.setStart(begin);
		receive.addNonErrorEnd(end);

		return receive;
	}

	public CAutomaton mio(Terminate act) {

		String name= "terminate";
		i++;

		String begin= "start term " + name + "(" + i + ")";
		String end= "end term " + name + "(" + i + ")";

		CAutomaton receive= new CAutomaton();
		receive.addTransition(name, CTType.MUST, CDirection.INT, begin, end);

		receive.setStart(begin);
		receive.addErrorEnd(end);

		return receive;
	}

	public CAutomaton mio(SendAndReceive act) {

		String name= act.getOperation().getName();
		i++;

		String begin= "start snd/rcv " + name + "(" + i + ")";
		String end= "end snd/rcv " + name + "(" + i + ")";
		String middle= "middle snd/rcv" + name + "(" + i + ")";

		CAutomaton sndAndRcv= new CAutomaton();
		sndAndRcv.addTransition(name, CTType.MUST, CDirection.OUT, begin, middle);
		sndAndRcv.addTransition("return_" + name, CTType.MUST, CDirection.IN, middle, end);

		sndAndRcv.setStart(begin);
		sndAndRcv.addNonErrorEnd(end);

		return sndAndRcv;
	}

	public CAutomaton mio(Reply act) {

		String name= act.getOperation().getName();

		i++;
		String begin= "start reply " + name + "(" + i + ")";
		String end= "end reply " + name + "(" + i + ")";

		CAutomaton reply= new CAutomaton();
		reply.addTransition("return_" + name, CTType.MUST, CDirection.OUT, begin, end);

		reply.setStart(begin);
		reply.addNonErrorEnd(end);

		return reply;
	}

	public CAutomaton mio(Compensate act) {

		String scopeToCompensate= act.getTarget().getName();

		i++;
		String begin= "start compensate " + scopeToCompensate + "(" + i + ")";
		String end= "end compensate " + scopeToCompensate + "(" + i + ")";

		CAutomaton compensateCall= new CAutomaton();
		compensateCall.addTransition("compensate " + scopeToCompensate, CTType.MUST, CDirection.INT, begin, end);
		compensateCall.setStart(begin);
		compensateCall.addNonErrorEnd(end);
		return compensateCall;
	}

	public CAutomaton mio(CompensateAll act) {

		i++;
		String begin= "start compensateAll(" + i + ")";
		String end= "end compensateAll(" + i + ")";

		CAutomaton compensateAllCall= new CAutomaton();
		compensateAllCall.addTransition("compensateAll", CTType.MUST, CDirection.INT, begin, end);
		compensateAllCall.setStart(begin);
		compensateAllCall.addNonErrorEnd(end);
		return compensateAllCall;
	}


}
