/*
 * 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.smm2java.impl;

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

import org.eclipse.emf.common.util.EList;
import org.eclipse.gmt.modisco.java.AbstractTypeDeclaration;
import org.eclipse.gmt.modisco.java.ArrayType;
import org.eclipse.gmt.modisco.java.Assignment;
import org.eclipse.gmt.modisco.java.AssignmentKind;
import org.eclipse.gmt.modisco.java.Block;
import org.eclipse.gmt.modisco.java.BodyDeclaration;
import org.eclipse.gmt.modisco.java.CatchClause;
import org.eclipse.gmt.modisco.java.ClassInstanceCreation;
import org.eclipse.gmt.modisco.java.CompilationUnit;
import org.eclipse.gmt.modisco.java.EnumConstantDeclaration;
import org.eclipse.gmt.modisco.java.EnumDeclaration;
import org.eclipse.gmt.modisco.java.Expression;
import org.eclipse.gmt.modisco.java.ExpressionStatement;
import org.eclipse.gmt.modisco.java.FieldDeclaration;
import org.eclipse.gmt.modisco.java.IfStatement;
import org.eclipse.gmt.modisco.java.InfixExpressionKind;
import org.eclipse.gmt.modisco.java.MethodDeclaration;
import org.eclipse.gmt.modisco.java.MethodInvocation;
import org.eclipse.gmt.modisco.java.Model;
import org.eclipse.gmt.modisco.java.Package;
import org.eclipse.gmt.modisco.java.ParameterizedType;
import org.eclipse.gmt.modisco.java.PrimitiveType;
import org.eclipse.gmt.modisco.java.PrimitiveTypeBoolean;
import org.eclipse.gmt.modisco.java.PrimitiveTypeChar;
import org.eclipse.gmt.modisco.java.ReturnStatement;
import org.eclipse.gmt.modisco.java.SingleVariableAccess;
import org.eclipse.gmt.modisco.java.SingleVariableDeclaration;
import org.eclipse.gmt.modisco.java.Statement;
import org.eclipse.gmt.modisco.java.SwitchCase;
import org.eclipse.gmt.modisco.java.SwitchStatement;
import org.eclipse.gmt.modisco.java.TryStatement;
import org.eclipse.gmt.modisco.java.Type;
import org.eclipse.gmt.modisco.java.TypeDeclaration;
import org.eclipse.gmt.modisco.java.VariableDeclarationFragment;
import org.eclipse.gmt.modisco.java.VariableDeclarationStatement;
import org.eclipse.gmt.modisco.java.WhileStatement;
import org.eclipse.gmt.modisco.java.emf.JavaFactory;

import eu.mdd4soa.smm.behaviour.ProtocolState;
import eu.mdd4soa.smm.behaviour.ProtocolTransition;
import eu.mdd4soa.smm.behaviour.ReceiveReplyingTransition;
import eu.mdd4soa.smm.behaviour.ReceivingTransition;
import eu.mdd4soa.smm.behaviour.ReplyingTransition;
import eu.mdd4soa.smm.behaviour.SendingTransition;
import eu.mdd4soa.smm.behaviour.ServiceActivity;
import eu.mdd4soa.smm.exception.TransformationException;
import eu.mdd4soa.smm.statik.BooleanType;
import eu.mdd4soa.smm.statik.InParameter;
import eu.mdd4soa.smm.statik.InterfaceOperation;
import eu.mdd4soa.smm.statik.InterfaceParameter;
import eu.mdd4soa.smm.statik.Participant;
import eu.mdd4soa.smm.statik.ProvidedService;
import eu.mdd4soa.smm.statik.RequiredService;
import eu.mdd4soa.smm.statik.SMMType;
import eu.mdd4soa.smm.statik.Service;
import eu.mdd4soa.smm.statik.StringType;
import eu.mdd4soa.trans.smm2java.JMHelper;
import eu.mdd4soa.trans.smm2java.impl.PartnerTracker.Partner;

public class SimulationCreator {

	private static final String EVENT_PARTNERSTARTUP= "STARTUP";

	private static final String EVENT_PARTNERCOMPLETION= "COMPLETION";

	private static final String EVENT_PARTNERMAYCOMPLETE= "MAYCOMPLETE";

	private static final String EVENT_TIMEOUT= "TIMEOUT";

	private static final String EVENT_PROBLEMINSEND= "PROBLEMINSEND";

	private static final String EVENT_PROBLEMINRECEIVE= "PROBLEMINRECEIVE";


	public static void createSimulation(Model model, Participant p, ServiceActivity behaviour, Package rootPackage, TypeTracker typeTracker,
			PartnerTracker partners) throws TransformationException {
		SimulationCreator c= new SimulationCreator(model, p, behaviour, rootPackage, typeTracker, partners);
		c.execute();
	}

	private Model fModel;

	private Participant fParticipant;

	private Package fRootPackage;

	private TypeTracker fTypeTracker;

	private PartnerTracker fPartners;

	private TypeDeclaration fSimClass;

	private CompilationUnit fSimUnit;

	private Type fServiceExceptionType;

	private Type fRunnable;

	private AbstractTypeDeclaration fSimHelper;

	private MethodDeclaration fSimHelperRandomMethod;

	private ServiceActivity fServiceActivity;

	private MethodDeclaration fSimHelperWaitMethod;

	private MethodDeclaration fSimHelperLogMethod;

	private AbstractTypeDeclaration fArrayBlckQueue;

	private ParameterizedType fArrayBlckQueueString;

	private Map<Service, FieldDeclaration> fPartnerQueueFields;

	private Map<Service, FieldDeclaration> fPartnerRunnableFields;

	private MethodDeclaration fArrayBlckQueueAddMethod;

	private Map<Service, FieldDeclaration> fPartnerImplementationFields;

	private FieldDeclaration fPUTClassField;

	private TypeDeclaration fPUTClass;

	private MethodDeclaration fArrayBlckQueuePeekMethod;

	private Map<InterfaceOperation, MethodDeclaration> fSendMethods;

	private MethodDeclaration fArrayBlckQueuePollMethod;

	private MethodDeclaration fStringEqualsMethod;

	private MethodDeclaration fExceptionGetMessageMethod;

	public SimulationCreator(Model model, Participant p, ServiceActivity activity, Package rootPackage, TypeTracker t, PartnerTracker partners) {
		fModel= model;
		fParticipant= p;
		fServiceActivity= activity;
		fRootPackage= rootPackage;
		fTypeTracker= t;
		fPartners= partners;
	}

	private void execute() throws TransformationException {

		// Create the simulation class.

		fSimClass= fTypeTracker.createClass(fRootPackage.getName() + ".simulation." + fServiceActivity.getName() + "Simulator");
		fSimUnit= fSimClass.getOriginalCompilationUnit();
		fSimClass.setModifier(JMHelper.createModifier("public"));

		// Required types:

		fArrayBlckQueue= (AbstractTypeDeclaration) fTypeTracker.getType("java.util.concurrent.ArrayBlockingQueue");
		fArrayBlckQueueString= fTypeTracker.createParameterizedType(fArrayBlckQueue, fTypeTracker.getType("java.lang.String"));
		fArrayBlckQueueAddMethod= JMHelper.findMethod(fArrayBlckQueue, "add");
		fArrayBlckQueuePeekMethod= JMHelper.findMethod(fArrayBlckQueue, "peek");
		fArrayBlckQueuePollMethod= JMHelper.findMethod(fArrayBlckQueue, "poll");

		fServiceExceptionType= fTypeTracker.getType("util.ServiceException");
		JMHelper.ensureImport(fSimUnit, fServiceExceptionType);

		fRunnable= fTypeTracker.getType("java.lang.Runnable");

		fSimHelper= (AbstractTypeDeclaration) fTypeTracker.getType("util.SimHelper");
		JMHelper.ensureImport(fSimUnit, fSimHelper);
		fSimHelperRandomMethod= JMHelper.findMethod((AbstractTypeDeclaration) fSimHelper, "getRandomNumber");
		fSimHelperWaitMethod= JMHelper.findMethod((AbstractTypeDeclaration) fSimHelper, "waitRandom");
		fSimHelperLogMethod= JMHelper.findMethod((AbstractTypeDeclaration) fSimHelper, "log");

		AbstractTypeDeclaration throwable= (AbstractTypeDeclaration) fTypeTracker.getType("java.lang.Throwable");
		fExceptionGetMessageMethod= JMHelper.findMethod((AbstractTypeDeclaration) throwable, "getMessage");

		fStringEqualsMethod= JMHelper.findMethod((AbstractTypeDeclaration) fTypeTracker.getType("java.lang.String"), "equals");

		fPUTClass= fTypeTracker.getMainClass(fServiceActivity);
		JMHelper.ensureImport(fSimUnit, fPUTClass);
		fPUTClassField= JMHelper.createField("private", "mainBehaviour", fPUTClass);
		JMHelper.addField(fSimClass, fPUTClassField);

		// First, create some startup stuff like partner fields.

		fPartnerRunnableFields= new HashMap<Service, FieldDeclaration>();
		fPartnerQueueFields= new HashMap<Service, FieldDeclaration>();

		fPartnerImplementationFields= new HashMap<Service, FieldDeclaration>();

		fSendMethods= new HashMap<InterfaceOperation, MethodDeclaration>();

		/******************************************************************************************
		 * LOOP START FOR ALL SERVICES
		 ******************************************************************************************/

		for (Service service : fParticipant.getPartners()) {

			// enum over the states.
			EList<ProtocolState> states= service.getProtocol().getStates();
			EnumDeclaration stateEnum= JMHelper.createEnumDeclaration("States_" + service.getName());
			fSimClass.getBodyDeclarations().add(stateEnum);

			Map<ProtocolState, EnumConstantDeclaration> fieldEnumMap= new HashMap<ProtocolState, EnumConstantDeclaration>();
			int index= 0;
			for (ProtocolState protocolState : states) {
				EnumConstantDeclaration createEnumConstant= JMHelper.createEnumConstant(stateEnum,
						"State" + "_" + index + "_" + protocolState.getName());
				fieldEnumMap.put(protocolState, createEnumConstant);
				index++;
			}

			// current state variable
			FieldDeclaration enumField= JMHelper.createField("private", "partner_state_" + service.getName(), stateEnum);
			JMHelper.addField(fSimClass, enumField);

			// some queues
			FieldDeclaration receivequeue= JMHelper.createField("private", "queue_" + service.getName(), fArrayBlckQueueString);
			JMHelper.addField(fSimClass, receivequeue);
			JMHelper.ensureImport(fSimUnit, fArrayBlckQueue);
			ClassInstanceCreation createTheArrayQueue= JMHelper.createConstructorInvocation(fArrayBlckQueueString, JMHelper.createNumberLiteral(42));
			receivequeue.getFragments().get(0).setInitializer(createTheArrayQueue);
			fPartnerQueueFields.put(service, receivequeue);

			// runnable fields with an empty run method (filled later)
			FieldDeclaration partnerField= JMHelper.createField("private", "partner_" + service.getName(), fRunnable);
			JMHelper.addField(fSimClass, partnerField);
			fPartnerRunnableFields.put(service, partnerField);
			MethodDeclaration runMethod= JMHelper.createMethod("public", fTypeTracker.getType("void"), "run");
			ClassInstanceCreation partnerFieldRunnable= JMHelper.createAnonymousClassCreation(fRunnable, runMethod);
			partnerField.getFragments().get(0).setInitializer(partnerFieldRunnable);

			/******************************************************************************************
			 * CREATE RECEIVING METHODS
			 ******************************************************************************************/

			Partner requiredIf= fPartners.getPartner(PartnerTracker.Type.REQUIRED, service);

			FieldDeclaration fieldDec= JMHelper.createField("private", "receive_" + requiredIf.getPartnerName(), requiredIf.getInterface());
			JMHelper.ensureImport(fSimUnit, requiredIf.getInterface());
			JMHelper.addField(fSimClass, fieldDec);

			fPartnerImplementationFields.put(service, fieldDec);

			// Create all methods.
			List<MethodDeclaration> methods= new ArrayList<MethodDeclaration>();
			List<InterfaceOperation> operations= requiredIf.getOperations();
			for (InterfaceOperation interfaceOperation : operations) {
				MethodDeclaration m= createReceivingMethod(service, interfaceOperation);
				methods.add(m);
			}

			ClassInstanceCreation cic= JMHelper.createAnonymousClassCreation(requiredIf.getInterface(), methods.toArray(new MethodDeclaration[0]));
			fieldDec.getFragments().get(0).setInitializer(cic);


			/******************************************************************************************
			 * CREATE SENDING METHODS
			 ******************************************************************************************/

			EList<InterfaceOperation> toUse= null;
			if (service instanceof ProvidedService)
				toUse= service.getInterface().getImplemented();
			if (service instanceof RequiredService)
				toUse= service.getInterface().getUsed();

			for (InterfaceOperation ifOp : toUse) {
				MethodDeclaration m= createSendingMethod(service, ifOp);
				fSimClass.getBodyDeclarations().add(m);
				fSendMethods.put(ifOp, m);
				System.out.println("Putting if operation " + ifOp + " into sendMethods.");
			}

			/******************************************************************************************
			 * CREATE RUNNABLE BODIES
			 ******************************************************************************************/

			// Now w really begin.
			runMethod.getBody().getStatements().add(logInfo(EVENT_PARTNERSTARTUP, service.getName(), ""));

			// set first state.
			ExpressionStatement setFirstState= JMHelper.createAssignmentToFieldWOThis(enumField,
					JMHelper.createEnumAccess(stateEnum, fieldEnumMap.get(states.get(0))));
			runMethod.getBody().getStatements().add(setFirstState);

			WhileStatement theOuterWhile= JavaFactory.eINSTANCE.createWhileStatement();
			theOuterWhile.setExpression(JMHelper.createBooleanLiteral(true));
			runMethod.getBody().getStatements().add(theOuterWhile);

			// "da switch"
			SwitchStatement sw= JavaFactory.eINSTANCE.createSwitchStatement();
			theOuterWhile.setBody(sw);
			sw.setExpression(JMHelper.createFieldAccess(enumField));

			for (ProtocolState protocolState : states) {

				/******************************************************************************************
				 * FOR EACH STATE OF THE SERVICE
				 ******************************************************************************************/

				SwitchCase theCase= JavaFactory.eINSTANCE.createSwitchCase();
				SingleVariableAccess accX= JavaFactory.eINSTANCE.createSingleVariableAccess();
				accX.setVariable(fieldEnumMap.get(protocolState));
				theCase.setExpression(accX);
				sw.getStatements().add(theCase);

				// What kind of state is this? Send, Receive, or both possible?
				List<ProtocolTransition> outgoings= getOutgoings(protocolState);
				if (outgoings.size() > 0) {

					// Special cases.

					// 1) If the only outgoing link is a receivereply link, we
					// ignore this state -- we have handled the send-back as
					// part of the receive.

					// 2) If the only outgoing link is a reply link, we ignore
					// this state, too -- because we are getting the reply
					// directly (not via another receive)

					if ( (outgoings.size() == 1) && (outgoings.get(0) instanceof ReceiveReplyingTransition)
							|| (outgoings.get(0) instanceof ReplyingTransition)) {

						// We directly move to the target.
						ExpressionStatement nextState= JMHelper.createAssignmentToFieldWOThis(enumField,
								JMHelper.createEnumAccess(stateEnum, fieldEnumMap.get(outgoings.get(0).getTarget())));
						sw.getStatements().add(nextState);

						sw.getStatements().add(JavaFactory.eINSTANCE.createBreakStatement());
						continue;
					}

					// 3) This might be a "possible" end state, meaning that all
					// following outgoing or incoming transitions are optional.
					// We report this. Also, there is a timeout later.
					boolean isMayComplete= hasOnlyMayOuts(outgoings);
					if (isMayComplete) {
						sw.getStatements().add(logInfo(EVENT_PARTNERMAYCOMPLETE, service.getName(), ""));
					}

					// Wait a bit (random)
					sw.getStatements().add(JMHelper.createMethodInvocationStatement(JMHelper.createTypeAccessTo(fSimHelper), fSimHelperWaitMethod));


					List<ProtocolTransition> sends= getSends(service, outgoings);
					List<ProtocolTransition> receives= getReceives(service, outgoings);

					if (sends.size() > 0 && receives.size() == 0) {
						sw.getStatements().addAll(createMultipleSendingStatements(stateEnum, fieldEnumMap, enumField, sends));
					} else {
						// wait for receive, and maybe do a send.
						// Idea: Use a queue per partner, waiting for incoming
						// calls. This ensures that we do not miss an incoming
						// call and we detect if there is something in the queue
						// which should not be there! (One queue per state would
						// not permit this)

						// receive takes precedence?

						/**
						 * <pre>
						 * 
						 * while (receiveQeue.peek() == null)) {
						 *  
						 *   // if also send:
						 *   int randomWeDoSomeThingElse= XUtil.getRandomNumber(10);
						 *   if (randomWeDoSomeThingElse < 2) {
						 *     // perform a send
						 *     // move to next
						 *     // break;
						 *   }
						 *   sleep(some milliseconds);
						 * 
						 * }
						 * 
						 * Msg= receiveQueue.get();
						 * // handle the receive.
						 * 
						 * </pre>
						 */

						// Sends? Use a while

						// Use a timer to decide when it is too late.
						VariableDeclarationStatement timer= createLocalVarDefinitionAndAssignment("timer_" + protocolState.getName(),
								fTypeTracker.getType("int"), JMHelper.createNumberLiteral(0));
						sw.getStatements().add(timer);

						VariableDeclarationStatement sent= null;
						if (sends.size() > 0) {
							// Use a variable to decide we have already sent
							// something.
							sent= createLocalVarDefinitionAndAssignment("sent_" + protocolState.getName(), fTypeTracker.getType("boolean"),
									JMHelper.createBooleanLiteral(false));
							sw.getStatements().add(sent);
						}

						WhileStatement theWhile= JavaFactory.eINSTANCE.createWhileStatement();

						Expression isQueueEmpty= JMHelper.createInfixExpression(InfixExpressionKind.EQUALS,
								JMHelper.createMethodInvocation(JMHelper.createFieldAccess(receivequeue), fArrayBlckQueuePeekMethod),
								JMHelper.createNullLiteral());

						Expression timeOut= JMHelper.createInfixExpression(InfixExpressionKind.LESS_EQUALS,
								JMHelper.createLocalVariableAccess(timer.getFragments().get(0)), JMHelper.createNumberLiteral(5000));

						Expression expression= JMHelper.createInfixExpression(InfixExpressionKind.CONDITIONAL_AND, isQueueEmpty, timeOut);

						theWhile.setExpression(expression);

						Block inWhile= JavaFactory.eINSTANCE.createBlock();
						theWhile.setBody(inWhile);

						if (sends.size() > 0) {

							// Check if we want to do something maybe?
							VariableDeclarationStatement decSt= createRandomAssignment("doSendSomething", 2);
							inWhile.getStatements().add(decSt);

							IfStatement ifSend= JavaFactory.eINSTANCE.createIfStatement();

							Expression expr= JMHelper.createInfixExpression(InfixExpressionKind.EQUALS,
									JMHelper.createLocalVariableAccess(decSt.getFragments().get(0)), JMHelper.createNumberLiteral(0));
							ifSend.setExpression(expr);

							Block thenBlock= JavaFactory.eINSTANCE.createBlock();
							ifSend.setThenStatement(thenBlock);

							// Yes, we have sent something.
							Assignment assignToSent= JavaFactory.eINSTANCE.createAssignment();
							assignToSent.setOperator(AssignmentKind.ASSIGN);
							assignToSent.setLeftHandSide(JMHelper.createLocalVariableAccess(sent.getFragments().get(0)));
							assignToSent.setRightHandSide(JMHelper.createBooleanLiteral(true));
							thenBlock.getStatements().add(JMHelper.createExpressionStatemenet(assignToSent));

							thenBlock.getStatements().addAll(createMultipleSendingStatements(stateEnum, fieldEnumMap, enumField, sends));

							inWhile.getStatements().add(ifSend);
						}

						// Sleep a bit in loop. Do this AT THE END OF THE LOOP;
						// NOT BETWEEN
						// "is there a message for me?" and
						// "hey, I could send something as there is none!"
						Assignment assignToTimer= JavaFactory.eINSTANCE.createAssignment();
						assignToTimer.setOperator(AssignmentKind.PLUS_ASSIGN);
						assignToTimer.setLeftHandSide(JMHelper.createLocalVariableAccess(timer.getFragments().get(0)));
						assignToTimer
								.setRightHandSide(JMHelper.createMethodInvocation(JMHelper.createTypeAccessTo(fSimHelper), fSimHelperWaitMethod));
						inWhile.getStatements().add(JMHelper.createExpressionStatemenet(assignToTimer));

						sw.getStatements().add(theWhile);

						// timeout?
						IfStatement wasTimeOut= JavaFactory.eINSTANCE.createIfStatement();
						wasTimeOut.setExpression(JMHelper.createInfixExpression(InfixExpressionKind.GREATER,
								JMHelper.createLocalVariableAccess(timer.getFragments().get(0)), JMHelper.createNumberLiteral(5000)));
						Block timeOutBlock= JavaFactory.eINSTANCE.createBlock();
						wasTimeOut.setThenStatement(timeOutBlock);
						timeOutBlock.getStatements().add(logInfo(EVENT_TIMEOUT, service.getName(), ""));
						timeOutBlock.getStatements().add(JavaFactory.eINSTANCE.createReturnStatement());
						sw.getStatements().add(wasTimeOut);

						// If we have sent something, do NOT wait for a receive
						// in the same state (we can go back through the outer
						// while and THEN wait again if necessary and the send
						// really leads back)
						if (sends.size() > 0) {
							IfStatement wasSent= JavaFactory.eINSTANCE.createIfStatement();
							wasSent.setExpression(JMHelper.createLocalVariableAccess(sent.getFragments().get(0)));
							wasSent.setThenStatement(JavaFactory.eINSTANCE.createBreakStatement());
							sw.getStatements().add(wasSent);
						}

						/**
						 * Now -- whether send or not -- we go forward to
						 * receiving.
						 * 
						 * <pre>
						 * if (firstReceiveName.equals(msgReceived) {
						 *   // happily move forward
						 *   break;
						 * if (second...)
						 *   // move forward
						 *   break;
						 * 
						 * THROW SOMETHING
						 * 
						 */

						MethodInvocation mInv= JMHelper.createMethodInvocation(JMHelper.createFieldAccess(receivequeue), fArrayBlckQueuePollMethod);
						VariableDeclarationStatement pollmsg= createLocalVarDefinitionAndAssignment("msgReceived_" + protocolState.getName(),
								fTypeTracker.getType("java.lang.String"), mInv);
						sw.getStatements().add(pollmsg);

						for (ProtocolTransition possibleReceive : receives) {

							IfStatement isThisReceive= JavaFactory.eINSTANCE.createIfStatement();
							isThisReceive.setExpression(JMHelper.createMethodInvocation(
									JMHelper.createStringLiteral(possibleReceive.getOperation().getName()), fStringEqualsMethod,
									JMHelper.createLocalVariableAccess(pollmsg.getFragments().get(0))));

							Block block= JavaFactory.eINSTANCE.createBlock();
							isThisReceive.setThenStatement(block);

							// Move to the next state.
							block.getStatements().add(
									JMHelper.createAssignmentToFieldWOThis(enumField,
											JMHelper.createEnumAccess(stateEnum, fieldEnumMap.get(possibleReceive.getTarget()))));

							// and break.
							block.getStatements().add(JavaFactory.eINSTANCE.createBreakStatement());

							sw.getStatements().add(isThisReceive);
						}

						// and the else

						sw.getStatements().add(
								logError(EVENT_PROBLEMINRECEIVE, service.getName(), protocolState.getName(),
										JMHelper.createLocalVariableAccess(pollmsg.getFragments().get(0))));
						sw.getStatements().add(JavaFactory.eINSTANCE.createBreakStatement());

					}

				} else {
					// outgoings.size() == 0
					// reached an end state
					sw.getStatements().add(logInfo(EVENT_PARTNERCOMPLETION, service.getName(), ""));
					sw.getStatements().add(JavaFactory.eINSTANCE.createReturnStatement());
				}
			}

		} // end loop over all partners

		/******************************************************************************************
		 * START METHOD
		 ******************************************************************************************/

		// And finally, the start method.
		MethodDeclaration runMethod= JMHelper.createMethod("private", fTypeTracker.getType("void"), "start");
		fSimClass.getBodyDeclarations().add(runMethod);


		// Create process under test
		MethodDeclaration startMethod= null;
		EList<BodyDeclaration> bodyDeclarations2= ((AbstractTypeDeclaration) fPUTClass).getBodyDeclarations();
		for (BodyDeclaration bodyDeclaration : bodyDeclarations2) {
			if ( (bodyDeclaration instanceof MethodDeclaration) && ((MethodDeclaration) bodyDeclaration).getName().equals("start"))
				startMethod= (MethodDeclaration) bodyDeclaration;
		}

		if (startMethod == null)
			throw new TransformationException("Could not find start() method in ServiceActivity class to be simulated");

		List<SingleVariableAccess> lss= new ArrayList<SingleVariableAccess>();

		for (Service sv : fParticipant.getPartners()) {
			FieldDeclaration fieldDeclaration= fPartnerImplementationFields.get(sv);
			SingleVariableAccess fieldAcc= JMHelper.createFieldAccess(fieldDeclaration);
			lss.add(fieldAcc);
		}

		ClassInstanceCreation cons= JMHelper.createConstructorInvocation(fPUTClass, lss.toArray(new SingleVariableAccess[0]));
		ExpressionStatement varSetupField= JMHelper.createAssignmentToField(fPUTClassField, cons);
		runMethod.getBody().getStatements().add(varSetupField);


		// Start the main process.
		runMethod.getBody().getStatements().add(JMHelper.createMethodInvocationStatement(JMHelper.createFieldAccess(fPUTClassField), startMethod));


		// Create the threads
		Type threadType= fTypeTracker.getType("java.lang.Thread");
		MethodDeclaration threadStartMethod= null;
		EList<BodyDeclaration> bodyDeclarations= ((AbstractTypeDeclaration) threadType).getBodyDeclarations();
		for (BodyDeclaration bodyDeclaration : bodyDeclarations) {
			if ( (bodyDeclaration instanceof MethodDeclaration) && ((MethodDeclaration) bodyDeclaration).getName().equals("start")) {
				threadStartMethod= (MethodDeclaration) bodyDeclaration;
				break;
			}
		}

		if (threadStartMethod == null)
			throw new TransformationException("Could not find Thread.start() method Declaration");


		// Start a thread for each partner.
		for (Service service : fParticipant.getPartners()) {
			ClassInstanceCreation constr= JMHelper.createConstructorInvocation(threadType,
					JMHelper.createFieldAccess(fPartnerRunnableFields.get(service)));
			ExpressionStatement st= JMHelper.createMethodInvocationStatement(constr, threadStartMethod);

			runMethod.getBody().getStatements().add(st);
		}

		// public static void main(String[] argh)

		ArrayType arrayType= JavaFactory.eINSTANCE.createArrayType();
		arrayType.setElementType(JMHelper.createTypeAccessTo(fTypeTracker.getType("java.lang.String")));
		arrayType.setDimensions(1);
		fModel.getOrphanTypes().add(arrayType);

		MethodDeclaration publicstaticvoidmain= JMHelper.createMethod("public static", "main");
		SingleVariableDeclaration argh= JMHelper.createLocalVariable(arrayType, "argh");
		publicstaticvoidmain.getParameters().add(argh);

		ClassInstanceCreation constr= JMHelper.createConstructorInvocation(fSimClass);
		ExpressionStatement st= JMHelper.createMethodInvocationStatement(constr, startMethod);
		publicstaticvoidmain.getBody().getStatements().add(st);

		fSimClass.getBodyDeclarations().add(publicstaticvoidmain);

	}

	private boolean hasOnlyMayOuts(List<ProtocolTransition> outgoings) {

		for (ProtocolTransition protocolTransition : outgoings) {
			if (!protocolTransition.isIsOptional())
				return false;
		}
		return outgoings.size() > 0;
	}

	private Statement logInfo(String event, String partner, String message) {

		String complete= event + ":" + partner + ":" + message;
		return JMHelper.createMethodInvocationStatement(JMHelper.createTypeAccessTo(fSimHelper), fSimHelperLogMethod,
				JMHelper.createStringLiteral(complete));
	}

	private Statement logError(String event, String partner, String message, Expression expressionToLog) {

		String complete= event + ":" + partner + ":" + message + ":";

		Expression msg= JMHelper.createInfixExpression(InfixExpressionKind.PLUS, JMHelper.createStringLiteral(complete), expressionToLog);
		return JMHelper.createMethodInvocationStatement(JMHelper.createTypeAccessTo(fSimHelper), fSimHelperLogMethod, msg);
	}

	private List<Statement> createMultipleSendingStatements(EnumDeclaration stateEnum, Map<ProtocolState, EnumConstantDeclaration> fieldEnumMap,
			FieldDeclaration enumField, List<ProtocolTransition> sends) {

		List<Statement> stmnts= new ArrayList<Statement>();

		if (sends.size() == 1) {
			stmnts.addAll(createSendingStatements(stateEnum, enumField, fieldEnumMap, sends.get(0)));
		} else {

			// random choice
			VariableDeclarationStatement decSt= createRandomAssignment("rnd", sends.size());
			stmnts.add(decSt);

			// create a switch.
			SwitchStatement randomSendSwitch= JavaFactory.eINSTANCE.createSwitchStatement();
			stmnts.add(randomSendSwitch);
			randomSendSwitch.setExpression(JMHelper.createLocalVariableAccess(decSt.getFragments().get(0)));
			for (int i= 0; i < sends.size(); i++) {
				SwitchCase randomCase= JavaFactory.eINSTANCE.createSwitchCase();
				randomCase.setExpression(JMHelper.createNumberLiteral(i));
				randomSendSwitch.getStatements().add(randomCase);

				randomSendSwitch.getStatements().addAll(createSendingStatements(stateEnum, enumField, fieldEnumMap, sends.get(i)));
			}
			stmnts.add(JavaFactory.eINSTANCE.createBreakStatement());
		}

		return stmnts;
	}

	private List<Statement> createSendingStatements(EnumDeclaration stateEnum, FieldDeclaration enumField,
			Map<ProtocolState, EnumConstantDeclaration> fieldEnumMap, ProtocolTransition currentTransition) {
		List<Statement> statementsToAdd2= new ArrayList<Statement>();
		// call it:
		statementsToAdd2.add(JMHelper.createMethodInvocationStatement(fSendMethods.get(currentTransition.getOperation())));
		// next state:
		ExpressionStatement nextState= JMHelper.createAssignmentToFieldWOThis(enumField,
				JMHelper.createEnumAccess(stateEnum, fieldEnumMap.get(currentTransition.getTarget())));
		statementsToAdd2.add(nextState);
		// break:
		statementsToAdd2.add(JavaFactory.eINSTANCE.createBreakStatement());
		return statementsToAdd2;
	}

	private VariableDeclarationStatement createRandomAssignment(String nameOfVar, int maxExclusiveInt) {

		MethodInvocation mInv= JMHelper.createMethodInvocation(JMHelper.createTypeAccessTo(fSimHelper), fSimHelperRandomMethod);
		mInv.getArguments().add(JMHelper.createNumberLiteral(maxExclusiveInt));
		VariableDeclarationStatement decSt= JavaFactory.eINSTANCE.createVariableDeclarationStatement();
		decSt.setType(JMHelper.createTypeAccessTo(fTypeTracker.getType("int")));
		VariableDeclarationFragment de= JavaFactory.eINSTANCE.createVariableDeclarationFragment();
		de.setName(nameOfVar);
		de.setInitializer(mInv);
		decSt.getFragments().add(de);

		return decSt;
	}

	private VariableDeclarationStatement createLocalVarDefinitionAndAssignment(String nameOfVar, Type type, Expression assignment) {

		VariableDeclarationStatement decSt= JavaFactory.eINSTANCE.createVariableDeclarationStatement();
		decSt.setType(JMHelper.createTypeAccessTo(type));
		VariableDeclarationFragment de= JavaFactory.eINSTANCE.createVariableDeclarationFragment();
		de.setName(nameOfVar);
		de.setInitializer(assignment);
		decSt.getFragments().add(de);

		return decSt;
	}

	private List<ProtocolTransition> getSends(Service partner, List<ProtocolTransition> outgoings) {

		List<ProtocolTransition> sends= new ArrayList<ProtocolTransition>();
		for (ProtocolTransition protocolTransition : outgoings)
			if (protocolTransition instanceof ReceivingTransition || protocolTransition instanceof ReceiveReplyingTransition)
				sends.add(protocolTransition);

		return sends;
	}

	private List<ProtocolTransition> getReceives(Service partner, List<ProtocolTransition> outgoings) {

		List<ProtocolTransition> rcvs= new ArrayList<ProtocolTransition>();
		for (ProtocolTransition protocolTransition : outgoings)
			if (protocolTransition instanceof SendingTransition || protocolTransition instanceof ReplyingTransition)
				rcvs.add(protocolTransition);

		return rcvs;
	}

	private List<ProtocolTransition> getOutgoings(ProtocolState protocolState) {

		List<ProtocolTransition> outgoings= new ArrayList<ProtocolTransition>();

		EList<ProtocolTransition> transitions= protocolState.getProtocol().getTransitions();
		for (ProtocolTransition protocolTransition : transitions) {
			if (protocolTransition.getSource().equals(protocolState))
				outgoings.add(protocolTransition);
		}

		return outgoings;
	}


	private MethodDeclaration createSendingMethod(Service service, InterfaceOperation interfaceOperation) throws TransformationException {

		MethodDeclaration d= JavaFactory.eINSTANCE.createMethodDeclaration();
		d.setName("send_" + interfaceOperation.getName());
		d.setModifier(JMHelper.createModifier("public"));

		d.setBody(JavaFactory.eINSTANCE.createBlock());

		// Actually call the method:

		// Do not bother with any parameters. We don't have them anyway.
		List<Expression> params= new ArrayList<Expression>();
		EList<InterfaceParameter> parameters= interfaceOperation.getParameters();
		for (InterfaceParameter interfaceParameter : parameters) {

			if (interfaceParameter instanceof InParameter) {
				SMMType type= interfaceParameter.getType();
				if (type instanceof eu.mdd4soa.smm.statik.PrimitiveType) {
					if (type instanceof BooleanType)
						params.add(JMHelper.createNullLiteral());
					else if (type instanceof StringType)
						params.add(JMHelper.createStringLiteral(""));
					else
						params.add(JMHelper.createNumberLiteral(0));
				} else {
					params.add(JMHelper.createNullLiteral());
				}
			} else {
				// There is a return type here. We ignore it.
				// Note that this is means that the send is in fact a
				// send&receive in the original orchestration.
			}
		}

		// Where is the method?
		MethodDeclaration removeInvoc= JMHelper.findMethod(fPUTClass, interfaceOperation.getName());
		if (removeInvoc == null)
			throw new TransformationException("Could not find method in interface for method " + interfaceOperation.getName() + " on partner "
					+ service.getName());


		TryStatement trySt= JavaFactory.eINSTANCE.createTryStatement();

		CatchClause cc= JavaFactory.eINSTANCE.createCatchClause();
		SingleVariableDeclaration exception= JMHelper.createLocalVariable(fServiceExceptionType, "e");
		cc.setException(exception);
		cc.setBody(JavaFactory.eINSTANCE.createBlock());

		cc.getBody()
				.getStatements()
				.add(logError(EVENT_PROBLEMINSEND, service.getName(), interfaceOperation.getName(),
						JMHelper.createMethodInvocation(JMHelper.createLocalVariableAccess(exception), fExceptionGetMessageMethod)));

		trySt.getCatchClauses().add(cc);
		trySt.setBody(JavaFactory.eINSTANCE.createBlock());


		trySt.getBody()
				.getStatements()
				.add(JMHelper.createMethodInvocationStatement(JMHelper.createFieldAccess(fPUTClassField), removeInvoc,
						params.toArray(new Expression[0])));

		// Log the send.
		// trySt.getBody().getStatements().add(logInfo(EVENT_MSG_SEND,
		// service.getName(), interfaceOperation.getName()));
		//
		// // Send&Receive? Log the "receivereply".
		// if (removeInvoc.getReturnType() != null)
		// trySt.getBody().getStatements().add(logInfo(EVENT_MSG_RECEIVED,
		// service.getName(), "return_" + interfaceOperation.getName()));

		d.getBody().getStatements().add(trySt);

		return d;
	}


	private MethodDeclaration createReceivingMethod(Service service, InterfaceOperation interfaceOperation) {

		MethodDeclaration d= JavaFactory.eINSTANCE.createMethodDeclaration();
		d.setName(interfaceOperation.getName());
		d.setModifier(JMHelper.createModifier("public"));

		d.getThrownExceptions().add(JMHelper.createTypeAccessTo(fServiceExceptionType));

		EList<InterfaceParameter> parameters= interfaceOperation.getParameters();
		for (InterfaceParameter interfaceParameter : parameters) {

			org.eclipse.gmt.modisco.java.Type parameterType= fTypeTracker.resolveTypeWithMultiplicityAndImports(fSimUnit, interfaceParameter);

			if (interfaceParameter instanceof InParameter) {
				// Input
				d.getParameters().add(JMHelper.createLocalVariable(parameterType, interfaceParameter.getName()));
			} else {
				// Output
				d.setReturnType(JMHelper.createTypeAccessTo(parameterType));
			}
		}

		d.setBody(JavaFactory.eINSTANCE.createBlock());

		// // Now add a trace statement.
		// Statement logMessage= logInfo(EVENT_MSG_RECEIVED, service.getName(),
		// interfaceOperation.getName());
		// d.getBody().getStatements().add(logMessage);
		//
		// // And a trace in case we need a return (reply transition is next)
		// if (d.getReturnType() != null) {
		// d.getBody().getStatements().add(logInfo(EVENT_MSG_SEND,
		// service.getName(), "return_" + interfaceOperation.getName()));
		// }

		// Add received msg to queue.
		FieldDeclaration queueField= fPartnerQueueFields.get(service);
		ExpressionStatement addToQueue= JMHelper.createMethodInvocationStatement(JMHelper.createFieldAccess(queueField), fArrayBlckQueueAddMethod,
				JMHelper.createStringLiteral(interfaceOperation.getName()));
		d.getBody().getStatements().add(addToQueue);


		// And a return statement... (TODO uh-oh. need to configure something
		// here? Parameterized simulation runs with different data?)
		if (d.getReturnType() != null) {

			ReturnStatement returnStatement= JavaFactory.eINSTANCE.createReturnStatement();

			if (d.getReturnType().getType() instanceof PrimitiveType) {
				if (d.getReturnType().getType() instanceof PrimitiveTypeBoolean)
					returnStatement.setExpression(JMHelper.createBooleanLiteral(true));
				else if (d.getReturnType().getType() instanceof PrimitiveTypeChar)
					returnStatement.setExpression(JMHelper.createCharacterLiteral("E"));
				else
					returnStatement.setExpression(JMHelper.createNumberLiteral(0));

			} else {
				// This should be a message type. Try to create one.
				ClassInstanceCreation createMsgType= JMHelper.createConstructorInvocation(d.getReturnType().getType());
				returnStatement.setExpression(createMsgType);
			}

			d.getBody().getStatements().add(returnStatement);
		}


		return d;
	}


}
