/*
 * 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.uml2smm.transform;

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

import org.eclipse.emf.common.util.ECollections;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.uml2.uml.AcceptCallAction;
import org.eclipse.uml2.uml.Action;
import org.eclipse.uml2.uml.Activity;
import org.eclipse.uml2.uml.ActivityEdge;
import org.eclipse.uml2.uml.ActivityNode;
import org.eclipse.uml2.uml.Behavior;
import org.eclipse.uml2.uml.CallBehaviorAction;
import org.eclipse.uml2.uml.CallEvent;
import org.eclipse.uml2.uml.CallOperationAction;
import org.eclipse.uml2.uml.Event;
import org.eclipse.uml2.uml.ExceptionHandler;
import org.eclipse.uml2.uml.ExecutableNode;
import org.eclipse.uml2.uml.FinalNode;
import org.eclipse.uml2.uml.InitialNode;
import org.eclipse.uml2.uml.InputPin;
import org.eclipse.uml2.uml.NamedElement;
import org.eclipse.uml2.uml.ObjectNode;
import org.eclipse.uml2.uml.OpaqueAction;
import org.eclipse.uml2.uml.Operation;
import org.eclipse.uml2.uml.OutputPin;
import org.eclipse.uml2.uml.Pin;
import org.eclipse.uml2.uml.RaiseExceptionAction;
import org.eclipse.uml2.uml.ReplyAction;
import org.eclipse.uml2.uml.StructuredActivityNode;
import org.eclipse.uml2.uml.Trigger;
import org.eclipse.uml2.uml.Type;
import org.eclipse.uml2.uml.ValueSpecification;

import eu.mdd4soa.smm.behaviour.Compensate;
import eu.mdd4soa.smm.behaviour.CompositeElement;
import eu.mdd4soa.smm.behaviour.DataHandling;
import eu.mdd4soa.smm.behaviour.Decision;
import eu.mdd4soa.smm.behaviour.Handler;
import eu.mdd4soa.smm.behaviour.ISMBehaviourFactory;
import eu.mdd4soa.smm.behaviour.Loop;
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.ServiceActivity;
import eu.mdd4soa.smm.behaviour.ServiceInteraction;
import eu.mdd4soa.smm.behaviour.SimpleElement;
import eu.mdd4soa.smm.behaviour.Throw;
import eu.mdd4soa.smm.data.ISMDataFactory;
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.exception.TransformationException;
import eu.mdd4soa.smm.statik.ExceptionType;
import eu.mdd4soa.smm.statik.InParameter;
import eu.mdd4soa.smm.statik.InterfaceOperation;
import eu.mdd4soa.smm.statik.InterfaceParameter;
import eu.mdd4soa.smm.statik.InterfaceType;
import eu.mdd4soa.smm.statik.OutParameter;
import eu.mdd4soa.smm.statik.Participant;
import eu.mdd4soa.smm.statik.ProvidedService;
import eu.mdd4soa.smm.statik.SMMType;
import eu.mdd4soa.smm.statik.Service;
import eu.mdd4soa.trans.uml2smm.SMMUtil;
import eu.mdd4soa.trans.uml2smm.StereotypeUtil;
import eu.mdd4soa.trans.uml2smm.StereotypeUtil.StereoType;
import eu.mdd4soa.trans.uml2smm.UMLNodeUtil;
import eu.mdd4soa.trans.uml2smm.data.ParserAccess;
import eu.mdd4soa.trans.uml2smm.transform.PathFinder.FinderResult;
import eu.mdd4soa.trans.uml2smm.transform.PathFinder.SearchType;

/**
 * Converts an activity to an SMM representation.
 * 
 * TODO types of right-hand sides are currently not being set (i.e. calculated).
 * 
 * TODO collection handling is untested.
 * 
 * @author Philip Mayer, mayer@pst.ifi.lmu.de
 * 
 */
public class BehaviourConverter {

	private Activity fActivity;

	private ServiceActivity fRootServiceActivity;

	private Participant fParticipant;

	private Map<AcceptCallAction, Receive> fReceiveMap;

	enum PartitionType {
		ServiceActivity, MultipleActions, SingleAction, Loop, Branch, Parallel
	}

	enum TransactionType {
		RECEIVE, SEND, SENDANDRECEIVE, REPLY
	}

	enum HandlerType {
		EVENT, COMPENSATION, EXCEPTION
	}

	class Partition {

		// These fields are set in the beginning:

		private CompositeElement fContext;

		private PartitionType fType;

		private List<ActivityNode> fElements;

		private ActivityNode fIdentifiedStartNode;

		// These fields are identified during division phase:

		private ActivityNode fIdentifiedEndNode;

		private List<Partition> fSubPartitions;

		private String fName;

		public Partition(PartitionType type, String name, CompositeElement context, ActivityNode startNode, List<ActivityNode> list) {
			this(type, name, context, startNode, list.toArray(new ActivityNode[0]));
		}

