/***************************************************************************
 * Copyright 2014 Kieker Project (http://kieker-monitoring.net)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ***************************************************************************/

package kieker.webgui.web.beans.view;

import java.util.ArrayList;
import java.util.List;

import kieker.analysis.model.analysisMetaModel.MIAnalysisComponent;
import kieker.analysis.model.analysisMetaModel.MIFilter;
import kieker.analysis.model.analysisMetaModel.MIInputPort;
import kieker.analysis.model.analysisMetaModel.MIOutputPort;
import kieker.analysis.model.analysisMetaModel.MIPlugin;
import kieker.analysis.model.analysisMetaModel.MIPort;
import kieker.analysis.model.analysisMetaModel.MIProject;
import kieker.analysis.model.analysisMetaModel.MIReader;
import kieker.analysis.model.analysisMetaModel.MIRepository;
import kieker.analysis.model.analysisMetaModel.MIRepositoryConnector;
import kieker.common.logging.Log;
import kieker.common.logging.LogFactory;
import kieker.common.util.registry.Registry;
import kieker.webgui.common.exception.GraphLayoutException;
import kieker.webgui.service.IGraphLayoutService;
import kieker.webgui.web.beans.application.GlobalPropertiesBean;
import kieker.webgui.web.beans.session.UserBean;
import kieker.webgui.web.utility.IGraphListener;

import org.primefaces.context.RequestContext;

import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

/**
 * This bean contains the necessary data behind an graph of the analysis editor. It provides various methods to manipulate the current graph.<br>
 * </br>
 * 
 * The class is a Spring managed bean with view scope to make sure that one user (even in one session) can open multiple projects at a time without causing any
 * problems.
 * 
 * @author Nils Christian Ehmke
 */
@Component
@Scope("view")
public final class AnalysisEditorGraphBean {

	private static final Log LOG = LogFactory.getLog(AnalysisEditorGraphBean.class);

	private static final String[] PARAMETER_NAMES_AUTO_LAYOUT = { "nodes", "edges" };
	private static final Class<?>[] PARAMETER_TYPES_AUTO_LAYOUT = { String.class, String.class };

	private static final String[] PARAMETER_NAMES_ADD_AND_REMOVE_EDGES = { "sourcePortID", "targetPortID" };
	private static final Class<?>[] PARAMETER_TYPES_ADD_AND_REMOVE_EDGES = { String.class, String.class };

	private static final String[] PARAMETER_NAMES_CLICK_AND_REMOVE_NODES = { "ID" };
	private static final Class<?>[] PARAMETER_TYPES_CLICK_AND_REMOVE_NODES = { String.class };

	private static final String JS_CMD_CREATE_GRAPH_VARIABLE = "var graph = GraphFlow()";
	private static final String JS_CMD_ADD_EDGE_CONSTRAINTS = "graph.addEdgeConstraints()";

	private static final String JS_TEMPLATE_NODE = "{'id':'%s', 'name':'%s', 'nodeClass':'%s', 'tooltip':'%s'}";
	private static final String JS_TEMPLATE_PORT = "{'name':'%s','id':'%s', 'tooltip':'%s'}";

	private static final String JS_CMD_SET_GLOBAL_ICON = "graph.setNodeIcon('Global', '../img/graphIcons/ProjectConfiguration.png', true)";
	private static final String JS_CMD_SET_FILTER_ICON = "graph.setNodeIcon('Filter', '../img/graphIcons/FilterIcon.png', true)";
	private static final String JS_CMD_SET_READER_ICON = "graph.setNodeIcon('Reader', '../img/graphIcons/ReaderIcon.png', true)";
	private static final String JS_CMD_SET_REPO_ICON = "graph.setNodeIcon('Repository', '../img/graphIcons/RepositoryIcon.png', true)";
	private static final String JS_CMD_SET_VIZ_ICON = "graph.setNodeIcon('Visualization', '../img/graphIcons/VisualizationIcon.png', true)";

