/*
 * 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.mdd4soa.trans.smm2bpel.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.xml.namespace.QName;

import org.eclipse.bpel.model.Activity;
import org.eclipse.bpel.model.Assign;
import org.eclipse.bpel.model.BPELFactory;
import org.eclipse.bpel.model.Catch;
import org.eclipse.bpel.model.CompensateScope;
import org.eclipse.bpel.model.Condition;
import org.eclipse.bpel.model.Copy;
import org.eclipse.bpel.model.Correlation;
import org.eclipse.bpel.model.CorrelationSet;
import org.eclipse.bpel.model.CorrelationSets;
import org.eclipse.bpel.model.Correlations;
import org.eclipse.bpel.model.ElseIf;
import org.eclipse.bpel.model.Expression;
import org.eclipse.bpel.model.From;
import org.eclipse.bpel.model.If;
import org.eclipse.bpel.model.Import;
import org.eclipse.bpel.model.Invoke;
import org.eclipse.bpel.model.OnEvent;
import org.eclipse.bpel.model.PartnerLink;
import org.eclipse.bpel.model.PartnerLinks;
import org.eclipse.bpel.model.Process;
import org.eclipse.bpel.model.RepeatUntil;
import org.eclipse.bpel.model.Scope;
import org.eclipse.bpel.model.Sequence;
import org.eclipse.bpel.model.To;
import org.eclipse.bpel.model.Variables;
import org.eclipse.bpel.model.adapters.AdapterRegistry;
import org.eclipse.bpel.model.adapters.INamespaceMap;
import org.eclipse.bpel.model.partnerlinktype.PartnerLinkType;
import org.eclipse.bpel.model.partnerlinktype.Role;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.wst.wsdl.Message;
import org.eclipse.wst.wsdl.Part;

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.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.Throw;
import eu.mdd4soa.smm.data.Assignment;
import eu.mdd4soa.smm.data.Declaration;
import eu.mdd4soa.smm.data.InteractionParameter;
import eu.mdd4soa.smm.data.LeftHandSideExpression;
import eu.mdd4soa.smm.data.ReceiveParameter;
import eu.mdd4soa.smm.data.RightHandSideExpression;
import eu.mdd4soa.smm.data.SendParameter;
import eu.mdd4soa.smm.data.Statement;
import eu.mdd4soa.smm.data.Variable;
import eu.mdd4soa.smm.exception.TransformationException;
import eu.mdd4soa.smm.statik.MessageType;
import eu.mdd4soa.smm.statik.Participant;
import eu.mdd4soa.smm.statik.ProvidedService;
import eu.mdd4soa.smm.statik.Service;
import eu.mdd4soa.trans.smm2bpel.impl.util.ExpressionHelper;
import eu.mdd4soa.trans.smm2bpel.impl.util.GPTracker;
import eu.mdd4soa.trans.smm2bpel.impl.util.WSDLMessageType;

/**
 * 
 * The transformation is more-or-less straightforward. Note that all
 * communication activities in BPEL work with messages and message types.
 * Therefore, one message-typed-variable is required for each send and for each
 * receive, to which or from which the actual variable contents must be copied.
 * 
 * Also, the assignments are easier to convert than in Java because the
 * addressing schema of XPath is the same for left- and righthand side ($s for
 * variables, /s for fields).
 * 
 * TODO now: test a process with custom data types and invocation of other
 * (custom) services. Create those with WTP after having changed the magicdraw
 * model (or created a new one) with a partner, and using the partners WSDL
 * file.
 * 
 * TODO another problem is the URLs for services in the wsdls. The URL for the
 * provided services should be the same, but they are dependent on the current
 * behaviour converted, so perhaps create a subdir for each behaviour,
 * duplicating the files as appropriate? (and just generate "DUMMY" at first)
 * 
 * TODO info: there are no fault types in BPEL, so we just use a fault name (or
 * is there?)
 * 
 * TODO how are names of faults specified in UML4SOA? or is there none? (there
 * should be a pin on the raiseexceptionaction. probably not implemented yet)
 * 
 * TODO info: interrupting receives are handled with a throw and an empty catch.
 * 
 * TODO info: Write about the different artefacts playing together in the intro.
 * bpel, wsdl, xsd, soap, xpath, ws-addressing, and extensions for each.
 * 
 * TODO info: ODE 1.3.2 geht nicht wegen
 * "INTERNAL ERROR: No ENTRY for RESPONSE CHANNEL"; ODE 1.3.3 geht nicht wegen
 * "Two services cannot have same name." (meaning the namespace) ...auch in 2.0
 * beta2 gibts das INTERNAL ERROR problem... 2.0 beta hat außerdem noch andere
 * Probleme: Routet Nachrichten zu bereits terminierten Instanzen, d.h. die
 * Nachrichten kommen nicht immer da an wo man es erwartet...
 * 
 * TODO ODE kann insgesamt kein WS-Adressing reply-To. TODO Oracle BPEL-Server
 * kann reply-To, aber nur BPEL 1.1 (außer vermutlich in der 11g-Version) TODO
 * ActiveBPEL steht immer noch bei BPEL 1.1, außer in ominösen Kaufversionen und
 * Community Editions, die nirgendwo zu finden sind. * WSO2-BPS baut auf ODE
 * auf, nichts neues * bpel-g unterstützt nur document/literal, keine
 * multi-message-parts...
 * 
 * @author Philip Mayer
 * 
 */