		public Partition(PartitionType type, String name, CompositeElement context, ActivityNode startNode, ActivityNode... eContents) {
			fType= type;
			fName= name;
			fContext= context;
			fIdentifiedStartNode= startNode;
			fElements= new ArrayList<ActivityNode>();
			for (ActivityNode eObject : eContents) {
				fElements.add(eObject);
			}
			fSubPartitions= new ArrayList<Partition>();
		}

		public PartitionType getType() {
			return fType;
		}

		public List<ActivityNode> getElements() {
			return fElements;
		}

		public boolean containsNode(ActivityNode currentNode) {
			return fElements.contains(currentNode);
		}

		public CompositeElement getCurrentContext() {
			return fContext;
		}

		public ServiceActivity getCurrentServiceActivity() {
			return getNearestServiceActivity(fContext);
		}

		private ServiceActivity getNearestServiceActivity(CompositeElement context) {

			if (context == null)
				return null;

			if (context instanceof ServiceActivity)
				return (ServiceActivity) context;

			if (context instanceof Handler)
				return ((Handler) context).getServiceActivity();

			return getNearestServiceActivity(context.getParent());
		}

		public void addSubPartition(Partition partition) {
			fSubPartitions.add(partition);
		}

		public List<Partition> getSubPartitions() {
			return fSubPartitions;
		}

		public ActivityNode getIdentifiedEndNode() {
			return fIdentifiedEndNode;
		}

		public void setIdentifiedEndNode(ActivityNode identifiedEndNode) {
			fIdentifiedEndNode= identifiedEndNode;
		}

		public ActivityNode getIdentifiedStartNode() {
			return fIdentifiedStartNode;
		}

		public String getName() {
			return fName;
		}

	}

	public static ServiceActivity convert(Participant ismParticipant, Activity activity) throws TransformationException {

		BehaviourConverter c= new BehaviourConverter(ismParticipant, activity);
		c.convert();
		return c.getResult();
	}

	public BehaviourConverter(Participant ismParticipant, Activity activity) {
		fParticipant= ismParticipant;
		fActivity= activity;
		fReceiveMap= new HashMap<AcceptCallAction, Receive>();
	}

	private ServiceActivity getResult() {
		return fRootServiceActivity;
	}


	// ******************************************************
	// MAIN
	// ******************************************************


	private void convert() throws TransformationException {

		fRootServiceActivity= ISMBehaviourFactory.eINSTANCE.createServiceActivity();
		fRootServiceActivity.setName(fActivity.getName());

		// Add to participant (enables reverse lookup)
		fParticipant.getBehaviours().add(fRootServiceActivity);

		ActivityNode firstNode= UMLNodeUtil.getFirstNode(fActivity.getNodes());

		Partition initial= new Partition(PartitionType.MultipleActions, fActivity.getName(), fRootServiceActivity, firstNode, fActivity.getNodes());
		divideAndConquer(initial);

		createOrderInChaos();
	}

	private void createOrderInChaos() {
		// Paths are notoriously unordered. This is a problem for testing.
		for (TreeIterator<Object> contents= EcoreUtil.getAllContents(fParticipant, false); contents.hasNext();) {
			Object next= contents.next();
			if (next instanceof PathBasedPartition) {
				PathBasedPartition part= (PathBasedPartition) next;
				ECollections.sort(part.getChildren());
			}
		}
	}

	/**
	 * An idea would be to first divide on one level, identifying an ordered
	 * list of partitions (single action; loop; ...), and then starting to
	 * handle them one by one, going deeper one by one with additional divides
	 * and conquers.
	 * 
	 * During conquer, SMM classes can directly be used. What about divide?
	 * Already create and store them? But needs metadata... Maybe just one class
	 * with "children" and a "type" (enum) ?
	 * 
	 * @param eContents
	 * @throws TransformationException
	 */
	private void divideAndConquer(Partition p) throws TransformationException {
		divide(p);
		conquer(p);
	}

	// ******************************************************
	// DIVIDE
	// ******************************************************


