/*
 * 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.ui.eclipse.runner;

import java.io.File;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import net.miowb.model.mio.ModalIOAutomaton;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.uml2.uml.Activity;
import org.eclipse.uml2.uml.Behavior;
import org.eclipse.uml2.uml.Class;
import org.eclipse.uml2.uml.Component;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.Model;
import org.eclipse.uml2.uml.Package;
import org.eclipse.uml2.uml.Port;
import org.eclipse.uml2.uml.Property;
import org.eclipse.uml2.uml.ProtocolStateMachine;
import org.eclipse.uml2.uml.Stereotype;
import org.eclipse.uml2.uml.Type;
import org.eclipse.uml2.uml.UMLPackage;
import org.eclipse.uml2.uml.resource.UMLResource;

import eu.mdd4soa.smm.behaviour.ServiceActivity;
import eu.mdd4soa.smm.exception.TransformationException;
import eu.mdd4soa.smm.statik.Participant;
import eu.mdd4soa.trans.uml2smm.StereotypeUtil;
import eu.mdd4soa.trans.uml2smm.StereotypeUtil.StereoType;
import eu.mdd4soa.trans.uml2smm.transform.BehaviourConverter;
import eu.mdd4soa.trans.uml2smm.transform.StaticConverter;
import eu.uml4soa.utbm.u2m.SemanticsException;
import eu.uml4soa.utbm.u2m.U2MPlugin;
import eu.uml4soa.utbm.u2m.model.CAutomaton;
import eu.uml4soa.utbm.u2m.semantics.OrchestrationSemanticsFunction;
import eu.uml4soa.utbm.u2m.semantics.ProtocolSemanticsFunction;


public class SemanticsRunner {

	private Object fSelectedFile;

	private Map<Class, List<Behavior>> fSelectedBehaviors;

	private Map<Class, ParticipantSemantics> fSemanticsMapping;

	private Map<Class, List<Behavior>> fAvailableBehaviours;

	public SemanticsRunner(Object selectedFile) {
		fSelectedFile= selectedFile;

		// LinkedHashMap to keep order.
		fSemanticsMapping= new LinkedHashMap<Class, ParticipantSemantics>();
	}

	public void loadUML2Model(IProgressMonitor monitor) throws SemanticsException {
		monitor.beginTask("Loading model from file...", 120);
		monitor.worked(20);

		URI fileURI= convertToURI(fSelectedFile);
		EList<EObject> contents= null;
		try {
			ResourceSet resourceSet= new ResourceSetImpl();


			if (U2MPlugin.getDefault() == null || U2MPlugin.getDefault().getBundle() == null) {
				// Headless? Use worst hack ever. See
				// news://news.eclipse.org:119/h7b4el$7it$1@build.eclipse.org.

				resourceSet.getPackageRegistry().put(UMLPackage.eNS_URI, UMLPackage.eINSTANCE);
				resourceSet.getPackageRegistry().put("http://www.eclipse.org/uml2/2.0.0/UML", UMLPackage.eINSTANCE);

				resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(UMLResource.FILE_EXTENSION, UMLResource.Factory.INSTANCE);

				// For some unknown reason, we need the absolute path to the
				// eclipse uml2 uml resources JAR, which HAS to be in the
				// classpath!!!
				String property= System.getProperties().getProperty("java.class.path", null);
				String[] split= property.split(";");
				String path= null;
				for (String string : split) {
					if (string.contains("uml2.uml.resources")) {
						path= string;
						break;
					}
				}

				if (path == null)
					throw new RuntimeException("Could not find uml2 resources JAR in classpath. Include the jar in the classpath.");

				path= path.replace("\\", "/");

				Map<URI, URI> uriMap= resourceSet.getURIConverter().getURIMap();
				URI uri= URI.createURI("jar:file:/" + path + "!/");
				uriMap.put(URI.createURI(UMLResource.LIBRARIES_PATHMAP), uri.appendSegment("libraries").appendSegment(""));
				uriMap.put(URI.createURI(UMLResource.METAMODELS_PATHMAP), uri.appendSegment("metamodels").appendSegment(""));
				uriMap.put(URI.createURI(UMLResource.PROFILES_PATHMAP), uri.appendSegment("profiles").appendSegment(""));
			}

			Resource res= resourceSet.getResource(fileURI, true);

			monitor.worked(20);
			contents= res.getContents();
			monitor.worked(20);
		} catch (RuntimeException e) {
			e.printStackTrace();
			throw new SemanticsException("Could not read EMF resources from input file: " + e.getMessage(), e);
		}

		if (contents == null || contents.size() == 0)
			throw new SemanticsException("No EMF model is contained in the input .uml file.");
		if (! (contents.get(0) instanceof Model))
			throw new SemanticsException("The input .uml file provided does not contain an EMF UML2 model.");

		Model model= (Model) contents.get(0);

		monitor.worked(20);

		// Load behaviours
		fAvailableBehaviours= new HashMap<Class, List<Behavior>>();
		retrieveInput(model, fAvailableBehaviours);

		monitor.worked(20);
		monitor.done();
	}

	private void retrieveInput(EObject aContainer, Map<Class, List<Behavior>> participantActivities) {

		EList<EObject> contents= aContainer.eContents();

		for (EObject object : contents) {
			if (object instanceof Package || object instanceof Component)
				retrieveInput(object, participantActivities);
			else {
				if (object instanceof Class) {
					Class umlClass= (Class) object;
					if (StereotypeUtil.hasStereotype(umlClass, StereoType.PARTICIPANT)) {
						List<Behavior> activities= new LinkedList<Behavior>();
						retrieveInputFromParticipant(umlClass, activities);
						participantActivities.put(umlClass, activities);
					}
				}
			}
		}
	}

	private void retrieveInputFromParticipant(Class participant, List<Behavior> behaviours) {
		for (Behavior behavior : participant.getOwnedBehaviors()) {
			if (behavior instanceof Activity) {
				behaviours.add(behavior);
			}
		}
		EList<Property> ownedAttributes= participant.getOwnedAttributes();
		for (Property property : ownedAttributes) {
			if (property instanceof Port) {
				Port thePort= ((Port) property);
				if (hasStereotype(thePort, "service") || hasStereotype(thePort, "request")) {

					Type type= thePort.getType();
					if (type instanceof Class && (hasStereotype(type, "serviceInterface"))) {

						EList<Behavior> ownedBehaviors2= ((Class) type).getOwnedBehaviors();
						for (Behavior protocol : ownedBehaviors2) {
							if (protocol instanceof ProtocolStateMachine) {
								behaviours.add(protocol);
							}
						}
					}
				}
			}
		}
	}

	public Map<Class, List<Behavior>> getAvailableBehaviours() {
		return fAvailableBehaviours;
	}

	public static URI convertToURI(Object file) {
		URI fileURI= null;
		if (file instanceof IFile)
			fileURI= URI.createFileURI( ((IFile) file).getLocation().toString());
		else
			fileURI= URI.createFileURI( ((File) file).getAbsolutePath());

		return fileURI;
	}


	public static boolean hasStereotype(Element element, String stereotype) {
		for (Stereotype st : element.getAppliedStereotypes())
			if (stereotype.equalsIgnoreCase(st.getName()))
				return true;
		return false;
	}

	public void execute(IProgressMonitor monitor) throws SemanticsException {

		monitor.beginTask("Executing ...", 100);
		monitor.worked(20);

		for (Class c : fSelectedBehaviors.keySet()) {

			ParticipantSemantics s= new ParticipantSemantics(c);
			fSemanticsMapping.put(c, s);

			try {

				List<Behavior> list= fSelectedBehaviors.get(c);

				for (Behavior behavior : list) {

					if (behavior instanceof Activity) {
						ModalIOAutomaton semantics= generateSemantics((Activity) behavior, monitor);
						s.setImplementation((Activity) behavior, semantics);
					}

					if (behavior instanceof ProtocolStateMachine) {
						ModalIOAutomaton protocolSem= generateSemantics((ProtocolStateMachine) behavior);
						if (protocolSem != null)
							s.addSemantics((ProtocolStateMachine) behavior, protocolSem);
					}
				}

			} catch (TransformationException e) {

				// TODO warnings
				// WarningReceiver.getWarnings();
				// TransformerWarningDialog d= new TransformerWarningDialog()

				throw new SemanticsException("Problem converting to IOM", e);
			}
		}

		monitor.done();
	}

	private ModalIOAutomaton generateSemantics(ProtocolStateMachine behavior) throws SemanticsException {

		ProtocolSemanticsFunction f= new ProtocolSemanticsFunction(behavior);
		f.calculateSemantics();

		return f.getSemantics();
	}

	private ModalIOAutomaton generateSemantics(Activity behaviour, IProgressMonitor monitor) throws TransformationException, SemanticsException {

		Element participant= behaviour.getOwner();
		if (! (participant instanceof Class))
			throw new TransformationException("Problem: Owner of activity " + behaviour.getName() + " is not a class.");

		Class part= (Class) participant;
		if (!StereotypeUtil.hasStereotype(part, StereotypeUtil.StereoType.PARTICIPANT))
			throw new TransformationException("Problem: Owner of activity " + behaviour.getName() + " is not a participant.");

		Participant iomParticipant= StaticConverter.convert(part);
		ServiceActivity o= BehaviourConverter.convert(iomParticipant, behaviour);

		CAutomaton transform= OrchestrationSemanticsFunction.transform(o);

		return transform.toEMF();
	}

	public Map<Class, ParticipantSemantics> getSemanticsMapping() {
		return fSemanticsMapping;
	}

	public void setSelectedBehaviors(Map<Class, List<Behavior>> selectedBehaviors) {
		fSelectedBehaviors= selectedBehaviors;
	}

}
