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

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

import kieker.webgui.service.impl.utility.KielerInterface;

import de.cau.cs.kieler.core.kgraph.KNode;

/**
 * Objects of this class represent nodes within a {@link Graph}, that may or may not have any number
 * of {@link Port}s. These {@link Port}s are divided into input {@link Port}s, positioned to the
 * left of the node; repository {@link Port}s, positioned to the right of the node; and output ports
 * that are positioned below repository {@link Port}s. Any of these {@link Port}s are optional.
 * If the {@link Node} has no {@link Port}s, KIELER will generate it's own {@link Port}s, where it sees
 * fit.
 * 
 * @author Robin Weiss, Florian Fittkau
 * 
 */
public class Node {

	private final KNode kielerNode;
	private final String id;
	private final boolean nodeFamily;
	private final List<Port> inputPorts;
	private final List<Port> repoPorts;
	private final List<Port> outputPorts;
	private final Point position;
	private int width;
	private int height;

	/**
	 * Class constructor. Creates a {@link Node} and assigns a generated {@link KNode} to the graph.
	 * 
	 * @param graph
	 *            the graph that contains the node
	 * @param id
	 *            a unique string representing the node
	 * @param width
	 *            the width of the node
	 * @param height
	 *            the height of the node
	 * @param hasPorts
	 *            true if at least one port exists
	 */
	public Node(final Graph graph, final String id, final int width, final int height, final boolean hasPorts) {
		this.id = id;
		this.width = width;
		this.height = height;
		this.nodeFamily = hasPorts;

		this.inputPorts = new ArrayList<Port>();
		this.repoPorts = new ArrayList<Port>();
		this.outputPorts = new ArrayList<Port>();

		this.kielerNode = KielerInterface.makeKielerNode(graph, width, height, this.nodeFamily);
		this.kielerNode.setParent(graph.getKielerGraph());

		this.position = new Point(0, 0);
	}

	/**
	 * Creates a set of input {@link Port}s to the node.
	 * 
	 * @param graph
	 *            the graph that contains the node
	 * @param portStartY
	 *            the y-coordinate of the first {@link Port}, relative to the top border of the {@link Node}
	 * @param portCount
	 *            the number of ports that are added
	 */
	public void addInputPorts(final Graph graph, final int portStartY, final int portCount) {
		final int verticalSpace = graph.getPortHeight() * 2;
		final int portX = -graph.getPortHeight();
		int internalPortCount = portCount;
		int portY = portStartY;
		int portIndex = this.outputPorts.size() + this.repoPorts.size();

		while (internalPortCount > 0) {
			final Port port = new Port(graph, this, portIndex, portX, portY);
			this.inputPorts.add(port);

			portY += verticalSpace;
			portIndex++;
			internalPortCount--;
		}
	}

	/**
	 * Creates a set of repository {@link Port}s to the node.
	 * 
	 * @param graph
	 *            the graph that contains the node
	 * @param portStartY
	 *            the y-coordinate of the first {@link Port}, relative to the top border of the {@link Node}
	 * @param portCount
	 *            the number of ports that are added
	 */
	public void addRepositoryPorts(final Graph graph, final int portStartY, final int portCount) {
		final int verticalSpace = graph.getPortHeight() * 2;
		final int portX = this.width + graph.getPortHeight();
		int portY = portStartY;
		int internalPortCount = portCount;
		int portIndex = this.outputPorts.size() + this.inputPorts.size();

		while (internalPortCount > 0) {
			final Port port = new Port(graph, this, portIndex, portX, portY);
			this.repoPorts.add(port);

			portY += verticalSpace;
			portIndex++;
			internalPortCount--;
		}
	}

	/**
	 * Creates a set of output {@link Port}s to the node.
	 * 
	 * @param graph
	 *            the graph that contains the node
	 * @param portStartY
	 *            the y-coordinate of the first {@link Port}, relative to the top border of the {@link Node}
	 * @param portCount
	 *            the number of ports that are added
	 */
	public void addOutputPorts(final Graph graph, final int portStartY, final int portCount) {
		final int verticalSpace = graph.getPortHeight() * 2;
		final int portX = this.width + graph.getPortHeight();
		int portY = portStartY;
		int internalPortCount = portCount;
		int portIndex = this.inputPorts.size() + this.repoPorts.size();

		while (internalPortCount > 0) {
			final Port port = new Port(graph, this, portIndex, portX, portY);
			this.repoPorts.add(port);

			portY += verticalSpace;
			portIndex++;
			internalPortCount--;
		}
	}

	/**
	 * Looks for a port with the specified index. Port indices should start at 0 for the first
	 * input {@link Port}, progress through input {@link Port}s, then repository {@link Port}s and
	 * finally through output{@link Port}s.
	 * 
	 * @param portIndex
	 *            the index of the {@link Port}
	 * @return the {@link Port} with the specified index if it exists or null if it does not
	 */
	public Port getPort(final int portIndex) {
		if (!this.nodeFamily) {
			return null;
		}

		int length = this.inputPorts.size();

		int internalPortIndex = portIndex;
		if ((internalPortIndex >= 0) && (internalPortIndex < length)) {
			return this.inputPorts.get(internalPortIndex);
		}

		internalPortIndex -= length;
		length = this.repoPorts.size();
		if ((internalPortIndex >= 0) && (internalPortIndex < length)) {
			return this.repoPorts.get(internalPortIndex);
		}

		internalPortIndex -= length;
		length = this.outputPorts.size();
		if ((internalPortIndex >= 0) && (internalPortIndex < length)) {
			return this.outputPorts.get(internalPortIndex);
		}

		return null;
	}

	public KNode getKielerNode() {
		return this.kielerNode;
	}

	public String getId() {
		return this.id;
	}

	public boolean isNodeFamily() {
		return this.nodeFamily;
	}

	public List<Port> getInputPorts() {
		return this.inputPorts;
	}

	public List<Port> getRepoPorts() {
		return this.repoPorts;
	}

	public List<Port> getOutputPorts() {
		return this.outputPorts;
	}

	public Point getPosition() {
		return this.position;
	}

	/**
	 * Resizes the {@link Node}.
	 * 
	 * @param w
	 *            the new width of the {@link Node}
	 * @param h
	 *            the new height of the {@link Node}
	 */
	public void setDimensions(final int w, final int h) {
		this.width = w;
		this.height = h;
	}

	/**
	 * 
	 * @return the width of the {@link Node}
	 */
	public int getWidth() {
		return this.width;
	}

	/**
	 * 
	 * @return the height of the node
	 */
	public int getHeight() {
		return this.height;
	}
}
