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

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.swing.JOptionPane;

import com.nomagic.magicdraw.core.Project;
import com.nomagic.magicdraw.emfuml2xmi.v2.EmfUml2XmiPlugin;
import com.nomagic.magicdraw.ui.dialogs.MDDialogParentProvider;
import com.nomagic.magicdraw.uml.Visitor;
import com.nomagic.uml2.ext.jmi.helpers.StereotypesHelper;
import com.nomagic.uml2.ext.jmi.reflect.VisitorContext;
import com.nomagic.uml2.ext.magicdraw.actions.mdbasicactions.Action;
import com.nomagic.uml2.ext.magicdraw.actions.mdbasicactions.CallOperationAction;
import com.nomagic.uml2.ext.magicdraw.actions.mdcompleteactions.AcceptCallAction;
import com.nomagic.uml2.ext.magicdraw.activities.mdbasicactivities.ActivityEdge;
import com.nomagic.uml2.ext.magicdraw.activities.mdfundamentalactivities.Activity;
import com.nomagic.uml2.ext.magicdraw.auxiliaryconstructs.mdmodels.Model;
import com.nomagic.uml2.ext.magicdraw.classes.mdkernel.Class;
import com.nomagic.uml2.ext.magicdraw.classes.mdkernel.Element;
import com.nomagic.uml2.ext.magicdraw.classes.mdkernel.Operation;
import com.nomagic.uml2.ext.magicdraw.commonbehaviors.mdcommunications.CallEvent;
import com.nomagic.uml2.ext.magicdraw.commonbehaviors.mdcommunications.Event;
import com.nomagic.uml2.ext.magicdraw.commonbehaviors.mdcommunications.Trigger;
import com.nomagic.uml2.ext.magicdraw.interactions.mdbasicinteractions.ReceiveOperationEvent;
import com.nomagic.uml2.ext.magicdraw.statemachines.mdbehaviorstatemachines.State;
import com.nomagic.uml2.ext.magicdraw.statemachines.mdbehaviorstatemachines.Vertex;
import com.nomagic.uml2.ext.magicdraw.statemachines.mdprotocolstatemachines.ProtocolStateMachine;
import com.nomagic.uml2.ext.magicdraw.statemachines.mdprotocolstatemachines.ProtocolTransition;

import eu.uml4soa.utbm.ui.md.ui.SelectableElement;

public class MDUtil {

	public static void error(String message) {
		JOptionPane.showMessageDialog(MDDialogParentProvider.getProvider().getDialogParent(), message, "UML4SOA Plugin", JOptionPane.ERROR_MESSAGE);
	}

	public static void info(String message) {
		JOptionPane.showMessageDialog(MDDialogParentProvider.getProvider().getDialogParent(), message, "UML4SOA Plugin",
				JOptionPane.INFORMATION_MESSAGE);
	}

	public static File exportEMFUML(Project project) throws Exception {
		File tempDir= null;

		tempDir= createTempDirectory();
		EmfUml2XmiPlugin.getInstance().exportXMI(project, tempDir.getAbsolutePath());

		File inputFile= new File(tempDir, project.getName() + ".uml");
		return inputFile;

	}

	public static File createTempDirectory() throws IOException {
		final File temp;

		temp= File.createTempFile("uwetemp", Long.toString(System.nanoTime()));

		if (! (temp.delete())) {
			throw new IOException("Could not delete temp file: " + temp.getAbsolutePath());
		}

		if (! (temp.mkdir())) {
			throw new IOException("Could not create temp directory: " + temp.getAbsolutePath());
		}

		return (temp);
	}

	public static void removeTempDirectory(File tempDir) {
		File[] files= tempDir.listFiles();
		for (File f : files) {
			if (f.isDirectory())
				removeTempDirectory(f);
			else
				f.delete();
		}
		tempDir.delete();
	}