	private void divide(Partition p) throws TransformationException {

		// Separates children of p into subpartitions of p.
		ActivityNode nextNode= p.getIdentifiedStartNode();

		// Empty partition?
		if (nextNode == null)
			return;

		while (true) {

			ActivityNode currentNode= nextNode;
			ActivityNode designatedNext= null;

			System.out.println("Currently handling next node " + nextNode);

			boolean handled= false;

			// Prohibit multiple incoming edges. Required for correct partition
			// implementation.

			// TODO commented out because of interrupted edge handling...

			// if (currentNode.getIncomings().size() > 1 && (! ( (currentNode
			// instanceof DecisionNode) || (currentNode instanceof MergeNode))))
			// throw new
			// TransformationException("Found a node with more than one incoming links. This is not allowed: "
			// + currentNode);

			if (currentNode instanceof InitialNode || currentNode instanceof FinalNode) {
				// Simply skip it.
				handled= true;
			}

			if (UMLNodeUtil.isServiceActivity(currentNode)) {
				/*
				 * A nested service activity.
				 */
				StructuredActivityNode sa= ((StructuredActivityNode) currentNode);
				p.addSubPartition(new Partition(PartitionType.ServiceActivity, sa.getName(), p.getCurrentContext(), sa, sa));

				// Next node (might be null)
				designatedNext= UMLNodeUtil.getOneNextNode(currentNode);
				handled= true;
			}

			if (UMLNodeUtil.isServiceAction(currentNode) || UMLNodeUtil.isContributingAction(currentNode)) {
				/*
				 * We found a simple action node. Create one single action
				 * partition.
				 */
				p.addSubPartition(new Partition(PartitionType.SingleAction, "SingleAction", p.getCurrentContext(), currentNode, currentNode));

				// Next node (might be null)
				designatedNext= UMLNodeUtil.getOneNextNode(currentNode);
				handled= true;
			}

			if (UMLNodeUtil.isMergeNode(currentNode)) {
				/*
				 * We found a merge node without having first found a decision
				 * node. This means we have a loop. We need to find the end of
				 * the loop.
				 */
				FinderResult result= PathFinder.resolve(SearchType.LOOP, currentNode, p.getElements());
				List<ActivityNode> nodesInPartition= result.getElements();
				Partition loopPartition= new Partition(PartitionType.Loop, "Loop", p.getCurrentContext(), currentNode, nodesInPartition);
				loopPartition.setIdentifiedEndNode(result.getFinalNode());
				p.addSubPartition(loopPartition);

				// Next node (might be null)
				designatedNext= result.getNextNode();
				handled= true;
			}

			if (UMLNodeUtil.isDecisionNode(currentNode)) {
				/*
				 * We found a decision node without having first found a merge
				 * node. This means we have a decision. We need to find the end.
				 */

				FinderResult result= PathFinder.resolve(SearchType.DECISION, currentNode, p.getElements());
				List<ActivityNode> nodesInPartition= result.getElements();

				Partition branchPartition= new Partition(PartitionType.Branch, "Branch", p.getCurrentContext(), currentNode, nodesInPartition);
				branchPartition.setIdentifiedEndNode(result.getFinalNode());
				p.addSubPartition(branchPartition);

				// Next node (might be null)
				designatedNext= result.getNextNode();
				handled= true;
			}

			if (UMLNodeUtil.isForkNode(currentNode)) {
				/*
				 * Found a fork. Look for Join.
				 */
				FinderResult result= PathFinder.resolve(SearchType.PARALLEL, currentNode, p.getElements());
				List<ActivityNode> nodesInPartition= result.getElements();

				Partition parallelPartition= new Partition(PartitionType.Parallel, "Parallel", p.getCurrentContext(), currentNode, nodesInPartition);
				parallelPartition.setIdentifiedEndNode(result.getFinalNode());
				p.addSubPartition(parallelPartition);

				// Next node (might be null)
				designatedNext= result.getNextNode();
				handled= true;
			}

			if (!handled)
				throw new TransformationException("Unknown node type:  " + currentNode);

			/*
			 * Check if there is a next node to handle. Otherwise, we are done
			 * dividing.
			 */
			if (p.containsNode(designatedNext)) // is also false if null.
				nextNode= designatedNext;
			else
				break;
		}

	}

	// ******************************************************
	// CONQUER
	// ******************************************************


	private void conquer(Partition p) throws TransformationException {

		List<Partition> subPartitions= p.getSubPartitions();
		for (Partition partition : subPartitions) {

			switch (partition.getType()) {
				case SingleAction: {
					conquerSingleAction(partition);
					break;
				}
				case Loop: {
					conquerLoop(partition);
					break;
				}
				case Branch: {
					conquerPathBased(partition, ISMBehaviourFactory.eINSTANCE.createDecision());
					break;
				}
				case Parallel: {
					conquerPathBased(partition, ISMBehaviourFactory.eINSTANCE.createParallel());
					break;
				}
				case ServiceActivity: {
					conquerServiceActivity(partition);
					break;
				}
				default: {
					throw new TransformationException("Unknown Partition Type:  " + partition.getType());
				}
			}
		}
	}

	private void conquerSingleAction(Partition partition) throws TransformationException {

		ActivityNode theOnlyNode= partition.getIdentifiedStartNode();
		if (theOnlyNode == null)
			throw new TransformationException("Trying to handle a single action partition, but it has no start node: " + partition);

		SimpleElement element= null;
		if (UMLNodeUtil.isServiceAction(theOnlyNode))
			element= handleServiceAction(partition.getCurrentServiceActivity(), (Action) theOnlyNode);
		else
			element= handleContributingAction(partition, theOnlyNode);

		if (element == null)
			throw new TransformationException("Unknown element in transformation: " + theOnlyNode);

		partition.getCurrentContext().getChildren().add(element);
	}