public class BehaviourConverter {

	public static Process convert(GPTracker tracker, Participant participant, ServiceActivity behaviour) throws TransformationException {
		BehaviourConverter spc= new BehaviourConverter(tracker, participant, behaviour);
		spc.execute();
		return spc.getResult();
	}

	private static final BPELFactory bf= BPELFactory.eINSTANCE;

	private Participant fParticipant;

	private Process fProcess;

	private GPTracker fTracker;

	private ServiceActivity fBehaviour;

	private boolean fCreateInstance;

	private Map<Service, PartnerLink> fPartnerMap;

	// TODO this is probably not enough due to hiding etc.
	private Map<Variable, org.eclipse.bpel.model.Variable> fVariableMap;

	private ExpressionHelper fExpressionHelper;

	private Map<ServiceActivity, Scope> fCompensationHandlerMap;

	private Map<String, CorrelationSet> fCorrelationSets;

	private List<String> fInitialisedCorrelationSets;

	public BehaviourConverter(Participant participant, Service partnerPoint) {
		fParticipant= participant;
	}

	public BehaviourConverter(GPTracker tracker, Participant participant, ServiceActivity behaviour) {
		fParticipant= participant;
		fTracker= tracker;
		fBehaviour= behaviour;
		fVariableMap= new HashMap<Variable, org.eclipse.bpel.model.Variable>();
		fExpressionHelper= new ExpressionHelper(fVariableMap);
		fCompensationHandlerMap= new HashMap<ServiceActivity, Scope>();
		fInitialisedCorrelationSets= new ArrayList<String>();
	}

	private Process getResult() {
		return fProcess;
	}

