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

import java.util.List;

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;

import de.cau.cs.kieler.core.alg.BasicProgressMonitor;
import de.cau.cs.kieler.core.kgraph.KEdge;
import de.cau.cs.kieler.core.kgraph.KNode;
import de.cau.cs.kieler.core.kgraph.KPort;
import de.cau.cs.kieler.kiml.AbstractLayoutProvider;
import de.cau.cs.kieler.kiml.klayoutdata.KEdgeLayout;
import de.cau.cs.kieler.kiml.klayoutdata.KPoint;
import de.cau.cs.kieler.kiml.klayoutdata.KShapeLayout;
import de.cau.cs.kieler.kiml.options.EdgeRouting;
import de.cau.cs.kieler.kiml.options.LayoutOptions;
import de.cau.cs.kieler.kiml.options.PortConstraints;
import de.cau.cs.kieler.kiml.util.KimlUtil;

/**
 * An interface between our constrained graph representation and the KIELER graph model.
 * 
 * @author Robin Weiss, Nils Christian Ehmke, Florian Fittkau
 * 
 */
public final class KielerInterface {

	private KielerInterface() {
		// No code necessary
	}

	/**
	 * Calls a layout procedure on the {@link Graph}s KIELER representation and updates
	 * our graph representation.
	 * 
	 * @param graph
	 *            the {@link Graph} that is to be layouted
	 * @param layoutProvider
	 *            KIELER layout provider
	 * 
	 *            * @throws GraphLayoutException
	 *            If something went wrong during the layouting.
	 */
	public static void applyLayout(final Graph graph, final AbstractLayoutProvider layoutProvider) throws GraphLayoutException {
		layoutProvider.doLayout(graph.getKielerGraph(), new BasicProgressMonitor());
		KielerInterface.updateGraphWithResults(graph);
	}

	/**
	 * 
	 * @return a {@link KNode} that acts as a parent for all added nodes
	 */
	public static KNode makeKielerGraph() {
		final KNode kGraph = KimlUtil.createInitializedNode();

		final KShapeLayout layout = kGraph.getData(KShapeLayout.class);
		layout.setProperty(LayoutOptions.EDGE_ROUTING, EdgeRouting.ORTHOGONAL);
		return kGraph;
	}

	/**
	 * Constructs a {@link KNode} and adds it to the {@link Graph}s parent node.
	 * 
	 * @param graph
	 *            the {@link Graph} that contains the node
	 * @param width
	 *            the width of the node
	 * @param height
	 *            the height of the node
	 * @param nodeFamily
	 *            if true, {@link KPort}s can be added to the node
	 * @return
	 *         the resulting {@link KNode}
	 */
	public static KNode makeKielerNode(final Graph graph, final int width, final int height, final boolean nodeFamily) {
		final KNode kielerNode = KimlUtil.createInitializedNode();
		kielerNode.setParent(graph.getKielerGraph());

		final KShapeLayout layout = kielerNode.getData(KShapeLayout.class);
		layout.setWidth(width);
		layout.setHeight(height);
		if (nodeFamily) {
			layout.setProperty(LayoutOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_POS);
		}
		return kielerNode;
	}

	/**
	 * Constructs a {@link KPort} and adds it to a {@link KNode} representation of a {@link Node}.
	 * 
	 * @param graph
	 *            the {@link Graph} that contains the port
	 * @param node
	 *            the {@link Node} to which the port is added
	 * @param x
	 *            the x-coordinate of the port, relative to the left border of the node
	 * @param y
	 *            the y-coordinate of the port, relative to the top border of the node
	 * @return
	 *         the generated {@link KPort}
	 */
	public static KPort makeKielerPort(final Graph graph, final Node node, final int x, final int y) {
		final int dimHalf = graph.getPortHeight();
		final int dim = dimHalf * 2;

		final KPort kielerPort = KimlUtil.createInitializedPort();
		kielerPort.setNode(node.getKielerNode());

		final KShapeLayout portLayout = kielerPort.getData(KShapeLayout.class);
		portLayout.setWidth(dim);
		portLayout.setHeight(dimHalf);
		portLayout.setXpos(x);
		portLayout.setYpos(y);

		return kielerPort;
	}

	/**
	 * Constructs a directed {@link KEdge} between the KIELER representation of two nodes.
	 * 
	 * @param source
	 *            the source {@link Node} of the edge
	 * @param target
	 *            the target {@link Node} of the edge
	 * @param sourcePort
	 *            the {@link Port} of the source {@link Node}
	 * @param targetPort
	 *            the {@link Port} of the target {@link Node}
	 * @return
	 *         the generated {@link KEdge}
	 */
	public static KEdge makeKielerEdge(final Node source, final Node target, final Port sourcePort, final Port targetPort) {
		final KEdge kielerEdge = KimlUtil.createInitializedEdge();
		kielerEdge.setSource(source.getKielerNode());
		kielerEdge.setTarget(target.getKielerNode());

		if (targetPort != null) {
			kielerEdge.setTargetPort(targetPort.getKielerPort());
			targetPort.getKielerPort().getEdges().add(kielerEdge);
		}
		if (sourcePort != null) {
			kielerEdge.setSourcePort(sourcePort.getKielerPort());
			sourcePort.getKielerPort().getEdges().add(kielerEdge);
		}
		return kielerEdge;
	}

	/**
	 * Applies the changes that were made on the KIELER representation of the graph to
	 * our representation.
	 * 
	 * @param graph
	 *            the {@link Graph} that needs to be updated
	 */
	private static void updateGraphWithResults(final Graph graph) {
		for (final Node node : graph.getNodes()) {
			final KShapeLayout layout = node.getKielerNode().getData(KShapeLayout.class);

			node.getPosition().setValue(layout.getXpos(), layout.getYpos());
			node.setDimensions(Math.round(layout.getWidth()), Math.round(layout.getHeight()));
		}
		KielerInterface.addBendPointsInAbsoluteCoordinates(graph);
	}

	/**
	 * Reads bend point information from all{@link KEdge}s of a {@link Graph} and adds it to our representation.
	 * 
	 * @param graph
	 *            the {@link Graph} of which the edges are updated with bend points
	 */
	private static void addBendPointsInAbsoluteCoordinates(final Graph graph) {
		for (final Edge edge : graph.getEdges()) {
			final List<KPoint> bendPoints = edge.getKielerEdge().getData(KEdgeLayout.class).getBendPoints();

			final float pOffsetX = 0f;
			final float pOffsetY = 0f;

			for (final KPoint bendPoint : bendPoints) {
				final float x = bendPoint.getX() + pOffsetX;
				final float y = bendPoint.getY() + pOffsetY;
				edge.addBendPoint(x, y);
			}
		}
	}
}