	private void conquerLoop(Partition partition) throws TransformationException {

		Loop loop= ISMBehaviourFactory.eINSTANCE.createLoop();
		Path path= ISMBehaviourFactory.eINSTANCE.createPath();
		loop.getChildren().add(path);

		partition.getCurrentContext().getChildren().add(loop);

		List<ActivityNode> innards= new ArrayList<ActivityNode>(partition.getElements());

		// Must have a start node.
		ActivityNode start= partition.getIdentifiedStartNode();
		if (start == null)
			throw new TransformationException("Trying to handle a loop partition, but it has no start node: " + partition);
		innards.remove(start);
		ActivityNode innerStartNode= UMLNodeUtil.getOneNextNode(start);

		// A loop must have an end node.
		ActivityNode end= partition.getIdentifiedEndNode();
		if (end == null)
			throw new TransformationException("Trying to handle a loop partition, but it has no end node: " + partition);
		innards.remove(end);


		// Conditions at the end...?
		EList<ActivityEdge> outgoings= end.getOutgoings();
		for (ActivityEdge activityEdge : outgoings) {
			if (activityEdge.getTarget().equals(start)) {
				RightHandSideExpression rhs= parseGuard(partition, activityEdge);
				if (rhs != null)
					path.setEnterCondition(rhs);
			} else {
				// As there are only two outgoing edges, this is the other
				// one...
				RightHandSideExpression rhs= parseGuard(partition, activityEdge);
				if (rhs != null)
					loop.setLeaveCondition(rhs);
			}
		}

		Partition loopInnards= new Partition(PartitionType.MultipleActions, "Path", path, innerStartNode, innards);
		divideAndConquer(loopInnards);
	}

	private RightHandSideExpression parseGuard(Partition partition, ActivityEdge activityEdge) throws TransformationException {
		ValueSpecification guard= activityEdge.getGuard();
		if ( (guard != null) && (guard.stringValue() != null) && (!guard.stringValue().isEmpty())) {
			String guardString= guard.stringValue();
			return ParserAccess.parseGuard(partition.getCurrentServiceActivity(), guardString);
		}
		return null;
	}

	private void conquerPathBased(Partition partition, PathBasedPartition pbp) throws TransformationException {

		partition.getCurrentContext().getChildren().add(pbp);
		List<ActivityNode> innards= new ArrayList<ActivityNode>(partition.getElements());

		// Must have a start node.
		ActivityNode start= partition.getIdentifiedStartNode();
		if (start == null)
			throw new TransformationException("Partition " + partition + " has no start node: " + partition);
		innards.remove(start);

		// Might have an end node.
		ActivityNode end= partition.getIdentifiedEndNode();
		if (end != null)
			innards.remove(end);

		// The start node is supposed to have multiple outgoing links.
		EList<ActivityEdge> outgoings= start.getOutgoings();
		for (ActivityEdge activityEdge : outgoings) {

			ActivityNode target= activityEdge.getTarget();
			if (target == null)
				throw new TransformationException("Target of edge starting from " + start + " is null.");

			Path path= ISMBehaviourFactory.eINSTANCE.createPath();
			path.setName("Path starting with " + target.getName());
			pbp.getChildren().add(path);

			if (pbp instanceof Decision) {
				// Check for conditions.
				ValueSpecification guard= activityEdge.getGuard();
				if ( (guard != null) && (guard.stringValue() != null) && (!guard.stringValue().isEmpty())) {
					String guardString= guard.stringValue();
					RightHandSideExpression parsedGuard= ParserAccess.parseGuard(partition.getCurrentServiceActivity(), guardString);
					path.setEnterCondition(parsedGuard);
				}
			}

			// Handle the path
			Partition pathInnards= new Partition(PartitionType.MultipleActions, "Path", path, target, innards);
			divideAndConquer(pathInnards);
		}
	}


