/*******************************************************************************
 * 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 org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PointList;

/**
 * A directed Edge joining a source and target Node. Edges indicate the
 * dependencies between nodes. An Edge provides the information needed to
 * perform a graph layout, and it stores the result of the layout in its various
 * field. Therefore, it functions both as input and output. The input data
 * consists of:
 * <UL>
 * <LI>{@link #source} - the source Node
 * <LI>{@link #target} - the target Node
 * <LI>{@link #delta} - the minimum number of rows the edge should span
 * <LI>{@link #weight} - a hint indicating this edge's importance
 * <LI>{@link #width} - the edge's width
 * <LI>{@link #padding} - the amount of space to leave on either side of the
 * edge
 * <LI>[{@link #offsetSource}] - the edge's attachment point at the source node
 * <LI>[{@link #offsetTarget}] - the edge's attachment point at the target node
 * </UL>
 * <P>
 * The output of a layout consists of bending longer edges, and potentially
 * inverting edges to remove cycles in the graph. The output consists of:
 * <UL>
 * <LI>{@link #vNodes} - the virtual nodes (if any) which make up the bendpoints
 * <LI>{@link #isFeedback} - <code>true</code> if the edge points backwards
 * </UL>
 * 
 * @author hudsonr
 * @since 2.1.2
 */
public class Edge {

	int cut;

	/**
	 * An arbitrary data field for use by clients.
	 */
	public Object data;

	/**
	 * The minimum rank separation between the source and target nodes. The
	 * default value is 1.
	 * 
	 * @deprecated use accessors instead
	 */
	public int delta = 1;

	/**
	 * The ending point.
	 * 
	 * @deprecated use {@link #getPoints()}
	 */
	public Point end;

	boolean flag;

	/**
	 * @deprecated INTERNAL field, use accessor method Indicates an edge was
	 *             inverted during the layout
	 */
	public boolean isFeedback = false;

	/**
	 * The edge's attachment point at the <em>source</em> node. The default
	 * value is -1, which indicates that the edge should use the node's default
	 * {@link Node#getOffsetOutgoing() outgoing} attachment point.
	 * 
	 * @deprecated use accessors instead
	 */
	public int offsetSource = -1;

	/**
	 * The edge's attachment point at the <em>target</em> node. The default
	 * value is -1, which indicates that the edge should use the node's default
	 * {@link Node#getOffsetIncoming() incoming} attachment point.
	 * 
	 * @deprecated use accessors instead
	 */
	public int offsetTarget = -1;

	/**
	 * The minimum amount of space to leave on both the left and right sides of
	 * the edge.
	 * 
	 * @deprecated use accessors instead
	 */
	public int padding = 10;

	private PointList points;

	/**
	 * The source Node.
	 */
	public Node source;
	/**
	 * The starting point.
	 * 
	 * @deprecated use {@link #getPoints()}
	 */
	public Point start;

	/**
	 * The target Node.
	 */
	public Node target;

	boolean tree;

	/**
	 * The virtual nodes used to bend edges which go across one or more ranks.
	 * Each virtual node is just a regular node which occupies some small amount
	 * of space on a row. It's width is equivalent to the edge's width. Clients
	 * can use each virtual node's location (x, y, width, and height) as the way
	 * to position an edge which spans multiple rows.
	 */
	public NodeList vNodes;
	/**
	 * A hint indicating how straight and short the edge should be relative to
	 * other edges in the graph. The default value is <code>1</code>.
	 */
	public int weight = 1;

	/**
	 * @deprecated use accessors instead
	 */
	public int width = 1;

	/**
	 * Constructs a new edge with the given source and target nodes. All other
	 * fields will have their default values.
	 * 
	 * @param source
	 *            the source Node
	 * @param target
	 *            the target Node
	 */
	public Edge(Node source, Node target) {
		this(null, source, target);
	}

	/**
	 * Constructs a new edge with the given source, target, delta, and weight.
	 * 
	 * @param source
	 *            the source Node
	 * @param target
	 *            the target Node
	 * @param delta
	 *            the minimum edge span
	 * @param weight
	 *            the weight hint
	 */
	public Edge(Node source, Node target, int delta, int weight) {
		this(source, target);
		this.delta = delta;
		this.weight = weight;
	}

	/**
	 * Constructs a new edge with the given data object, source, and target
	 * node.
	 * 
	 * @param data
	 *            an arbitrary data object
	 * @param source
	 *            the source node
	 * @param target
	 *            the target node
	 */
	public Edge(Object data, Node source, Node target) {
		this.data = data;
		this.source = source;
		this.target = target;
		source.outgoing.add(this);
		target.incoming.add(this);
	}

	/**
	 * Returns the delta value. The delta is the minimum rank separation for the
	 * edge's source and target nodes.
	 * 
	 * @return the delta.
	 * @since 3.2
	 */
	public int getDelta() {
		return delta;
	}

