/***************************************************************************
 * 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.service.impl.utility.converter;

import kieker.webgui.common.exception.GraphLayoutException;
import kieker.webgui.service.impl.utility.model.Edge;
import kieker.webgui.service.impl.utility.model.Graph;
import kieker.webgui.service.impl.utility.model.Node;
import kieker.webgui.service.impl.utility.model.Port;

/**
 * Provides a method for converting strings to a {@link Graph}.
 * 
 * @author Robin Weiss, Nils Christian Ehmke, Florian Fittkau
 * 
 */
public final class GraphStringConverter {

	private GraphStringConverter() {
		// No code necessary
	}

	/**
	 * Converts strings to a {@link Graph} Object.
	 * 
	 * @param nodeStr
	 *            a String with {@link Node} information
	 * @param edgeStr
	 *            a String with {@link Edge} information
	 * @see GraphLayoutService
	 * @return
	 *         a {@link Graph} object
	 * 
	 * @throws GraphLayoutException
	 *             If something went wrong during the layouting.
	 */
	public static Graph convert(final String nodeStr, final String edgeStr) throws GraphLayoutException {

		// look if valid information exists
		final int firstSemicolon = nodeStr.indexOf(';');
		if (firstSemicolon == -1) {
			throw new GraphLayoutException("Empty Graph");
		}

		// construct graph using the parsed scale factor
		final Graph graph = new Graph(Integer.parseInt(nodeStr.substring(0, firstSemicolon)));

		// construct and add nodes
		final String[] nodeInfos = GraphStringConverter.correctEmptyArray(nodeStr.substring(firstSemicolon + 1).split(";"));
		GraphStringConverter.addNodes(graph, nodeInfos);

		// construct edges and add them to nodes
		final String[] edgeInfos = GraphStringConverter.correctEmptyArray(edgeStr.split(";"));
		GraphStringConverter.addEdges(graph, edgeInfos);

		return graph;
	}

	/**
	 * 
	 * @param arr
	 *            a split string array
	 * @return
	 *         an empty String array if its first component is empty, else the input array
	 */
	private static String[] correctEmptyArray(final String[] arr) {
		if ((arr.length == 1) && arr[0].isEmpty()) {
			return new String[0];
		} else {
			return arr;
		}
	}

	/**
	 * Parses each of the strings, that contain node information and constructs {@link Node}s out of it.
	 * 
	 * @param graph
	 *            the {@link Graph} to which the {@link Node}s are added
	 * @param nodeInfos
	 *            an array of node information strings
	 * @see GraphLayoutService
	 */
	private static void addNodes(final Graph graph, final String[] nodeInfos) {

		for (final String nodeInfo : nodeInfos) {

			// get node properties
			final String[] splitNodeInfo = nodeInfo.split(" ");
			final String id = splitNodeInfo[0];
			final int width = Integer.parseInt(splitNodeInfo[2]);
			final int height = Integer.parseInt(splitNodeInfo[3]);
			final boolean nodeFamily = "f".equals(splitNodeInfo[4]);
			final Node node = new Node(graph, id, width, height, nodeFamily);

			if (node.isNodeFamily()) {
				int portCount = Integer.parseInt(splitNodeInfo[5]);
				int portStartY = Integer.parseInt(splitNodeInfo[6]);
				node.addInputPorts(graph, portStartY, portCount);

				portCount = Integer.parseInt(splitNodeInfo[7]);
				portStartY = Integer.parseInt(splitNodeInfo[8]);
				node.addRepositoryPorts(graph, portStartY, portCount);

				portCount = Integer.parseInt(splitNodeInfo[9]);
				portStartY = Integer.parseInt(splitNodeInfo[10]);
				node.addOutputPorts(graph, portStartY, portCount);
			}

			graph.addNode(node);
		}
	}

	/**
	 * Parses each of the strings, that contain edge information and constructs {@link Edge}s out of it.
	 * 
	 * @param graph
	 *            the {@link Graph} to which the {@link Edge}s are added
	 * @param edgeInfos
	 *            an array of edge information strings
	 * @see GraphLayoutService
	 */
	private static void addEdges(final Graph graph, final String[] edgeInfos) {
		for (final String edgeInfo : edgeInfos) {
			final String[] splitEdgeInfo = edgeInfo.split(" ");

			final String sourceId = splitEdgeInfo[0];
			final Node sourceNode = graph.getNode(sourceId);
			final int sourcePortIndex = Integer.parseInt(splitEdgeInfo[1]);
			final Port sourcePort = sourceNode.getPort(sourcePortIndex);

			final String targetId = splitEdgeInfo[2];
			final Node targetNode = graph.getNode(targetId);
			final int targetPortIndex = Integer.parseInt(splitEdgeInfo[3]);
			final Port targetPort = targetNode.getPort(targetPortIndex);

			final Edge edge = new Edge(sourceNode, targetNode, sourcePort, targetPort);
			graph.addEdge(edge);
		}
	}

}