	private void execute() throws TransformationException {

		// Set first create instance
		fCreateInstance= true;

		fPartnerMap= new HashMap<Service, PartnerLink>();

		fProcess= bf.createProcess();
		fProcess.setName(fBehaviour.getName());

		fProcess.setTargetNamespace(fTracker.getProcessNamespace(fBehaviour));


		// Import all wsdls + create partnerlinks

		PartnerLinks links= bf.createPartnerLinks();
		fProcess.setPartnerLinks(links);

		EList<Service> partners= fParticipant.getPartners();
		for (Service partnerPoint : partners) {
			Import wsdlImport= bf.createImport();
			wsdlImport.setNamespace(fTracker.getPartnerNamespace(partnerPoint.getName()));
			wsdlImport.setImportType("http://schemas.xmlsoap.org/wsdl/");
			wsdlImport.setLocation(partnerPoint.getName() + ".wsdl");
			fProcess.getImports().add(wsdlImport);

			PartnerLinkType plt= fTracker.getServicePLT(partnerPoint);

			PartnerLink link= bf.createPartnerLink();
			link.setName(partnerPoint.getName());
			link.setPartnerLinkType(plt);

			fPartnerMap.put(partnerPoint, link);

			Role myRole;
			Role partnerRole;
			if (partnerPoint instanceof ProvidedService) {
				// role of participant is provider, i.e. myRole is set
				// (and maybe callback partnerRole, if exists)
				myRole= getRole(plt, true/* nocallback */);
				partnerRole= getRole(plt, false/* callback */);

			} else {
				// role of participant is requester, i.e. partnerRole is set
				// (and maybe callback myRole, if exists)
				partnerRole= getRole(plt, true/* nocallback */);
				myRole= getRole(plt, false/* callback */);
			}

			if (myRole != null)
				link.setMyRole(myRole);
			if (partnerRole != null)
				link.setPartnerRole(partnerRole);

			fProcess.getPartnerLinks().getChildren().add(link);
		}

		// Schema import...
		setPrefix(fProcess, fTracker.getTypeNamespace(), "types");

		Import importSchema= bf.createImport();
		importSchema.setImportType("http://www.w3.org/2001/XMLSchema");
		importSchema.setNamespace(fTracker.getTypeNamespace());
		importSchema.setLocation(fParticipant.getName() + ".xsd");
		fProcess.getImports().add(importSchema);

		// Correlation sets
		setPrefix(fProcess, fTracker.getTypeNamespace() + "correlation/", "corr");

		CorrelationSets sets= bf.createCorrelationSets();
		fCorrelationSets= new HashMap<String, CorrelationSet>();
		fProcess.setCorrelationSets(sets);
		Set<String> correlationSets= fTracker.getCorrelationSets();
		for (String propertyName : correlationSets) {
			CorrelationSet set= bf.createCorrelationSet();
			set.setName(propertyName);
			set.getProperties().add(fTracker.getCorrelationProperty(propertyName));
			sets.getChildren().add(set);
			fCorrelationSets.put(propertyName, set);
		}

		// actual process.
		Scope rootScope= convertScope(fBehaviour);
		fProcess.setActivity(rootScope);
	}

	public static void setPrefix(EObject eObject, String namespaceURI, String prefix) {

		INamespaceMap<String, String> prefixNSMap= getNamespaceMap(eObject);
		if (prefixNSMap.containsKey(prefix)) {
			throw new RuntimeException("Prefix is already mapped!");
		}
		prefixNSMap.put(prefix, namespaceURI);
	}

	/**
	 * @param eObject
	 * @return the namespace map for the given object.
	 */

	@SuppressWarnings("unchecked")
	static public INamespaceMap<String, String> getNamespaceMap(EObject eObject) {

		if (eObject == null) {
			throw new NullPointerException("eObject cannot be null in getNamespaceMap()");
		}

		INamespaceMap<String, String> nsMap= AdapterRegistry.INSTANCE.adapt(eObject, INamespaceMap.class);
		if (nsMap == null) {
			throw new IllegalStateException("INamespaceMap cannot be attached to an eObject");
		}

		return nsMap;
	}