	private void conquerServiceActivity(Partition partition) throws TransformationException {

		ActivityNode theSAN= partition.getIdentifiedStartNode();
		if (! (theSAN instanceof StructuredActivityNode))
			throw new TransformationException("Trying to handle a service activity partition, but it has no initial and only node:" + partition);

		StructuredActivityNode san= (StructuredActivityNode) theSAN;

		ActivityNode firstNode= UMLNodeUtil.getFirstNode(san.getContainedNodes());
		if (firstNode == null)
			throw new TransformationException("Couldn't find a first node in service activity " + san.getName());

		ServiceActivity activity= ISMBehaviourFactory.eINSTANCE.createServiceActivity();
		activity.setName(san.getName());

		partition.getCurrentContext().getChildren().add(activity);

		// Handle the normal inner parts (with the newly created activity as the
		// context!)
		Partition innerSAPartition= new Partition(PartitionType.MultipleActions, partition.getName(), activity, firstNode, san.getContainedNodes());
		divideAndConquer(innerSAPartition);

		// There might be interrupting receives.
		List<ActivityNode> interrupts= getInterruptingReceives(firstNode, san.getContainedNodes());
		for (ActivityNode intrRcv : interrupts) {
			SimpleElement theReceive= handleServiceAction(activity, (Action) intrRcv);
			activity.getInterruptingReceives().add((Receive) theReceive);
		}

		// There might be handlers.
		Map<NamedElement, HandlerType> handlers= new HashMap<NamedElement, HandlerType>();
		Map<Activity, ObjectNode> exceptionInputPins= new HashMap<Activity, ObjectNode>();

		for (ActivityEdge activityEdge : san.getOutgoings()) {

			boolean isHandler= false;
			if (StereotypeUtil.hasStereotype(activityEdge, StereoType.EVENTFLOW)) {
				handlers.put(activityEdge.getTarget(), HandlerType.EVENT);
				isHandler= true;
			}
			if (StereotypeUtil.hasStereotype(activityEdge, StereoType.COMPENSATIONFLOW)) {
				handlers.put(activityEdge.getTarget(), HandlerType.COMPENSATION);
				isHandler= true;
			}

			if (isHandler && ! (activityEdge.getTarget() instanceof StructuredActivityNode))
				throw new TransformationException("All handlers must be service activities. " + activityEdge.getTarget().getName() + " is not.");
		}

		for (ExceptionHandler exceptionHandler : san.getHandlers()) {

			ExecutableNode body= exceptionHandler.getHandlerBody();
			if (! (body instanceof CallBehaviorAction))
				throw new TransformationException("An exception handler body must be a call behaviour action.");

			Behavior theHandler= ((CallBehaviorAction) body).getBehavior();
			if (theHandler == null || ! (theHandler instanceof Activity))
				throw new TransformationException("Action " + body.getName() + " does not reference a proper activity.");

			if (!StereotypeUtil.hasStereotype(theHandler, StereoType.SERVICEACTIVITY))
				throw new TransformationException("Activity " + theHandler.getName() + " is not a service activity.");

			ObjectNode exceptionInput= exceptionHandler.getExceptionInput();
			if (exceptionInput == null)
				throw new TransformationException("Need an exception input node to create exception handler " + theHandler.getName());
			exceptionInputPins.put((Activity) theHandler, exceptionInput);

			handlers.put(theHandler, HandlerType.EXCEPTION);
		}

		for (NamedElement node : handlers.keySet()) {

			Handler h= null;
			EList<ActivityNode> containedNodes= null;

			switch (handlers.get(node)) {
				case EVENT:
					h= ISMBehaviourFactory.eINSTANCE.createEventHandler();
					containedNodes= ((StructuredActivityNode) node).getContainedNodes();
					break;
				case COMPENSATION:
					h= ISMBehaviourFactory.eINSTANCE.createCompensationHandler();
					containedNodes= ((StructuredActivityNode) node).getContainedNodes();
					break;
				case EXCEPTION:
					h= ISMBehaviourFactory.eINSTANCE.createExceptionHandler();
					containedNodes= ((Activity) node).getNodes();
					break;
			}

			ActivityNode firstHandlerNode= UMLNodeUtil.getFirstNode(containedNodes);
			if (firstHandlerNode == null)
				throw new TransformationException("Could not find a first node in handler " + node.getName());

			h.setName(node.getName());
			activity.getHandlers().add(h);

			if (handlers.get(node).equals(HandlerType.EXCEPTION)) {
				ObjectNode exInput= exceptionInputPins.get(node);
				Type type= exInput.getType();
				if (type == null)
					throw new TransformationException("Exception Input Pin " + exInput.getName() + " does not have a type.");


				SMMType ismType= SMMUtil.addExceptionType(fParticipant, type);
				((eu.mdd4soa.smm.behaviour.ExceptionHandler) h).setExceptionType((ExceptionType) ismType);
				// TODO still need the exception variable reference.
			}

			Partition handlerPartition= new Partition(PartitionType.MultipleActions, node.getName(), h, firstHandlerNode, containedNodes);
			divideAndConquer(handlerPartition);
		}

	}

	private List<ActivityNode> getInterruptingReceives(ActivityNode identifiedStartNode, List<ActivityNode> elements) {

		List<ActivityNode> interruptingReceives= new ArrayList<ActivityNode>();
		for (ActivityNode activityNode : elements) {

			if (StereotypeUtil.hasStereotype(activityNode, StereoType.RECEIVE) && UMLNodeUtil.hasOutgoingInterruptingEdge(activityNode))
				interruptingReceives.add(activityNode);
		}

		return interruptingReceives;
	}


	private ServiceInteraction handleServiceAction(ServiceActivity context, Action theOnlyNode) throws TransformationException {

		ServiceInteraction interaction= null;

		if (StereotypeUtil.hasStereotype(theOnlyNode, StereoType.RECEIVE)) {
			interaction= ISMBehaviourFactory.eINSTANCE.createReceive();
			handlePins(context, interaction, theOnlyNode, TransactionType.RECEIVE);
			fReceiveMap.put((AcceptCallAction) theOnlyNode, (Receive) interaction);
		}

		if (StereotypeUtil.hasStereotype(theOnlyNode, StereoType.SEND)) {
			interaction= ISMBehaviourFactory.eINSTANCE.createSend();
			handlePins(context, interaction, theOnlyNode, TransactionType.SEND);
		}

		if (StereotypeUtil.hasStereotype(theOnlyNode, StereoType.SENDANDRECEIVE)) {
			interaction= ISMBehaviourFactory.eINSTANCE.createSendAndReceive();
			handlePins(context, interaction, theOnlyNode, TransactionType.SENDANDRECEIVE);
		}

		if (StereotypeUtil.hasStereotype(theOnlyNode, StereoType.REPLY)) {
			interaction= ISMBehaviourFactory.eINSTANCE.createReply();
			handlePins(context, interaction, theOnlyNode, TransactionType.REPLY);
			matchReplyToReceive(context, theOnlyNode, interaction);
		}

		interaction.setName(theOnlyNode.getName());
		return interaction;
	}