	private static final String JS_CMD_ADD_CLICK_NODE_LISTENER = "graph.addListener('onClick', nodeClickListener)";
	private static final String JS_CMD_ADD_ENTER_NODE_LISTENER = "graph.addListener('onMouseEnter', nodeEnterListener)";
	private static final String JS_CMD_ADD_REMOVE_NODE_LISTENER = "graph.addListener('onRemoveNode', nodeRemoveListener)";
	private static final String JS_CMD_ADD_CREATE_EDGE_LISTENER = "graph.addListener('onCreateEdge', edgeCreateListener)";
	private static final String JS_CMD_ADD_REMOVE_EDGE_LISTENER = "graph.addListener('onRemoveEdge', edgeRemoveListener)";
	private static final String JS_CMD_ADD_AUTO_LAYOUT_LISTENER = "graph.addListener('autoLayout', autoLayoutListener)";

	private static final String JS_CMD_CREATE_GRAPH_CENTER_VARIABLE = "var center = graph.getScreenCenter()";

	// private static final String JS_CMD_ADD_VIZ = JS_CMD_CREATE_GRAPH_CENTER_VARIABLE +
	// "; graph.addNode(center.x,center.y,%s,[%s],[%s],[%s],'Visualization',false)";
	private static final String JS_CMD_ADD_FILTER = JS_CMD_CREATE_GRAPH_CENTER_VARIABLE + "; graph.addNode(center.x,center.y,%s,[%s],[%s],[%s],'Filter',false)";
	private static final String JS_CMD_ADD_READER = JS_CMD_CREATE_GRAPH_CENTER_VARIABLE + "; graph.addNode(center.x,center.y,%s,[%s],null,[%s],'Reader',false)";
	private static final String JS_CMD_ADD_REPO = JS_CMD_CREATE_GRAPH_CENTER_VARIABLE + "; graph.addNode(center.x,center.y, %s,null,[%s],null,'Repository',false)";

	private static final String JS_CMD_ADD_GLOBAL_COMPONENT = JS_CMD_CREATE_GRAPH_CENTER_VARIABLE
			+ "; graph.addNode(center.x,center.y,{'id':'%s','name':'%s','nodeClass':'','tooltip':'%s'}, null, null, null, 'Global', false)"
			+ "; graph.setNodeData('%s.close', {'$visible' : false});";

	private static final Object REPOSITORY_INPUT_PORT = "R";
	private static final String JS_PORT_TYPE_INPUT = "inputPort";

	private static final String JS_CMD_ADD_EDGE = "graph.addEdge('%s', '%s', '%s', false, false)";

	private static final String JS_CMD_ENABLE_GRID = "graph.setGridVisible(true, false)";
	private static final String JS_CMD_DISABLE_GRID = "graph.setGridVisible(false, false)";
	private static final String JS_CMD_ENABLE_SNAP = "graph.setGridSnap(true)";
	private static final String JS_CMD_DISABLE_SNAP = "graph.setGridSnap(false)";
	private static final String JS_CMD_SET_GRID_COLOR = "graph.setGridColor('%s', false)";
	private static final String JS_CMD_SET_GRID_SIZE = "graph.setGridSize(%d, false)";
	private static final String JS_CMD_SET_READ_ONLY = "graph.setReadOnly(false, true)";
	private static final String JS_CMD_SCALE_TO_FIT = "graph.scaleToFit()";

	private static final String JS_CMD_START_AUTO_LAYOUT = "graph.autoLayout()";
	private static final String JS_CMD_LOAD_FROM_LAYOUT = "graph.loadPositions('%s')";
	private static final String JS_CMD_RENAME_NODE = "graph.setNodeData('%s', {'name' : '%s'})";

	private final Registry<MIAnalysisComponent> componentMap = new Registry<MIAnalysisComponent>();
	private final List<IGraphListener> graphListeners = new ArrayList<IGraphListener>();
	private final Registry<EObject> portMap = new Registry<EObject>();

	private boolean gridEnabled;
	private boolean snapEnabled;

	@Autowired
	private GlobalPropertiesBean globalPropertiesBean;
	@Autowired
	private IGraphLayoutService layouter;
	@Autowired
	private UserBean userBean;

	/**
	 * Creates a new instance of this class. <b>Do not call this constructor manually. It will only be accessed by Spring.</b>
	 */
	public AnalysisEditorGraphBean() {
		// No code necessary
	}

