/*
 * 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 org.eclipse.emf.common.util.EList;
import org.eclipse.uml2.uml.CallEvent;
import org.eclipse.uml2.uml.Class;
import org.eclipse.uml2.uml.Event;
import org.eclipse.uml2.uml.FinalState;
import org.eclipse.uml2.uml.Operation;
import org.eclipse.uml2.uml.ProtocolStateMachine;
import org.eclipse.uml2.uml.Pseudostate;
import org.eclipse.uml2.uml.PseudostateKind;
import org.eclipse.uml2.uml.ReceiveOperationEvent;
import org.eclipse.uml2.uml.Region;
import org.eclipse.uml2.uml.SendOperationEvent;
import org.eclipse.uml2.uml.Stereotype;
import org.eclipse.uml2.uml.Trigger;
import org.eclipse.uml2.uml.Vertex;

import eu.mdd4soa.smm.behaviour.ISMBehaviourFactory;
import eu.mdd4soa.smm.behaviour.NoopTransition;
import eu.mdd4soa.smm.behaviour.ProtocolState;
import eu.mdd4soa.smm.behaviour.ProtocolTransition;
import eu.mdd4soa.smm.behaviour.ServiceProtocol;
import eu.mdd4soa.smm.exception.TransformationException;
import eu.mdd4soa.smm.statik.InterfaceOperation;
import eu.mdd4soa.smm.statik.Participant;
import eu.mdd4soa.smm.statik.Service;

public class ProtocolConverter {

	public static ServiceProtocol convert(Participant ismParticipant, Class portType, ProtocolStateMachine protocolStateMachine)
			throws TransformationException {

		ProtocolConverter c= new ProtocolConverter(ismParticipant, portType, protocolStateMachine);
		c.convert();
		return c.getResult();
	}

	private ServiceProtocol fResultingProtocol;

	private Participant fParticipant;

	private ProtocolStateMachine fProtocol;

	private Map<Vertex, ProtocolState> fStateMap= new HashMap<Vertex, ProtocolState>();

	private Class fPortType;

	private Service fService;

	public ProtocolConverter(Participant ismParticipant, Class portType, ProtocolStateMachine protocolStateMachine) {
		fParticipant= ismParticipant;
		fPortType= portType;
		fProtocol= protocolStateMachine;
	}

	private ServiceProtocol getResult() {
		return fResultingProtocol;
	}


	private void convert() throws TransformationException {

		fService= null;

		String names= "";
		EList<Service> partners= fParticipant.getPartners();
		for (Service service : partners) {
			names+= " " + service.getInterface().getName();
			if (service.getInterface().getName().equals(fPortType.getName())) {
				fService= service;
				break;
			}
		}

		if (fService == null)
			throw new TransformationException("Could not find a maching SMM service for the port Type " + fPortType.getName()
					+ ". Available names are: " + names);


		EList<Region> regions= fProtocol.getRegions();
		if (regions == null || regions.size() != 1)
			throw new TransformationException("Need one region as child of protocol state machine " + fProtocol.getName());

		Region base= regions.get(0);

		EList<Vertex> subvertices= base.getSubvertices();

		Pseudostate initialVertex= getInitial(subvertices);
		if (initialVertex == null)
			throw new TransformationException("Need an initial node in protocol state machine " + fProtocol.getName());

		if (initialVertex.getOutgoings().size() > 1)
			throw new TransformationException("Initial node in protocol state machine + " + fProtocol.getName() + " may only have one outgoing link.");

		Vertex firstRealState= initialVertex.getOutgoings().get(0).getTarget();

		fResultingProtocol= ISMBehaviourFactory.eINSTANCE.createServiceProtocol();
		fResultingProtocol.setName( ("Protocol" + fProtocol.getName()));

		ProtocolState firstRealServiceProtocolState= createState(firstRealState.getName());
		fResultingProtocol.getStates().add(firstRealServiceProtocolState);
		fResultingProtocol.setStartState(firstRealServiceProtocolState);
		fStateMap.put(firstRealState, firstRealServiceProtocolState);

		goFigure(firstRealState);

		// Add to the service.
		fService.setProtocol(fResultingProtocol);

	}

	private void goFigure(Vertex vertex) throws TransformationException {

		ProtocolState from= fStateMap.get(vertex);

		EList<org.eclipse.uml2.uml.Transition> outgoings= vertex.getOutgoings();
		for (org.eclipse.uml2.uml.Transition transition : outgoings) {
			Vertex target= transition.getTarget();

			boolean isNew= !fStateMap.containsKey(target);
			ProtocolState ourNewState= null;
			if (fStateMap.containsKey(target))
				ourNewState= fStateMap.get(target);
			else {
				String label= null;
				if (target instanceof FinalState)
					label= "End";
				else
					label= target.getLabel();
				ourNewState= createState(label);
				fResultingProtocol.getStates().add(ourNewState);
				fStateMap.put(target, ourNewState);
			}

			ProtocolTransition t= null;

			if (isSend(transition)) {
				t= ISMBehaviourFactory.eINSTANCE.createSendingTransition();
			} else if (isReply(transition)) {
				t= ISMBehaviourFactory.eINSTANCE.createReplyingTransition();
			} else if (isRcvReply(transition)) {
				t= ISMBehaviourFactory.eINSTANCE.createReceiveReplyingTransition();
			} else if (isReceive(transition)) {
				t= ISMBehaviourFactory.eINSTANCE.createReceivingTransition();
			} else
				t= ISMBehaviourFactory.eINSTANCE.createNoopTransition();

			t.setIsOptional(isMay(transition));
			t.setSource(from);
			t.setTarget(ourNewState);


			if (! (t instanceof NoopTransition)) {
				t.setOperation(resolveOperationFromTransition(transition));
			}

			fResultingProtocol.getTransitions().add(t);

			if (isNew)
				goFigure(target);
		}

	}

	private InterfaceOperation resolveOperationFromTransition(org.eclipse.uml2.uml.Transition transition) throws TransformationException {

		// The operation.
		// TODO for now, we do a name matching. Not that great, but hey.

		Operation op= getOperation(transition);

		List<InterfaceOperation> ifOps= new ArrayList<InterfaceOperation>();
		ifOps.addAll(fService.getInterface().getImplemented());
		ifOps.addAll(fService.getInterface().getUsed());

		InterfaceOperation foundOperation= null;

		for (InterfaceOperation interfaceOperation : ifOps) {
			if (interfaceOperation.getName().equals(op.getName())) {
				foundOperation= interfaceOperation;
				break;
			}
		}

		if (foundOperation == null)
			throw new TransformationException("Could not find a matching SMM interface operation in service " + fService
					+ ". Was trying to find operation " + op.getName());
		return foundOperation;
	}

	private Operation getOperation(org.eclipse.uml2.uml.Transition transition) throws TransformationException {

		EList<Trigger> triggers= transition.getTriggers();
		if (triggers.size() != 1)
			throw new TransformationException("Got a receive, reply, or send transition without a trigger, or with too many triggers: " + transition
					+ " triggers: " + triggers);

		Trigger trigger= triggers.get(0);
		Event ev= trigger.getEvent();

		if (ev instanceof CallEvent)
			return ((CallEvent) ev).getOperation();
		if (ev instanceof ReceiveOperationEvent)
			return ((ReceiveOperationEvent) ev).getOperation();
		if (ev instanceof SendOperationEvent)
			return ((SendOperationEvent) ev).getOperation();

		throw new TransformationException("Got a receive, reply, or send transition with an invalid event: " + ev);
	}

	private boolean isReceive(org.eclipse.uml2.uml.Transition transition) {
		return hasStereotype(transition, "receive");
	}

	private boolean isReply(org.eclipse.uml2.uml.Transition transition) {
		return hasStereotype(transition, "reply");
	}

	private boolean isRcvReply(org.eclipse.uml2.uml.Transition transition) {
		return hasStereotype(transition, "receivereply");
	}

	private boolean isSend(org.eclipse.uml2.uml.Transition transition) {
		return hasStereotype(transition, "send");
	}

	private boolean isMay(org.eclipse.uml2.uml.Transition transition) {
		return hasStereotype(transition, "optional");
	}

	private boolean hasStereotype(org.eclipse.uml2.uml.Transition transition, String stereotypeLabel) {
		EList<Stereotype> appliedStereotypes= transition.getAppliedStereotypes();
		for (Stereotype stereotype : appliedStereotypes) {
			if (stereotype.getLabel().equalsIgnoreCase(stereotypeLabel))
				return true;
		}
		return false;
	}

	private Pseudostate getInitial(EList<Vertex> subvertices) {
		for (Vertex vertex : subvertices) {
			if (vertex instanceof Pseudostate && ((Pseudostate) vertex).getKind().equals(PseudostateKind.INITIAL_LITERAL))
				return (Pseudostate) vertex;
		}
		return null;
	}

	public ProtocolState createState(String name) {
		ProtocolState state= ISMBehaviourFactory.eINSTANCE.createProtocolState();
		state.setName(name);
		return state;
	}

}