	private Scope convertScope(ServiceActivity serviceActivity) throws TransformationException {

		Scope scope= bf.createScope();
		scope.setName(serviceActivity.getName());
		mapScopeVariables(scope, serviceActivity.getVariables());
		scope.setActivity(convertSMMElements(scope, serviceActivity.getChildren()));

		// Handlers.
		EList<Handler> handlers= serviceActivity.getHandlers();
		for (Handler handler : handlers) {

			if (handler instanceof CompensationHandler) {
				scope.setCompensationHandler(convertCompensationHandler((CompensationHandler) handler));
				fCompensationHandlerMap.put(handler.getServiceActivity(), scope);
			}

			if (handler instanceof EventHandler) {
				ensureEventHandlers(scope);
				scope.getEventHandlers().getEvents().add(convertEventHandler((EventHandler) handler));
			}

			if (handler instanceof ExceptionHandler) {
				ensureFaultHandlers(scope);
				scope.getFaultHandlers().getCatch()
						.add(convertExceptionHandler( ((ExceptionHandler) handler).getExceptionType().getName(), handler.getChildren()));
			}
		}

		// Interrupting receives.
		EList<Receive> interruptingReceives= serviceActivity.getInterruptingReceives();
		for (Receive receive : interruptingReceives) {

			// Create the event
			ensureEventHandlers(scope);
			OnEvent event= createOnEvent("Interrupting_" + receive.getName(), receive, new ArrayList<ServiceElement>());
			org.eclipse.bpel.model.Throw interruptThrow= bf.createThrow();
			interruptThrow.setFaultName(new QName(fTracker.getTypeNamespace(), receive.getName()));
			((Sequence) ((Scope) event.getActivity()).getActivity()).getActivities().add(interruptThrow);
			scope.getEventHandlers().getEvents().add(event);

			event.setCorrelations(getCorrelationSet(event.getMessageType()));

			// Create the catch
			ensureFaultHandlers(scope);
			Catch interruptHandler= convertExceptionHandler(receive.getName(), new ArrayList<ServiceElement>());
			scope.getFaultHandlers().getCatch().add(interruptHandler);
			((Sequence) ((Scope) interruptHandler.getActivity()).getActivity()).getActivities().add(bf.createEmpty());
		}

		return scope;
	}

	private void ensureFaultHandlers(Scope scope) {
		if (scope.getFaultHandlers() == null)
			scope.setFaultHandlers(bf.createFaultHandler());
	}

	private void ensureEventHandlers(Scope scope) {
		if (scope.getEventHandlers() == null)
			scope.setEventHandlers(bf.createEventHandler());
	}

	private Sequence convertSMMElements(Scope parentScope, List<ServiceElement> elements) throws TransformationException {

		Sequence rootSequence= bf.createSequence();

		for (ServiceElement serviceElement : elements) {

			if (serviceElement instanceof ServiceInteraction)
				rootSequence.getActivities().addAll(convertServiceInteraction(parentScope, ((ServiceInteraction) serviceElement)));

			if (serviceElement instanceof DataHandling)
				rootSequence.getActivities().addAll(convertDataHandling(parentScope, ((DataHandling) serviceElement)));

			if (serviceElement instanceof Compensate)
				rootSequence.getActivities().add(convertCompensate(parentScope, ((Compensate) serviceElement)));

			if (serviceElement instanceof CompensateAll)
				rootSequence.getActivities().add(convertCompensateAll(parentScope, ((CompensateAll) serviceElement)));

			if (serviceElement instanceof Throw)
				rootSequence.getActivities().add(convertThrow(parentScope, ((Throw) serviceElement)));

			if (serviceElement instanceof ServiceActivity)
				rootSequence.getActivities().add(convertScope((ServiceActivity) serviceElement));

			if (serviceElement instanceof Decision)
				rootSequence.getActivities().add(convertDecision(parentScope, (Decision) serviceElement));

			if (serviceElement instanceof Loop)
				rootSequence.getActivities().add(convertLoop(parentScope, (Loop) serviceElement));

			if (serviceElement instanceof Parallel)
				rootSequence.getActivities().add(convertParallel(parentScope, (Parallel) serviceElement));

		}
		return rootSequence;
	}