	/**
	 * Declares the graph.
	 */
	public void declareGraph() {
		// Declare the graph
		RequestContext.getCurrentInstance().execute(JS_CMD_CREATE_GRAPH_VARIABLE);

		// Add the edge constraints
		RequestContext.getCurrentInstance().execute(JS_CMD_ADD_EDGE_CONSTRAINTS);

		// Set the icons for the available types
		RequestContext.getCurrentInstance().execute(JS_CMD_SET_FILTER_ICON);
		RequestContext.getCurrentInstance().execute(JS_CMD_SET_REPO_ICON);
		RequestContext.getCurrentInstance().execute(JS_CMD_SET_READER_ICON);
		RequestContext.getCurrentInstance().execute(JS_CMD_SET_GLOBAL_ICON);
		RequestContext.getCurrentInstance().execute(JS_CMD_SET_VIZ_ICON);

		// Make sure that guests cannot modify the graph
		if ("Guest".equals(this.userBean.getUserrole())) {
			RequestContext.getCurrentInstance().execute(JS_CMD_SET_READ_ONLY);
		}
	}

	/**
	 * Initializes the listener for the graph.
	 */
	public void initializeListeners() {
		RequestContext.getCurrentInstance().execute(JS_CMD_ADD_CLICK_NODE_LISTENER);
		RequestContext.getCurrentInstance().execute(JS_CMD_ADD_ENTER_NODE_LISTENER);

		RequestContext.getCurrentInstance().execute(JS_CMD_ADD_AUTO_LAYOUT_LISTENER);

		RequestContext.getCurrentInstance().execute(JS_CMD_ADD_REMOVE_NODE_LISTENER);
		RequestContext.getCurrentInstance().execute(JS_CMD_ADD_CREATE_EDGE_LISTENER);
		RequestContext.getCurrentInstance().execute(JS_CMD_ADD_REMOVE_EDGE_LISTENER);
	}

	/**
	 * Adds a listener to the graph. The listeners will be informed for javascript events.
	 * 
	 * @param graphListener
	 *            The new listener.
	 */
	public void addGraphListener(final IGraphListener graphListener) {
		this.graphListeners.add(graphListener);
	}

	/**
	 * Adds a project to the graph. All components (plugins and repositories) and their connections from the given project are added to the graph.
	 * 
	 * @param project
	 *            The project to add to the graph.
	 */
	public void addProject(final MIProject project) {
		this.addComponents(project.getPlugins());
		this.addComponents(project.getRepositories());

		this.addConnections(project.getPlugins());
	}

	/**
	 * Adds a non removable node for the global configuration of the project.
	 * 
	 * @param globalConfigurationInstance
	 *            The non removable node to be added.
	 */
	public void addGlobalConfigurationInstance(final MIAnalysisComponent globalConfigurationInstance) {
		final String name = this.globalPropertiesBean.getGlobalConfigurationComponentName();
		final int id = this.componentMap.get(globalConfigurationInstance);

		RequestContext.getCurrentInstance().execute(String.format(JS_CMD_ADD_GLOBAL_COMPONENT, "id" + id, name, name, "id" + id));
	}

	/**
	 * Adds a reader to the graph.
	 * 
	 * @param reader
	 *            The reader to be added.
	 */
	public void addComponent(final MIReader reader) {
		final String cmd = String.format(JS_CMD_ADD_READER, this.assembleGraphString(reader), this.assembleGraphRepositoryPortString(reader.getRepositories()),
				this.assembleGraphOutputPortString(reader));
		RequestContext.getCurrentInstance().execute(cmd);
	}

	/**
	 * Adds a filter to the graph.
	 * 
	 * @param filter
	 *            The filter to be added.
	 */
	public void addComponent(final MIFilter filter) {
		final String cmd = String.format(JS_CMD_ADD_FILTER, this.assembleGraphString(filter), this.assembleGraphRepositoryPortString(filter.getRepositories()),
				this.assembleGraphInputPortString(filter), this.assembleGraphOutputPortString(filter));
		RequestContext.getCurrentInstance().execute(cmd);
	}