	private void matchReplyToReceive(ServiceActivity context, Action theReplyNode, ServiceInteraction theReplyInteraction)
			throws TransformationException {

		Trigger replyToCall= ((ReplyAction) theReplyNode).getReplyToCall();
		if (replyToCall == null)
			return; // no match

		AcceptCallAction rcvNode= null;
		Set<AcceptCallAction> keySet= fReceiveMap.keySet();
		for (AcceptCallAction acceptCallAction : keySet) {
			boolean done= false;
			EList<Trigger> triggers= acceptCallAction.getTriggers();
			for (Trigger trigger : triggers) {
				if (trigger.equals(replyToCall)) {
					rcvNode= acceptCallAction;
					done= true;
					break;
				}
			}
			if (done)
				break;
		}

		if (rcvNode == null)
			throw new TransformationException("Could not find the AcceptCallAction linked from the reply-to-call trigger of reply node "
					+ theReplyNode.getName());

		Receive receiveInteraction= fReceiveMap.get(rcvNode);
		if (receiveInteraction == null)
			throw new TransformationException("Found AcceptCallAction " + rcvNode.getName() + " from reply-to-call trigger of "
					+ theReplyNode.getName() + ", but not the corresponding interaction -- not converte yet?");

		((Reply) theReplyInteraction).setReceive(receiveInteraction);
	}

	private void handlePins(ServiceActivity context, ServiceInteraction interaction, Action theOnlyNode, TransactionType ttype)
			throws TransformationException {

		handleLink(interaction, theOnlyNode);
		if (interaction.getPartner() == null)
			return;

		handleOperation(interaction, ttype, theOnlyNode);
		if (interaction.getOperation() == null)
			return;

		// SEND PINS:

		if (ttype.equals(TransactionType.SEND) || ttype.equals(TransactionType.SENDANDRECEIVE)) {

			// In a send or send and receive, we invoke something on the
			// outside, i.e. we give data as parameters to an operation call.
			handleSendPins(context, interaction, theOnlyNode, getInParameters(interaction.getOperation()));
		}
		if (ttype.equals(TransactionType.REPLY)) {
			// In a reply, we reply to a previous receive, so we give data as
			// RESULTS of an operation call.
			handleSendPins(context, interaction, theOnlyNode, getOutParameters(interaction.getOperation()));
		}

		/*
		 * Receive pins.
		 * 
		 * "The result pins must match the in and inout parameters of the
		 * operation specified by the trigger event in number, type, and order."
		 */

		if (ttype.equals(TransactionType.RECEIVE)) {

			// In a receive, we definitely get called from the outside, i.e. we
			// receive the parameters of the call.
			handleReceivePins(context, interaction, theOnlyNode, getInParameters(interaction.getOperation()));
		}

		if (ttype.equals(TransactionType.SENDANDRECEIVE)) {

			// In a send and receive, we call someone on the outside and then
			// get back a result. I.e. we receive the RESULT of the call.
			handleReceivePins(context, interaction, theOnlyNode, getOutParameters(interaction.getOperation()));
		}
	}

	private void handleSendPins(ServiceActivity context, ServiceInteraction interaction, Action sendOrReplyAction,
			List<InterfaceParameter> parametersOfOperation) throws TransformationException {

		// We disregard pins not explicitely declared as send pins.
		List<InputPin> usedParameters= new ArrayList<InputPin>();
		for (InputPin outputPin : sendOrReplyAction.getInputs()) {
			if (StereotypeUtil.hasStereotype(outputPin, StereoType.SNDPIN))
				usedParameters.add(outputPin);
		}

		if (parametersOfOperation.size() != usedParameters.size())
			throw new TransformationException("Problem trying to handle send pins in action " + sendOrReplyAction
					+ ". The number of snd pins does not match the number specified in the operation.");

		for (int i= 0; i < parametersOfOperation.size(); i++) {
			InterfaceParameter op= parametersOfOperation.get(i);
			InputPin inputPin= usedParameters.get(i);

			if (inputPin.getType() == null)
				throw new TransformationException("Input pin " + inputPin + " of action " + sendOrReplyAction + " does not have a type.");

			SMMType typeOfInputPin= SMMUtil.resolveType(fParticipant, inputPin.getType().getName());
			if (!typeOfInputPin.equals(op.getType()))
				throw new TransformationException("Input pin #" + i + " (" + inputPin + ") of action " + sendOrReplyAction + " has type "
						+ typeOfInputPin + ", but was expecting " + op.getType() + " as declared.");

			// OK!
			SendParameter sndParameter= ISMDataFactory.eINSTANCE.createSendParameter();
			sndParameter.setOperationParameter(op);

			String name= inputPin.getName();
			RightHandSideExpression expression= ParserAccess.parseSendPin(context, name);

			// TODO check that the type matches

			sndParameter.setSource(expression);
			interaction.getSndParameters().add(sndParameter);

		}

	}

