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

import java.util.HashMap;
import java.util.Map;

import net.miowb.model.mio.Action;
import net.miowb.model.mio.InputAction;
import net.miowb.model.mio.InternalAction;
import net.miowb.model.mio.MayTransition;
import net.miowb.model.mio.MioFactory;
import net.miowb.model.mio.ModalIOAutomaton;
import net.miowb.model.mio.MustTransition;
import net.miowb.model.mio.OutputAction;
import net.miowb.model.mio.State;
import net.miowb.model.mio.Transition;

import org.eclipse.emf.common.util.EList;
import org.eclipse.uml2.uml.CallEvent;
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.uml4soa.utbm.u2m.SemanticsException;


public class ProtocolSemanticsFunction {

	private ProtocolStateMachine fProtocol;

	private ModalIOAutomaton fSemantics;

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

	public ProtocolSemanticsFunction(ProtocolStateMachine psm) {
		fProtocol= psm;
	}

	public void calculateSemantics() throws SemanticsException {

		EList<Region> regions= fProtocol.getRegions();
		if (regions == null || regions.size() != 1)
			throw new SemanticsException("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 SemanticsException("Need an initial node in protocol state machine " + fProtocol.getName());
		ModalIOAutomaton mio= MioFactory.eINSTANCE.createModalIOAutomaton();
		mio.setName( ("Protocol" + fProtocol.getName()));

		fSemantics= mio;

		State initialState= createState(fSemantics, "Start");
		fSemantics.setStart(initialState);
		fStateMap.put(initialVertex, initialState);

		goFigure(initialVertex);

	}

	private void goFigure(Vertex vertex) throws SemanticsException {

		State 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);
			State 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(fSemantics, label);
				fStateMap.put(target, ourNewState);
			}

			Transition t= null;
			if (isMay(transition)) {
				t= MioFactory.eINSTANCE.createMayTransition();
				fSemantics.getMayTransitions().add((MayTransition) t);
			} else {
				t= MioFactory.eINSTANCE.createMustTransition();
				fSemantics.getMustTransitions().add((MustTransition) t);
			}

			Action a= null;
			if (isSend(transition)) {
				Operation op= getOperation(transition);
				a= createOutput(fSemantics, op.getName());
			} else if (isReply(transition)) {
				Operation op= getOperation(transition);
				a= createOutput(fSemantics, "return_" + op.getName());
			} else if (isRcvReply(transition)) {
				Operation op= getOperation(transition);
				a= createInput(fSemantics, "return_" + op.getName());
			} else if (isReceive(transition)) {
				Operation op= getOperation(transition);
				a= createInput(fSemantics, op.getName());
			} else
				a= createInternal(fSemantics, transition.getLabel());

			t.setFrom(from);
			t.setTo(ourNewState);
			t.setAction(a);

			if (isNew)
				goFigure(target);
		}

	}

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

		EList<Trigger> triggers= transition.getTriggers();
		if (triggers.size() != 1)
			throw new SemanticsException("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 SemanticsException("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 ModalIOAutomaton getSemantics() {
		return fSemantics;
	}

	public static State createState(ModalIOAutomaton t, String name) {
		State state= MioFactory.eINSTANCE.createState();
		state.setLabel(name);
		t.getStates().add(state);
		return state;
	}

	public static InputAction createInput(ModalIOAutomaton newTom, String string) {
		InputAction action= MioFactory.eINSTANCE.createInputAction();
		action.setLabel(string);
		InputAction in= action;
		newTom.getInputs().add(in);
		return in;
	}

	public static OutputAction createOutput(ModalIOAutomaton newTom, String string) {
		OutputAction action= MioFactory.eINSTANCE.createOutputAction();
		action.setLabel(string);
		OutputAction out= action;
		newTom.getOutputs().add(out);
		return out;
	}

	public static InternalAction createInternal(ModalIOAutomaton newTom, String string) {
		InternalAction action= MioFactory.eINSTANCE.createInternalAction();
		action.setLabel(string);
		InternalAction intt= action;
		newTom.getInternals().add(intt);
		return intt;
	}
}