	/**
	 * Adds a repository to the graph.
	 * 
	 * @param repository
	 *            The repository to be added.
	 */
	public void addComponent(final MIRepository repository) {
		final String repoPort = String.format(JS_TEMPLATE_PORT, JS_PORT_TYPE_INPUT, REPOSITORY_INPUT_PORT, "N/A");
		final String cmd = String.format(JS_CMD_ADD_REPO, this.assembleGraphString(repository), repoPort);
		RequestContext.getCurrentInstance().execute(cmd);
	}

	/**
	 * Deletes a component from the graph.
	 * 
	 * @param component
	 *            The component to be deleted.
	 */
	public void deleteComponent(final MIAnalysisComponent component) {
		// No code necessary currently
	}

	/**
	 * Renames a component within the graph.
	 * 
	 * @param component
	 *            The component to rename.
	 * @param newName
	 *            The new name of the component.
	 */
	public void renameComponent(final MIAnalysisComponent component, final String newName) {
		final String cmd = String.format(JS_CMD_RENAME_NODE, "id" + this.componentMap.get(component), AnalysisEditorGraphBean.simpleEscape(newName));
		RequestContext.getCurrentInstance().execute(cmd);
	}

	/**
	 * Adds a connection between two components to the graph.
	 * 
	 * @param source
	 *            The source node.
	 * @param target
	 *            The target node.
	 * @param outputPort
	 *            The output port of the source node.
	 * @param inputPort
	 *            The input port of the target node.
	 */
	public void addConnection(final MIPlugin source, final MIPlugin target, final MIOutputPort outputPort, final MIInputPort inputPort) {
		final String cmd = String.format(JS_CMD_ADD_EDGE, this.assembleGraphPortID(source, outputPort), this.assembleGraphPortID(target, inputPort), "");
		RequestContext.getCurrentInstance().execute(cmd);
	}

	/**
	 * Adds a connection between two components to the graph.
	 * 
	 * @param source
	 *            The source node.
	 * @param target
	 *            The target node.
	 * @param repositoryPort
	 *            The repository port of the source node.
	 */
	public void addConnection(final MIPlugin source, final MIRepository target, final MIRepositoryConnector repositoryPort) {
		final String cmd = String.format(JS_CMD_ADD_EDGE, this.assembleGraphPortID(source, repositoryPort), this.assembleGraphPortID(target), "");
		RequestContext.getCurrentInstance().execute(cmd);
	}

	/**
	 * Deletes a connection between two components within the graph.
	 * 
	 * @param source
	 *            The source node.
	 * @param target
	 *            The target node.
	 * @param outputPort
	 *            The output port of the source node.
	 * @param inputPort
	 *            The input port of the target node.
	 */
	public void deleteConnection(final MIPlugin source, final MIPlugin target, final MIOutputPort outputPort, final MIInputPort inputPort) {
		// No code necessary currently
	}

	/**
	 * Deletes a connection between two components within the graph.
	 * 
	 * @param source
	 *            The source node.
	 * @param target
	 *            The target node.
	 * @param repositoryPort
	 *            The repository port of the source node.
	 */
	public void deleteConnection(final MIPlugin source, final MIRepository target, final MIRepositoryConnector repositoryPort) {
		// No code necessary currently
	}

	/**
	 * Selects a given component.
	 * 
	 * @param analysisComponent
	 *            The component to select.
	 */
	public void selectComponent(final MIAnalysisComponent analysisComponent) {
		// No code necessary currently
	}

	/**
	 * Switches the visibility of the grid.
	 */
	public void switchGrid() {
		if (this.gridEnabled) {
			RequestContext.getCurrentInstance().execute(JS_CMD_DISABLE_GRID);
		} else {
			RequestContext.getCurrentInstance().execute(JS_CMD_ENABLE_GRID);
		}

		this.gridEnabled = !this.gridEnabled;
	}

	/**
	 * Switches the state of the snap mode.
	 */
	public void switchSnap() {
		if (this.snapEnabled) {
			RequestContext.getCurrentInstance().execute(JS_CMD_DISABLE_SNAP);
		} else {
			RequestContext.getCurrentInstance().execute(JS_CMD_ENABLE_SNAP);
		}

		this.snapEnabled = !this.snapEnabled;
	}

	public boolean isGridEnabled() {
		return this.gridEnabled;
	}

	public boolean isSnapEnabled() {
		return this.snapEnabled;
	}