	/**
	 * For internal use only. Returns the index of the {@link Node} (or
	 * {@link VirtualNode}) on this edge at the given rank. If this edge doesn't
	 * have a node at the given rank, -1 is returned.
	 * 
	 * @param rank
	 *            the rank
	 * @return the edges index at the given rank
	 */
	int getIndexForRank(int rank) {
		if (source.rank == rank)
			return source.index;
		if (target.rank == rank)
			return target.index;
		if (vNodes != null)
			return vNodes.getNode(rank - source.rank - 1).index;
		return -1;
	}

	/**
	 * For internal use only. Returns the target node's row minus the source
	 * node's row.
	 * 
	 * @return the distance from the source to target ranks
	 */
	public int getLength() {
		return (target.rank - source.rank);
	}

	public int getPadding() {
		return padding;
	}

	/**
	 * Returns the path connecting the edge's source and target.
	 * 
	 * @return a point list
	 * @since 3.2
	 */
	public PointList getPoints() {
		return points;
	}

	int getSlack() {
		return (target.rank - source.rank) - delta;
	}

	/**
	 * Returns the effective source offset for this edge. The effective source
	 * offset is either the {@link #offsetSource} field, or the source node's
	 * default outgoing offset if that field's value is -1.
	 * 
	 * @return the source offset
	 */
	public int getSourceOffset() {
		if (offsetSource != -1)
			return offsetSource;
		return source.getOffsetOutgoing();
	}

	/**
	 * Returns the effective target offset for this edge. The effective target
	 * offset is either the {@link #offsetTarget} field, or the target node's
	 * default incoming offset if that field's value is -1.
	 * 
	 * @return the target offset
	 */
	public int getTargetOffset() {
		if (offsetTarget != -1)
			return offsetTarget;
		return target.getOffsetIncoming();
	}

	public int getWidth() {
		return width;
	}

	/**
	 * Swaps the source and target nodes. If any positional data has been
	 * calculated, it is inverted as well to reflect the new direction.
	 * 
	 * @since 2.1.2
	 */
	public void invert() {
		source.outgoing.remove(this);
		target.incoming.remove(this);

		Node oldTarget = target;
		target = source;
		source = oldTarget;

		int temp = offsetSource;
		offsetSource = offsetTarget;
		offsetTarget = temp;

		target.incoming.add(this);
		source.outgoing.add(this);

		if (points != null)
			points.reverse();

		if (vNodes != null) {
			NodeList newVNodes = new NodeList();
			for (int j = vNodes.size() - 1; j >= 0; j--) {
				newVNodes.add(vNodes.getNode(j));
			}
			vNodes = newVNodes;
		}

		if (start != null) {
			Point pt = start;
			start = end;
			end = pt;
		}
	}

	/**
	 * Returns <code>true</code> if the edge was a feedback edge. The layout
	 * algorithm may invert one or more edges to remove all cycles from the
	 * input. The set of edges that are inverted are referred to as the
	 * "feedback" set.
	 * 
	 * @return <code>true</code> if the edge is feedback
	 * @since 3.2
	 */
	public boolean isFeedback() {
		return isFeedback;
	}

	/**
	 * For internal use only. Returns the node opposite the given node on this
	 * edge.
	 * 
	 * @param end
	 *            one end
	 * @return the other end
	 */
	public Node opposite(Node end) {
		if (source == end)
			return target;
		return source;
	}

	/**
	 * Sets the delta value.
	 * 
	 * @param delta
	 *            the new delta value
	 * @since 3.2
	 */
	public void setDelta(int delta) {
		this.delta = delta;
	}

	/**
	 * Sets the padding for this edge.
	 * 
	 * @param padding
	 *            the padding
	 * @since 3.2
	 */
	public void setPadding(int padding) {
		this.padding = padding;
	}

	void setPoints(PointList points) {
		this.points = points;
		start = points.getFirstPoint();
		end = points.getLastPoint();
	}

	/**
	 * Sets the source node and adds this edge to the new source's outgoing
	 * edges. If the source node is previously set, removes this edge from the
	 * old source's outgoing edges.
	 * 
	 * @param node
	 *            the new source
	 * @since 3.2
	 */
	public void setSource(Node node) {
		if (source == node)
			return;
		if (source != null)
			source.outgoing.remove(this);
		source = node;
		if (source != null)
			source.outgoing.add(this);
	}

	public void setSourceOffset(int offset) {
		this.offsetSource = offset;
	}

	/**
	 * Sets the target node and adds this edge to the new target's incoming
	 * edges. If the target node is previously set, removes this edge from the
	 * old target's incoming edges.
	 * 
	 * @param node
	 *            the new target
	 * @since 3.2
	 */
	public void setTarget(Node node) {
		if (target == node)
			return;
		if (target != null)
			target.incoming.remove(this);
		target = node;
		if (target != null)
			target.incoming.add(this);
	}

	public void setTargetOffset(int offset) {
		this.offsetTarget = offset;
	}

	/**
	 * Sets the width of the edge.
	 * 
	 * @param width
	 *            the new width
	 * @since 3.2
	 */
	public void setWidth(int width) {
		this.width = width;
	}

}