	/**
	 * Converts a service interaction. We have to use messagetype-typed
	 * variables for send and receive. Thus, there is a copy operation after
	 * each receive and before each send.
	 * 
	 * 
	 * @param serviceInteraction
	 * @return
	 * @throws TransformationException
	 */
	private List<Activity> convertServiceInteraction(Scope scope, ServiceInteraction serviceInteraction) throws TransformationException {

		List<Activity> acts= new ArrayList<Activity>();

		if (serviceInteraction instanceof Receive && ! (serviceInteraction instanceof SendAndReceive)) {

			org.eclipse.bpel.model.Receive bpelReceive= bf.createReceive();
			setPartnerAndOperation(bpelReceive, serviceInteraction);

			org.eclipse.bpel.model.Variable bpelVariable= createMessageTypedVariable(scope, WSDLMessageType.IN, "_receive", serviceInteraction);
			bpelReceive.setVariable(bpelVariable);

			bpelReceive.setCreateInstance(fCreateInstance);
			fCreateInstance= false;

			bpelReceive.setCorrelations(getCorrelationSet(bpelVariable.getMessageType()));

			Assign ass= bf.createAssign();
			for (InteractionParameter param : serviceInteraction.getRcvParameters()) {
				Copy copy= assignMessageVarPartsToVariables(bpelVariable, (ReceiveParameter) param);
				ass.getCopy().add(copy);
			}

			acts.add(bpelReceive);
			acts.add(ass);

		} else if (serviceInteraction instanceof Reply) {

			org.eclipse.bpel.model.Reply bpelReply= bf.createReply();
			setPartnerAndOperation(bpelReply, serviceInteraction);

			org.eclipse.bpel.model.Variable bpelVariable= createMessageTypedVariable(scope, WSDLMessageType.OUT, "_reply", serviceInteraction);
			bpelReply.setVariable(bpelVariable);

			// TODO No correlation on reply?
			// bpelReply.setCorrelations(getCorrelationSet(bpelVariable.getMessageType()));

			Assign ass= bf.createAssign();
			for (InteractionParameter param : serviceInteraction.getSndParameters()) {
				Copy copy= assignVariablesToMessageParts(bpelVariable, (SendParameter) param);
				ass.getCopy().add(copy);
			}

			acts.add(ass);
			acts.add(bpelReply);

		} else if (serviceInteraction instanceof Send || serviceInteraction instanceof SendAndReceive) {

			Invoke invoke= bf.createInvoke();
			setPartnerAndOperation(invoke, serviceInteraction);

			org.eclipse.bpel.model.Variable bpelSendVariable= createMessageTypedVariable(scope, WSDLMessageType.IN, "_send", serviceInteraction);
			invoke.setInputVariable(bpelSendVariable);

			invoke.setCorrelations(getCorrelationSet(bpelSendVariable.getMessageType()));

			Assign assBefore= bf.createAssign();
			for (InteractionParameter param : serviceInteraction.getSndParameters()) {
				Copy copy= assignVariablesToMessageParts(bpelSendVariable, (SendParameter) param);
				assBefore.getCopy().add(copy);
			}

			Assign assAfter= null;
			if (serviceInteraction instanceof SendAndReceive) {

				org.eclipse.bpel.model.Variable bpelReceiveVariable= createMessageTypedVariable(scope, WSDLMessageType.OUT, "_receive",
						serviceInteraction);
				invoke.setOutputVariable(bpelReceiveVariable);

				// TODO no correlation on the receive?
				// Correlations correlationSet=
				// getCorrelationSet(bpelReceiveVariable.getMessageType());
				// invoke.getCorrelations().getChildren().add(correlationSet.getChildren().get(0));

				assAfter= bf.createAssign();
				for (InteractionParameter param : serviceInteraction.getRcvParameters()) {
					Copy copy= assignMessageVarPartsToVariables(bpelReceiveVariable, (ReceiveParameter) param);
					assAfter.getCopy().add(copy);
				}
			}

			acts.add(assBefore);
			acts.add(invoke);
			if (assAfter != null)
				acts.add(assAfter);

		} else
			throw new TransformationException("Unknown element:" + serviceInteraction);

		return acts;
	}

	private Correlations getCorrelationSet(Message msgType) {
		String set= fTracker.getCorrelationSet(msgType);
		Correlations corrs= bf.createCorrelations();
		Correlation cor= bf.createCorrelation();
		cor.setSet(fCorrelationSets.get(set));
		corrs.getChildren().add(cor);

		System.out.println("set is " + set);

		if (!fInitialisedCorrelationSets.contains(set)) {
			fInitialisedCorrelationSets.add(set);
			cor.setInitiate("yes");
		} else
			cor.setInitiate("no");

		return corrs;
	}