	/**
	 * Sets the color of the grid to a new value.
	 * 
	 * @param color
	 *            The new color of the grid.
	 */
	public void setGridColor(final String color) {
		final String cmd = String.format(JS_CMD_SET_GRID_COLOR, "#" + color);
		RequestContext.getCurrentInstance().execute(cmd);
	}

	/**
	 * Sets the size of the grid to a new value.
	 * 
	 * @param size
	 *            The new size of the grid.
	 */
	public void setGridSize(final int size) {
		final String cmd = String.format(JS_CMD_SET_GRID_SIZE, size);
		RequestContext.getCurrentInstance().execute(cmd);
	}

	/**
	 * Scales the graph to fit into the window.
	 */
	public void scaleToFit() {
		RequestContext.getCurrentInstance().execute(JS_CMD_SCALE_TO_FIT);
	}

	/**
	 * Starts the auto layout of the graph.
	 */
	public void startAutoLayout() {
		RequestContext.getCurrentInstance().execute(JS_CMD_START_AUTO_LAYOUT);
	}

	/**
	 * Loads a layout for the graph.
	 * 
	 * @param layout
	 *            The new layout of the graph.
	 */
	public void loadLayout(final String layout) {
		final String cmd = String.format(JS_CMD_LOAD_FROM_LAYOUT, layout);
		RequestContext.getCurrentInstance().execute(cmd);
	}

	/**
	 * This method is called from JavaScript. It represents the event that a component has been selected.
	 */
	public void jsSelectComponentEvent() {
		// Get the parameters
		final Object[] parameters = GlobalPropertiesBean.convertObjectsFromParameterMap(PARAMETER_NAMES_CLICK_AND_REMOVE_NODES,
				PARAMETER_TYPES_CLICK_AND_REMOVE_NODES);
		final String clickedNodeID = (String) parameters[0];

		// Search the correct component

		if (clickedNodeID.isEmpty()) {
			for (final IGraphListener graphListener : this.graphListeners) {
				graphListener.componentSelectedEvent(null);
			}
		} else {
			try {
				final MIAnalysisComponent selectedComponent = this.componentMap.get(Integer.parseInt(clickedNodeID.substring(2)));
				if (selectedComponent != null) {
					for (final IGraphListener graphListener : this.graphListeners) {
						graphListener.componentSelectedEvent(selectedComponent);
					}
				}
			} catch (final NumberFormatException ex) {
				// Ignore an invalid ID, but log it.
				AnalysisEditorGraphBean.LOG.info("Invalid ID", ex);
			}
		}

	}

	/**
	 * This method is called from JavaScript. It represents the event that a component has been deleted.
	 */
	public void jsDeleteComponentEvent() {
		// Get the parameters
		final Object[] parameters = GlobalPropertiesBean.convertObjectsFromParameterMap(PARAMETER_NAMES_CLICK_AND_REMOVE_NODES,
				PARAMETER_TYPES_CLICK_AND_REMOVE_NODES);
		final String clickedNodeID = (String) parameters[0];

		// Now search the correct node
		try {
			final MIAnalysisComponent deletedComponent = this.componentMap.get(Integer.parseInt(clickedNodeID.substring(2)));
			if (deletedComponent != null) {
				for (final IGraphListener graphListener : this.graphListeners) {
					graphListener.componentDeletedEvent(deletedComponent);
				}
			}
		} catch (final NumberFormatException ex) {
			// Ignore an invalid ID, but log it.
			AnalysisEditorGraphBean.LOG.info("Invalid ID", ex);
		}
	}