	public static String getMachineReadableName(ProtocolStateMachine machine) {
		com.nomagic.uml2.ext.magicdraw.classes.mdkernel.Class c= (Class) machine.getOwner();
		return c.getQualifiedName() + "::" + machine.getName();
	}

	public static String getMachineReadableName(Class c) {
		return c.getQualifiedName();
	}

	public static List<ProtocolStateMachine> getAllProtocolStateMachines(Model model) {
		List<ProtocolStateMachine> m= new ArrayList<ProtocolStateMachine>();
		getAllPSMs(model, m);
		return m;
	}

	private static void getAllPSMs(Element model, List<ProtocolStateMachine> allElements) {

		if (model instanceof ProtocolStateMachine)
			allElements.add((ProtocolStateMachine) model);

		for (Element el : model.getOwnedElement()) {
			getAllPSMs(el, allElements);
		}
	}

	private static void getAllClasses(Element model, List<Class> allElements) {

		if (model instanceof Class && (StereotypesHelper.hasStereotype(model, "participant"))) {
			allElements.add((Class) model);
		}

		for (Element el : model.getOwnedElement()) {
			getAllClasses(el, allElements);
		}
	}

	public static List<Class> getAllParticipants(Model model) {
		List<Class> m= new ArrayList<Class>();
		getAllClasses(model, m);
		return m;
	}

	public static List<SelectableElement> createPSMSelectableElementList(List<ProtocolStateMachine> allElements) {

		List<SelectableElement> l= new ArrayList<SelectableElement>();
		for (ProtocolStateMachine protocolStateMachine : allElements) {

			String name= protocolStateMachine.getName();
			Element owner= protocolStateMachine.getOwner();
			if (owner != null)
				name+= " (" + owner.getHumanName() + ")";

			l.add(new SelectableElement(name, protocolStateMachine));
		}

		return l;
	}

	public static List<SelectableElement> createParticipantSelectableElementList(List<Class> allElements) {
		List<SelectableElement> l= new ArrayList<SelectableElement>();
		for (Class clazz : allElements) {

			String name= clazz.getName() + "(" + clazz.getQualifiedName() + ")";
			l.add(new SelectableElement(name, clazz));
		}

		return l;
	}

	static List<Element> findProblematicMDElementsInPSM(ProtocolStateMachine toAnnotate, final String stateInPSM, final String transitionInPSM) {

		final List<Element> problematix= new ArrayList<Element>();

		Collection<Element> ownedElement= toAnnotate.getOwnedElement();
		try {
			while (!ownedElement.isEmpty()) {

				Collection<Element> newOwneds= new ArrayList<Element>();

				for (Element element : ownedElement) {
					element.accept(new Visitor() {

						@Override
						public void visitState(State element, VisitorContext context) {
							if (isOutState(element, stateInPSM))
								problematix.add(element);

							super.visitState(element, context);
						}

						private boolean isOutState(State element, final String problematicState) {

							String elementName= element.getName();
							if (elementName == null)
								return false;
							else
								return element.getName().equals(problematicState);
						}

						@Override
						public void visitProtocolTransition(ProtocolTransition element, VisitorContext context) {

							if (transitionInPSM == null)
								return;

							Vertex source= element.getSource();
							if (! (source instanceof State))
								return;

							if (!isOutState((State) source, stateInPSM))
								return;

							String opName= null;

							Collection<Trigger> trigger= element.getTrigger();
							if (trigger != null)
								for (Trigger t : trigger) {
									Event event= t.getEvent();
									if (event instanceof CallEvent) {
										CallEvent ce= (CallEvent) event;
										opName= ce.getOperation().getName();
										break;
									}
									if (event instanceof ReceiveOperationEvent) {
										ReceiveOperationEvent ce= (ReceiveOperationEvent) event;
										opName= ce.getOperation().getName();
										break;
									}
								}

							if (opName != null && opName.equals(transitionInPSM))
								problematix.add(element);

							super.visitProtocolTransition(element, context);
						}

					});
					newOwneds.addAll(element.getOwnedElement());
				}
				ownedElement= newOwneds;
			}
		} catch (Exception ex) {
			MDUtil.error("An exception occurred during findProblematicElements:" + ex + " with message: " + ex.getMessage());
		}

		return problematix;
	}