	private Collection<? extends Activity> convertDataHandling(Scope scope, DataHandling dataHandling) throws TransformationException {

		List<Activity> activities= new ArrayList<Activity>();

		EList<Statement> statements= dataHandling.getStatements();
		for (Statement statement : statements) {
			if (statement instanceof Declaration) {
				// The variable has already been added to the scope as it is a
				// normal variable of the service activity, too.

				Variable declaredVar= ((Declaration) statement).getDeclaredVar().getVariable();

				if (declaredVar.getType() instanceof MessageType) {

					String literal= fExpressionHelper.createLiteralInitialisation((MessageType) declaredVar.getType());
					Assign ass= bf.createAssign();
					Copy copy= bf.createCopy();

					// Source
					From from= bf.createFrom();
					from.setLiteral(literal);
					copy.setFrom(from);

					To to= bf.createTo();
					Expression expr= bf.createExpression();
					expr.setBody(fExpressionHelper.convertSMMExpression( ((Declaration) statement).getDeclaredVar()));
					to.setExpression(expr);
					copy.setTo(to);

					ass.getCopy().add(copy);
					activities.add(ass);
				}
			}

			if (statement instanceof Assignment) {
				Assignment assignment= (Assignment) statement;
				RightHandSideExpression source= assignment.getSource();
				LeftHandSideExpression target= assignment.getTarget();

				String targetExpression= fExpressionHelper.convertSMMExpression(target);
				To to= bf.createTo();
				Expression tExpr= bf.createExpression();
				tExpr.setBody(targetExpression);
				to.setExpression(tExpr);

				String sourceExpression= fExpressionHelper.convertSMMExpression(source);
				From from= bf.createFrom();
				Expression sExpr= bf.createExpression();
				sExpr.setBody(sourceExpression);
				from.setExpression(sExpr);

				Copy copy= bf.createCopy();
				copy.setFrom(from);
				copy.setTo(to);

				Assign ass= bf.createAssign();
				ass.getCopy().add(copy);
				activities.add(ass);

			}
		}

		return activities;
	}

	private Activity convertCompensate(Scope parentScope, Compensate compensate) {
		CompensateScope compScope= bf.createCompensateScope();
		compScope.setTarget(fCompensationHandlerMap.get(compensate.getTarget()));

		return compScope;
	}

	private Activity convertCompensateAll(Scope parentScope, CompensateAll compensate) {
		org.eclipse.bpel.model.Compensate bpelCompensate= bf.createCompensate();
		return bpelCompensate;
	}

	private Activity convertThrow(Scope parentScope, eu.mdd4soa.smm.behaviour.Throw serviceElement) {

		org.eclipse.bpel.model.Throw bpelThrow= bf.createThrow();
		bpelThrow.setFaultName(new QName(fTracker.getTypeNamespace(), serviceElement.getExceptionType().getName()));

		return bpelThrow;
	}

	private Activity convertDecision(Scope scope, Decision serviceElement) throws TransformationException {

		If bpelIf= null;

		EList<ServiceElement> children= serviceElement.getChildren();
		for (ServiceElement serviceElement2 : children) {
			Path path= (Path) serviceElement2;
			String condition= fExpressionHelper.convertSMMCondition(path.getEnterCondition());

			Condition bpelCondition= bf.createCondition();
			bpelCondition.setBody(condition);

			if (bpelIf == null) {
				bpelIf= bf.createIf();
				bpelIf.setCondition(bpelCondition);
				bpelIf.setActivity(convertSMMElements(scope, path.getChildren()));
			} else {
				ElseIf newElseIf= bf.createElseIf();
				newElseIf.setCondition(bpelCondition);
				newElseIf.setActivity(convertSMMElements(scope, path.getChildren()));
				bpelIf.getElseIf().add(newElseIf);
			}
		}

		return bpelIf;
	}