	/**
	 * This method is called from JavaScript. It represents the event that a connection has been added.
	 */
	public void jsAddConnectionEvent() {
		// Get the parameters
		final Object[] parameters = GlobalPropertiesBean.convertObjectsFromParameterMap(PARAMETER_NAMES_ADD_AND_REMOVE_EDGES, PARAMETER_TYPES_ADD_AND_REMOVE_EDGES);
		final String sourceIDAsStr = ((String) parameters[0]).split("\\.")[0];
		final String sourcePortIDAsStr = ((String) parameters[0]).split("\\.")[1];
		final String targetIDAsStr = ((String) parameters[1]).split("\\.")[0];
		final String targetPortIDAsStr = ((String) parameters[1]).split("\\.")[1];

		try {
			// Convert the IDs
			final int sourceID = Integer.parseInt(sourceIDAsStr.substring(2));
			final int sourcePortID = Integer.parseInt(sourcePortIDAsStr);
			final int targetID = Integer.parseInt(targetIDAsStr.substring(2));

			this.componentMap.get(sourceID);
			final MIAnalysisComponent target = this.componentMap.get(targetID);

			// There are only two allowed cases: A connection between two filters with regular ports and a connection between a filter and a repository using the
			// repository ports.
			if (!REPOSITORY_INPUT_PORT.equals(sourcePortIDAsStr)) {
				// Source is not the special port of a repository
				final EObject sourcePort = this.portMap.get(sourcePortID);
				if (REPOSITORY_INPUT_PORT.equals(targetPortIDAsStr)) {
					// Target is the special port of a repository
					if (sourcePort instanceof MIRepositoryConnector) {
						for (final IGraphListener graphListener : this.graphListeners) {
							graphListener.connectionAddedEvent((MIRepositoryConnector) sourcePort, (MIRepository) target);
						}
					}
				} else {
					// Target is not the special port of a repository
					final int targetPortID = Integer.parseInt(targetPortIDAsStr);
					final EObject targetPort = this.portMap.get(targetPortID);
					if ((sourcePort instanceof MIOutputPort) && (targetPort instanceof MIInputPort)) {
						for (final IGraphListener graphListener : this.graphListeners) {
							graphListener.connectionAddedEvent((MIOutputPort) sourcePort, (MIInputPort) targetPort);
						}
					}
				}
			}
		} catch (final NumberFormatException ex) {
			// Ignore an invalid ID, but log it.
			AnalysisEditorGraphBean.LOG.info("Invalid ID", ex);
		}
	}

	/**
	 * This method is called from JavaScript. It represents the event that a connection has been deleted.
	 */
	public void jsDeleteConnectionEvent() {
		// Get the parameters
		final Object[] parameters = GlobalPropertiesBean.convertObjectsFromParameterMap(PARAMETER_NAMES_ADD_AND_REMOVE_EDGES, PARAMETER_TYPES_ADD_AND_REMOVE_EDGES);
		final String sourcePortID = ((String) parameters[0]).split("\\.")[1];
		final String targetPortID = ((String) parameters[1]).split("\\.")[1];

		// Now search the correct components
		try {
			if (REPOSITORY_INPUT_PORT.equals(targetPortID)) {
				// This is a special case: An edge between a filter and a repository
				final MIRepositoryConnector sourcePort = (MIRepositoryConnector) this.portMap.get(Integer.parseInt(sourcePortID));
				final String targetID = ((String) parameters[1]).split("\\.")[0];
				final MIAnalysisComponent targetRepo = this.componentMap.get(Integer.parseInt(targetID.substring(2)));
				if ((sourcePort != null) && (targetRepo instanceof MIRepository)) {
					for (final IGraphListener graphListener : this.graphListeners) {
						graphListener.connectionDeletedEvent(sourcePort, (MIRepository) targetRepo);
					}
				}
			} else {
				// This is the normal case: An edge between two filters
				final MIPort sourcePort = (MIPort) this.portMap.get(Integer.parseInt(sourcePortID));
				final MIPort targetPort = (MIPort) this.portMap.get(Integer.parseInt(targetPortID));

				if ((sourcePort != null) && (targetPort != null)) {
					for (final IGraphListener graphListener : this.graphListeners) {
						graphListener.connectionDeletedEvent((MIOutputPort) sourcePort, (MIInputPort) targetPort);
					}
				}
			}
		} catch (final NumberFormatException ex) {
			// Ignore an invalid ID, but log it.
			AnalysisEditorGraphBean.LOG.info("Invalid ID", ex);
		}
	}

	/**
	 * This method is called from JavaScript. It represents the event that the auto layout has been started.
	 */
	public void jsAutoLayoutEvent() {
		try {
			// Get the parameters
			final Object[] parameters = GlobalPropertiesBean.convertObjectsFromParameterMap(PARAMETER_NAMES_AUTO_LAYOUT, PARAMETER_TYPES_AUTO_LAYOUT);
			final String nodes = (String) parameters[0];
			final String edges = (String) parameters[1];

			// Calculate the layout and use it
			this.loadLayout(this.layouter.layoutGraph(nodes, edges));
		} catch (final GraphLayoutException ex) {
			AnalysisEditorGraphBean.LOG.warn("Autolayout failed.", ex);
		}
	}