	enum LookInPlace {
		BEFORE, AFTER
	};

	enum LookInAction {
		RCV, SND
	};

	public static Collection<? extends Element> findProblematicMDElementsInActivity(Activity toAnnotate, String problematicStateInActivity,
			String transition) {
		final List<Element> problematix= new ArrayList<Element>();

		LookInPlace pplace= null;
		LookInAction aaction= null;
		String n= "";

		if (problematicStateInActivity.startsWith("start rcv")) {
			pplace= LookInPlace.BEFORE;
			aaction= LookInAction.RCV;
			n= problematicStateInActivity.substring("start rcv".length() + 1);
		}
		if (problematicStateInActivity.startsWith("start snd")) {
			pplace= LookInPlace.BEFORE;
			aaction= LookInAction.SND;
			n= problematicStateInActivity.substring("start snd".length() + 1);
		}
		if (problematicStateInActivity.startsWith("end rcv")) {
			pplace= LookInPlace.AFTER;
			aaction= LookInAction.RCV;
			n= problematicStateInActivity.substring("end rcv".length() + 1);
		}
		if (problematicStateInActivity.startsWith("end snd")) {
			pplace= LookInPlace.AFTER;
			aaction= LookInAction.SND;
			n= problematicStateInActivity.substring("end snd".length() + 1);
		}

		if (n.contains("("))
			n= n.substring(0, n.indexOf("("));

		if (n.isEmpty())
			return problematix;

		// MDUtil.info("Looking for \"" + n + "\" place= " + pplace +
		// " action= " + aaction);

		final String name= n;
		final LookInPlace place= pplace;
		final LookInAction action= aaction;

		Collection<Element> ownedElement= toAnnotate.getOwnedElement();
		try {
			while (!ownedElement.isEmpty()) {

				Collection<Element> newOwneds= new ArrayList<Element>();

				for (Element element : ownedElement) {
					element.accept(new Visitor() {

						@Override
						public void visitAcceptCallAction(AcceptCallAction element, VisitorContext context) {

							if (action.equals(LookInAction.RCV)) {
								Collection<Trigger> trigger= element.getTrigger();
								Operation op= null;
								for (Trigger trigger2 : trigger) {
									Event event= trigger2.getEvent();
									if (event instanceof ReceiveOperationEvent)
										op= ((ReceiveOperationEvent) event).getOperation();
									if (event instanceof CallEvent)
										op= ((CallEvent) event).getOperation();
								}

								if (op != null && nameMatch(op, name)) {
									problematix.add(getElement(element, place));
								}

							}
							super.visitAcceptCallAction(element, context);
						}

						@Override
						public void visitCallOperationAction(CallOperationAction element, VisitorContext context) {
							if (action.equals(LookInAction.SND)) {
								if (nameMatch(element.getOperation(), name)) {
									problematix.add(getElement(element, place));
								}
							}
							super.visitCallOperationAction(element, context);
						}

						private boolean nameMatch(Operation operation, String name) {

							String a= operation.getName();
							String b= name;

							return operation.getName().equals(name);
						}

						private Element getElement(Action element, LookInPlace place) {
							switch (place) {
								case AFTER:
									return element;
								case BEFORE: {
									Collection<ActivityEdge> incoming= element.getIncoming();
									for (ActivityEdge edge : incoming) {
										// TODO. Should only be one, though.
										return edge.getSource();
									}
								}
							}
							return null;
						}

					});
					newOwneds.addAll(element.getOwnedElement());
				}
				ownedElement= newOwneds;
			}
		} catch (Exception ex) {
			MDUtil.error("An exception occurred during findProblematicElements:" + ex + " with message: " + ex.getMessage());
		}

		return problematix;
	}

}