	private Activity convertLoop(Scope scope, Loop loop) throws TransformationException {

		EList<ServiceElement> children= loop.getChildren();
		if (children.size() != 1)
			throw new TransformationException("Loop with more or less than one 1 path found.");

		Path theLoop= (Path) children.get(0);

		RepeatUntil until= bf.createRepeatUntil();
		until.setActivity(convertSMMElements(scope, theLoop.getChildren()));

		eu.mdd4soa.smm.data.Expression leaveCondition= loop.getLeaveCondition();
		Condition bpelLeaveCondition= bf.createCondition();
		if (leaveCondition == null) {
			bpelLeaveCondition.setBody("false()");
		} else {
			bpelLeaveCondition.setBody("not(" + fExpressionHelper.convertSMMCondition(leaveCondition) + ")");
		}
		until.setCondition(bpelLeaveCondition);

		return until;
	}

	private Activity convertParallel(Scope parentScope, Parallel serviceElement) {

		// TODO parallel

		return null;
	}

	// ##########################################################################################################
	// Handlers
	// ##########################################################################################################


	private Catch convertExceptionHandler(String exceptionName, List<ServiceElement> elementsInCatch) throws TransformationException {

		Catch bpelCatch= bf.createCatch();

		bpelCatch.setFaultName(new QName(fTracker.getTypeNamespace(), exceptionName));

		Scope exHandlerScope= bf.createScope();
		exHandlerScope.setVariables(bf.createVariables());
		if (!elementsInCatch.isEmpty())
			exHandlerScope.setActivity(convertSMMElements(exHandlerScope, elementsInCatch));
		else
			exHandlerScope.setActivity(bf.createSequence());

		bpelCatch.setActivity(exHandlerScope);

		return bpelCatch;
	}

	private OnEvent createOnEvent(String name, Receive initial, List<ServiceElement> rest) throws TransformationException {

		OnEvent onev= bf.createOnEvent();

		// Set the onevent:
		Scope handlerScope= bf.createScope();
		handlerScope.setVariables(bf.createVariables());
		handlerScope.setName(name);

		List<Activity> convertedElements= convertServiceInteraction(handlerScope, initial);
		org.eclipse.bpel.model.Receive bpelReceive= (org.eclipse.bpel.model.Receive) convertedElements.get(0);
		org.eclipse.bpel.model.Assign bpelAssign= (org.eclipse.bpel.model.Assign) convertedElements.get(1);
		onev.setPartnerLink(bpelReceive.getPartnerLink());
		onev.setOperation(bpelReceive.getOperation());
		onev.setVariable(bpelReceive.getVariable());

		onev.setCorrelations(getCorrelationSet(onev.getMessageType()));

		// Get sequence, but add the first assign.
		Sequence seq= bf.createSequence();
		seq.getActivities().add(bpelAssign);
		if (!rest.isEmpty())
			seq= convertSMMElements(handlerScope, rest);

		handlerScope.setActivity(seq);
		onev.setActivity(handlerScope);

		return onev;
	}

	private OnEvent convertEventHandler(EventHandler handler) throws TransformationException {

		EList<ServiceElement> children= handler.getChildren();
		if (children.size() == 0)
			throw new TransformationException("Event Handler without children:" + handler);

		ServiceElement first= children.get(0);
		if (! (first instanceof Receive))
			throw new TransformationException("First action in event handler must be receive: " + handler);

		return createOnEvent(handler.getName(), (Receive) first, children.subList(1, children.size()));


	}

	private org.eclipse.bpel.model.CompensationHandler convertCompensationHandler(CompensationHandler handler) throws TransformationException {

		org.eclipse.bpel.model.CompensationHandler bpelCHandler= bf.createCompensationHandler();

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

		Scope handlerScope= bf.createScope();
		handlerScope.setVariables(bf.createVariables());
		handlerScope.setName(handler.getName());

		handlerScope.setActivity(convertSMMElements(handlerScope, children));
		bpelCHandler.setActivity(handlerScope);
		return bpelCHandler;
	}


