/*******************************************************************************
 * Copyright (c) 2003, 2010 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.draw2d.graph;

import java.util.Iterator;

import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Insets;

/**
 * A node in a DirectedGraph. A node has 0 or more incoming and outgoing
 * {@link Edge}s. A node is given a width and height by the client. When a
 * layout places the node in the graph, it will determine the node's x and y
 * location. It may also modify the node's height.
 * 
 * A node represents both the <EM>input</EM> and the <EM>output</EM> for a
 * layout algorithm. The following fields are used as input to a graph layout:
 * <UL>
 * <LI>{@link #width} - the node's width.
 * <LI>{@link #height} - the node's height.
 * <LI>{@link #outgoing} - the node's outgoing edges.
 * <LI>{@link #incoming} - the node's incoming edges.
 * <LI>padding - the amount of space to be left around the outside of the node.
 * <LI>{@link #incomingOffset} - the default attachment point for incoming
 * edges.
 * <LI>{@link #outgoingOffset} - the default attachment point for outgoing
 * edges.
 * <LI>parent - the parent subgraph containing this node.
 * </UL>
 * <P>
 * The following fields are calculated by a graph layout and comprise the
 * <EM>output</EM>:
 * <UL>
 * <LI>{@link #x} - the node's x location
 * <LI>{@link #y} - the node's y location
 * <LI>{@link #height} - the node's height may be stretched to match the height
 * of other nodes
 * </UL>
 * 
 * @author Randy Hudson
 * @since 2.1.2
 */
public class Node {

	Node left, right;

	Object workingData[] = new Object[3];
	int workingInts[] = new int[4];

	/**
	 * Clients may use this field to mark the Node with an arbitrary data
	 * object.
	 */
	public Object data;

	// used by various graph visitors
	boolean flag;

	/**
	 * The height of this node. This value should be set prior to laying out the
	 * directed graph. Depending on the layout rules, a node's height may be
	 * expanded to match the height of other nodes around it.
	 */
	public int height = 40;

	/**
	 * @deprecated use {@link #setRowConstraint(int)} and
	 *             {@link #getRowConstraint()}
	 */
	public int rowOrder = -1;

	/**
	 * The edges for which this node is the target.
	 */
	public EdgeList incoming = new EdgeList();

	/**
	 * The default attachment point for incoming edges. <code>-1</code>
	 * indicates that the node's horizontal center should be used.
	 */
	public int incomingOffset = -1;

	// A non-decreasing number given to consecutive nodes in a Rank.
	int index;

	// Used in Compound graphs to quickly determine whether a node is inside a
	// subgraph.
	int nestingIndex = -1;

	/**
	 * The edges for which this node is the source.
	 */
	public EdgeList outgoing = new EdgeList();

	Insets padding;
	private Subgraph parent;
	int rank;

	/**
	 * @deprecated for internal use only
	 */
	public double sortValue;

	/**
	 * The node's outgoing offset attachment point.
	 */
	public int outgoingOffset = -1;

	/**
	 * The node's width. The default value is 50.
	 */
	public int width = 50;

	/**
	 * The node's x coordinate.
	 */
	public int x;
	/**
	 * The node's y coordinate.
	 */
	public int y;

	/**
	 * Constructs a new node.
	 */
	public Node() {
	}

	/**
	 * Constructs a node with the given data object
	 * 
	 * @param data
	 *            an arbitrary data object
	 */
	public Node(Object data) {
		this(data, null);
	}

	/**
	 * Constructs a node inside the given subgraph.
	 * 
	 * @param parent
	 *            the parent subgraph
	 */
	public Node(Subgraph parent) {
		this(null, parent);
	}

	/**
	 * Constructs a node with the given data object and parent subgraph. This
	 * node is added to the set of members for the parent subgraph
	 * 
	 * @param data
	 *            an arbitrary data object
	 * @param parent
	 *            the parent subgraph or <code>null</code>
	 */
	public Node(Object data, Subgraph parent) {
		this.data = data;
		this.parent = parent;
		if (parent != null)
			parent.addMember(this);
	}