	private void handleReceivePins(ServiceActivity context, ServiceInteraction interaction, Action rcvOrSndRcvAction,
			List<InterfaceParameter> parametersOfOperation) throws TransformationException {

		// We disregard pins not explicitely declared as receive pins.
		List<OutputPin> usedParameters= new ArrayList<OutputPin>();
		for (OutputPin outputPin : rcvOrSndRcvAction.getOutputs()) {
			if (StereotypeUtil.hasStereotype(outputPin, StereoType.RCVPIN))
				usedParameters.add(outputPin);
		}

		if (parametersOfOperation.size() != usedParameters.size())
			throw new TransformationException("Problem trying to handle receive pins in action " + rcvOrSndRcvAction.getName()
					+ ". The number of rcv pins does not match the number specified in the operation.");

		for (int i= 0; i < parametersOfOperation.size(); i++) {
			InterfaceParameter op= parametersOfOperation.get(i);
			OutputPin outputPin= usedParameters.get(i);

			if (outputPin.getType() == null)
				throw new TransformationException("Output pin " + outputPin.getName() + " of action " + rcvOrSndRcvAction.getName()
						+ " does not have a type.");

			SMMType typeOfOutputPin= SMMUtil.resolveType(fParticipant, outputPin.getType().getName());

			if (typeOfOutputPin == null)
				throw new TransformationException("Could not resolve type '" + outputPin.getType().getName() + "' of output pin '"
						+ outputPin.getName() + "'. Does this type occur in an operation?");


			if (!typeOfOutputPin.equals(op.getType()))
				throw new TransformationException("Output pin #" + i + " (" + outputPin.getName() + ") of action " + rcvOrSndRcvAction.getName()
						+ " has type " + typeOfOutputPin + ", but was expecting " + op.getType() + " as declared.");

			// OK!
			ReceiveParameter rcvParameter= ISMDataFactory.eINSTANCE.createReceiveParameter();
			rcvParameter.setOperationParameter(op);

			String name= outputPin.getName();
			LeftHandSideExpression expression= ParserAccess.parseReceivePin(context, typeOfOutputPin, name);

			rcvParameter.setTarget(expression);
			interaction.getRcvParameters().add(rcvParameter);
		}
	}

	/**
	 * Find the link...
	 * 
	 * @param interaction
	 * @param theOnlyNode
	 * @throws TransformationException
	 */
	private void handleLink(ServiceInteraction interaction, Action theOnlyNode) throws TransformationException {

		List<Pin> pins= new ArrayList<Pin>();
		pins.addAll(theOnlyNode.getInputs());
		pins.addAll(theOnlyNode.getOutputs());

		String partner= null;

		for (Pin pin : pins) {
			if (StereotypeUtil.hasStereotype(pin, StereoType.LNKPIN)) {
				partner= pin.getName();
				break;
			}
		}

		if (partner != null) {

			Service point= null;

			EList<Service> partners= fParticipant.getPartners();
			for (Service partnerPoint : partners) {
				if (partnerPoint.getName().equals(partner)) {
					point= partnerPoint;
					break;
				}
			}

			if (point == null)
				throw new TransformationException("Link pin of action " + theOnlyNode + " specified partner " + partner
						+ ", but this is not a valid partner point of the participant.");

			interaction.setPartner(point);
		}
	}