	// ##########################################################################################################
	// Helpers
	// ##########################################################################################################

	private org.eclipse.bpel.model.Variable createMessageTypedVariable(Scope scope, WSDLMessageType msgType, String suffix,
			ServiceInteraction serviceInteraction) throws TransformationException {
		org.eclipse.bpel.model.Variable bpelVariable= bf.createVariable();

		// TODO Unclear if these need inititialisation -- as a message type
		// ALWAYS has its parts assigned, probably not.

		bpelVariable.setName(serviceInteraction.getOperation().getName() + suffix);
		Message message= fTracker.getMessage(serviceInteraction.getOperation(), msgType);
		if (message == null)
			throw new TransformationException("Message is null while handling service interaction " + serviceInteraction.getName() + " op "
					+ serviceInteraction.getOperation().getName());
		bpelVariable.setMessageType(message);
		scope.getVariables().getChildren().add(bpelVariable);
		return bpelVariable;
	}

	private void setPartnerAndOperation(org.eclipse.bpel.model.PartnerActivity bpelCommActivity, ServiceInteraction ismServiceInteraction) {
		bpelCommActivity.setPartnerLink(fPartnerMap.get(ismServiceInteraction.getPartner()));
		bpelCommActivity.setOperation(fTracker.getOperation(ismServiceInteraction.getOperation()));
	}

	private Copy assignVariablesToMessageParts(org.eclipse.bpel.model.Variable messageTypeVariable, SendParameter sndParam)
			throws TransformationException {

		Copy copy= bf.createCopy();

		From from= bf.createFrom();
		from.setExpression(encapsulateExpression(fExpressionHelper.convertSMMExpression(sndParam.getSource())));
		copy.setFrom(from);

		To to= bf.createTo();
		to.setPart((Part) messageTypeVariable.getMessageType().getPart(sndParam.getOperationParameter().getName()));
		to.setVariable(messageTypeVariable);
		copy.setTo(to);

		return copy;
	}

	private Copy assignMessageVarPartsToVariables(org.eclipse.bpel.model.Variable messageTypeVariable, ReceiveParameter rcvParam)
			throws TransformationException {

		Copy copy= bf.createCopy();
		From from= bf.createFrom();
		from.setPart((Part) messageTypeVariable.getMessageType().getPart(rcvParam.getOperationParameter().getName()));
		from.setVariable(messageTypeVariable);
		copy.setFrom(from);

		To to= bf.createTo();
		to.setExpression(encapsulateExpression(fExpressionHelper.convertSMMExpression(rcvParam.getTarget())));

		copy.setTo(to);
		return copy;
	}

	private Expression encapsulateExpression(String ex) {
		Expression expression= bf.createExpression();
		expression.setBody(ex);
		return expression;
	}

	private void mapScopeVariables(Scope scope, EList<Variable> iomVariables) throws TransformationException {

		Variables vars= bf.createVariables();
		scope.setVariables(vars);

		for (Variable variable : iomVariables) {
			org.eclipse.bpel.model.Variable bpelVar= createBPELVariableForSMMVariable(variable);
			vars.getChildren().add(bpelVar);
			fVariableMap.put(variable, bpelVar);
		}
	}

	private org.eclipse.bpel.model.Variable createBPELVariableForSMMVariable(Variable variable) throws TransformationException {

		// TODO initialisation of inner structure if it HAS inner structure?
		// Also: min max ordered?

		org.eclipse.bpel.model.Variable bpelVar= bf.createVariable();
		bpelVar.setName(variable.getName());
		bpelVar.setType(fTracker.getMatchingXSDType(variable.getType()));
		return bpelVar;
	}

	private Role getRole(PartnerLinkType plt, boolean b) {

		EList<Role> role= plt.getRole();
		for (Role role2 : role) {
			if (!b && role2.getName().endsWith("_callback"))
				return role2;
			if (b && !role2.getName().endsWith("_callback"))
				return role2;
		}

		return null;
	}


}