	private String assembleGraphRepositoryPortString(final EList<MIRepositoryConnector> ports) {
		final StringBuilder builder = new StringBuilder();
		final int len = ports.size();

		for (int i = 0; i < len; i++) {
			final MIRepositoryConnector port = ports.get(i);

			if (i != 0) {
				builder.append(',');
			}

			builder.append(String.format(JS_TEMPLATE_PORT, AnalysisEditorGraphBean.simpleEscape(port.getName()), this.portMap.get(port),
					AnalysisEditorGraphBean.simpleEscape(port.getName())));
		}

		return builder.toString();
	}

	private String assembleGraphInputPortString(final MIFilter filter) {
		return this.assembleGraphPortString(filter.getInputPorts());
	}

	private String assembleGraphOutputPortString(final MIPlugin plugin) {
		return this.assembleGraphPortString(plugin.getOutputPorts());
	}

	private Object assembleGraphPortID(final MIPlugin plugin, final MIRepositoryConnector port) {
		return "id" + this.componentMap.get(plugin) + "." + this.portMap.get(port);
	}

	private String assembleGraphPortID(final MIPlugin plugin, final MIPort port) {
		return "id" + this.componentMap.get(plugin) + "." + this.portMap.get(port);
	}

	private Object assembleGraphPortID(final MIRepository repository) {
		return "id" + this.componentMap.get(repository) + "." + REPOSITORY_INPUT_PORT;
	}

	private String assembleGraphPortString(final EList<? extends MIPort> ports) {
		final StringBuilder builder = new StringBuilder();
		final int len = ports.size();

		for (int i = 0; i < len; i++) {
			final MIPort port = ports.get(i);

			if (i != 0) {
				builder.append(',');
			}

			builder.append(String.format(JS_TEMPLATE_PORT, AnalysisEditorGraphBean.simpleEscape(port.getName()), this.portMap.get(port),
					AnalysisEditorGraphBean.simpleEscape(port.getName())));
		}

		return builder.toString();
	}

	private String assembleGraphString(final MIAnalysisComponent component) {
		final String name = component.getName();
		final String className = component.getClassname();
		final String shortName = className.substring(className.lastIndexOf('.') + 1);

		return String.format(JS_TEMPLATE_NODE, "id" + this.componentMap.get(component),
				AnalysisEditorGraphBean.simpleEscape(name), AnalysisEditorGraphBean.simpleEscape(shortName),
				AnalysisEditorGraphBean.simpleEscape(className));
	}

	private void addConnections(final EList<MIPlugin> plugins) {
		// Add the connections between plugins
		for (final MIPlugin plugin : plugins) {
			for (final MIOutputPort outputPort : plugin.getOutputPorts()) {
				for (final MIInputPort subscriber : outputPort.getSubscribers()) {
					this.addConnection(plugin, subscriber.getParent(), outputPort, subscriber);
				}
			}
		}
		// Add the connections between plugins and repositories
		for (final MIPlugin plugin : plugins) {
			for (final MIRepositoryConnector repositoryConnector : plugin.getRepositories()) {
				if (repositoryConnector.getRepository() != null) {
					this.addConnection(plugin, repositoryConnector.getRepository(), repositoryConnector);
				}
			}
		}
	}

	private void addComponents(final Iterable<? extends MIAnalysisComponent> components) {
		for (final MIAnalysisComponent component : components) {
			this.addComponent(component);
		}
	}

	private void addComponent(final MIAnalysisComponent component) {
		if (component instanceof MIReader) {
			this.addComponent((MIReader) component);
		} else if (component instanceof MIFilter) {
			this.addComponent((MIFilter) component);
		} else if (component instanceof MIRepository) {
			this.addComponent((MIRepository) component);
		}
	}

	private static final String simpleEscape(final String str) {
		if (str != null) {
			return str.replaceAll("\'", "");
		} else {
			return str;
		}
	}

}