	private void handleOperation(ServiceInteraction interaction, TransactionType ttype, Action node) throws TransformationException {

		Operation foundOp= null;

		if (node instanceof AcceptCallAction || node instanceof ReplyAction) {

			Trigger call= null;

			if (node instanceof ReplyAction) {
				call= ((ReplyAction) node).getReplyToCall();
			} else {
				EList<Trigger> triggers= ((AcceptCallAction) node).getTriggers();

				// No triggers is okay...
				if (triggers.isEmpty())
					return;

				if (triggers.size() > 1)
					throw new TransformationException("Found several triggers in action " + node + ". Please only use one.");

				call= triggers.get(0);
			}

			// No triggers is okay (for the moment).
			if (call == null)
				return;

			Event event= call.getEvent();
			if (! (event instanceof CallEvent))
				throw new TransformationException("Event of trigger " + call + " in action " + node + " is not a call event or is empty.");

			Operation operation= ((CallEvent) event).getOperation();
			if (operation == null)
				throw new TransformationException("Event " + event + " of trigger " + call + " of action " + node + " does not contain an operation.");

			foundOp= operation;
		}

		if (node instanceof CallOperationAction) {
			foundOp= ((CallOperationAction) node).getOperation();
		}

		// TODO actually, this is a problem...
		if (foundOp == null)
			return;

		/*
		 * Two choices here. Either we look at the owner of the operation and
		 * try to match it with a known SMM type, or we look at the type at the
		 * link. The latter is easier as the owner of the operation might not be
		 * the service interface.
		 */

		Service partner= interaction.getPartner();
		if (partner == null)
			throw new TransformationException("Cannot look for an operation without a specified partner point.");


		InterfaceType partnerInterface= partner.getInterface();

		if (partnerInterface == null)
			throw new TransformationException("Partner " + partner + " does not have a type.");


		EList<InterfaceOperation> toSearch= null;

		switch (ttype) {
			case RECEIVE:
			case REPLY:
				if (partner instanceof ProvidedService)
					toSearch= partnerInterface.getImplemented();
				else
					toSearch= partnerInterface.getUsed();
				break;
			case SEND:
			case SENDANDRECEIVE:
				if (partner instanceof ProvidedService)
					toSearch= partnerInterface.getUsed();
				else
					toSearch= partnerInterface.getImplemented();
				break;
		}

		if (toSearch == null)
			throw new NullPointerException("BUG: For some reason, toSearch is null.");

		InterfaceOperation match= null;

		// TODO this prevents overloading.
		for (InterfaceOperation serviceOperation : toSearch) {
			if (serviceOperation.getName().equals(foundOp.getName())) {
				match= serviceOperation;
				break;
			}
		}

		if (match == null)
			throw new TransformationException("Was looking for operation " + foundOp.getName() + " in SMM type " + partnerInterface
					+ ", but did not find it.");

		interaction.setOperation(match);
	}

	private SimpleElement handleContributingAction(Partition p, ActivityNode node) throws TransformationException {

		SimpleElement element= null;

		if (StereotypeUtil.hasStereotype(node, StereoType.DATA)) {
			DataHandling data= ISMBehaviourFactory.eINSTANCE.createDataHandling();

			data.setName(node.getName());

			if (! (node instanceof OpaqueAction))
				throw new TransformationException("Found a data handling node which is not an OpaqueAction: " + node);

			EList<String> bodies= ((OpaqueAction) node).getBodies();

			// Empty data is allowed.
			if (bodies != null && !bodies.isEmpty()) {
				String body= flatten(bodies);
				List<Statement> statements= ParserAccess.parseDataAction(p.getCurrentServiceActivity(), body);
				data.getStatements().addAll(statements);
			}

			element= data;
		}

		if (node instanceof RaiseExceptionAction) {
			Throw throu= ISMBehaviourFactory.eINSTANCE.createThrow();

			InputPin exception= ((RaiseExceptionAction) node).getException();
			if (exception == null)
				throw new TransformationException("Raise exception action " + node.getName() + " does not have an exception attached.");

			Type exceptionType= exception.getType();
			SMMType matching= SMMUtil.addExceptionType(fParticipant, exceptionType);
			throu.setExceptionType((ExceptionType) matching);

			element= throu;
		}

		if (StereotypeUtil.hasStereotype(node, StereoType.COMPENSATEALL)) {
			// Just add the element; nothing more required.
			element= ISMBehaviourFactory.eINSTANCE.createCompensateAll();
		}

		if (StereotypeUtil.hasStereotype(node, StereoType.COMPENSATE)) {
			element= ISMBehaviourFactory.eINSTANCE.createCompensate();

			if (! (node instanceof OpaqueAction))
				throw new TransformationException("Compensate action " + node.getName() + " is not an opaque action.");

			String body= flatten( ((OpaqueAction) node).getBodies());
			if (body.isEmpty())
				throw new TransformationException("Compensate action " + node.getName()
						+ " does not have a specification of the target service activity in its body.");

			ServiceActivity target= SMMUtil.resolveServiceActivity(fParticipant, body);
			if (target == null)
				throw new TransformationException("Compensate action " + node.getName() + " references service activity " + body
						+ " which does not exist.");

			((Compensate) element).setTarget(target);
		}

		return element;
	}

	// Helpers

	private List<InterfaceParameter> getOutParameters(InterfaceOperation operation) {
		List<InterfaceParameter> declaredOutgoingParameters= new ArrayList<InterfaceParameter>();
		for (InterfaceParameter serviceOperationParameter : operation.getParameters()) {
			if (serviceOperationParameter instanceof OutParameter)
				declaredOutgoingParameters.add(serviceOperationParameter);
		}
		return declaredOutgoingParameters;
	}

	private List<InterfaceParameter> getInParameters(InterfaceOperation operation) {
		List<InterfaceParameter> declaredIncomingParameters= new ArrayList<InterfaceParameter>();
		for (InterfaceParameter serviceOperationParameter : operation.getParameters()) {
			if (serviceOperationParameter instanceof InParameter)
				declaredIncomingParameters.add(serviceOperationParameter);
		}
		return declaredIncomingParameters;
	}

	private String flatten(EList<String> bodies) {
		String body= "";
		for (String string : bodies) {
			body+= string.replace("\n", "");
		}
		return body;
	}

}