	/**
	 * Returns the incoming attachment point. This is the distance from the left
	 * edge to the default incoming attachment point for edges. Each incoming
	 * edge may have it's own attachment setting which takes priority over this
	 * default one.
	 * 
	 * @return the incoming offset
	 */
	public int getOffsetIncoming() {
		if (incomingOffset == -1)
			return width / 2;
		return incomingOffset;
	}

	/**
	 * Returns the outgoing attachment point. This is the distance from the left
	 * edge to the default outgoing attachment point for edges. Each outgoing
	 * edge may have it's own attachment setting which takes priority over this
	 * default one.
	 * 
	 * @return the outgoing offset
	 */
	public int getOffsetOutgoing() {
		if (outgoingOffset == -1)
			return width / 2;
		return outgoingOffset;
	}

	/**
	 * Returns the padding for this node or <code>null</code> if the default
	 * padding for the graph should be used.
	 * 
	 * @return the padding or <code>null</code>
	 */
	public Insets getPadding() {
		return padding;
	}

	/**
	 * Returns the parent Subgraph or <code>null</code> if there is no parent.
	 * Subgraphs are only for use in {@link CompoundDirectedGraphLayout}.
	 * 
	 * @return the parent or <code>null</code>
	 */
	public Subgraph getParent() {
		return parent;
	}

	/**
	 * For internal use only. Returns <code>true</code> if the given node is
	 * equal to this node. This method is implemented for consitency with
	 * Subgraph.
	 * 
	 * @param node
	 *            the node in question
	 * @return <code>true</code> if nested
	 */
	boolean isNested(Node node) {
		return node == this;
	}

	/**
	 * Sets the padding. <code>null</code> indicates that the default padding
	 * should be used.
	 * 
	 * @param padding
	 *            an insets or <code>null</code>
	 */
	public void setPadding(Insets padding) {
		this.padding = padding;
	}

	/**
	 * Sets the parent subgraph. This method should not be called directly. The
	 * constructor will set the parent accordingly.
	 * 
	 * @param parent
	 *            the parent
	 */
	public void setParent(Subgraph parent) {
		this.parent = parent;
	}

	/**
	 * Sets the row sorting constraint for this node. By default, a node's
	 * constraint is <code>-1</code>. If two nodes have different values both >=
	 * 0, the node with the smaller constraint will be placed to the left of the
	 * other node. In all other cases no relative placement is guaranteed.
	 * 
	 * @param value
	 *            the row constraint
	 * @since 3.2
	 */
	public void setRowConstraint(int value) {
		this.rowOrder = value;
	}

	/**
	 * Returns the row constraint for this node.
	 * 
	 * @return the row constraint
	 * @since 3.2
	 */
	public int getRowConstraint() {
		return rowOrder;
	}

	/**
	 * Sets the size of this node to the given dimension.
	 * 
	 * @param size
	 *            the new size
	 * @since 3.2
	 */
	public void setSize(Dimension size) {
		width = size.width;
		height = size.height;
	}

	/**
	 * @see Object#toString()
	 */
	public String toString() {
		return "N(" + data + ")"; //$NON-NLS-1$ //$NON-NLS-2$
	}

	Iterator iteratorNeighbors() {
		return new Iterator() {
			int offset;
			EdgeList list = outgoing;

			public Object next() {
				Edge edge = list.getEdge(offset++);
				if (offset < list.size())
					return edge.opposite(Node.this);
				if (list == outgoing) {
					list = incoming;
					offset = 0;
				} else
					list = null;
				return edge.opposite(Node.this);
			}

			public boolean hasNext() {
				if (list == null)
					return false;
				if (offset < list.size())
					return true;
				if (list == outgoing) {
					list = incoming;
					offset = 0;
				}
				return offset < list.size();
			}

			public void remove() {
				throw new RuntimeException("Remove not supported"); //$NON-NLS-1$
			}
		};
	}

	/**
	 * Returns a reference to a node located left from this one
	 * 
	 * @return <code>Node</code> on the left from this one
	 * @since 3.4
	 */
	public Node getLeft() {
		return left;
	}

	/**
	 * Returns a reference to a node located right from this one
	 * 
	 * @return <code>Node</code> on the right from this one
	 * @since 3.4
	 */
	public Node getRight() {
		return right;
	}

}
