/**
 * $Id: mxEdgeStyle.java,v 1.35 2010/06/10 08:57:30 gaudenz Exp $
 * Copyright (c) 2007, Gaudenz Alder
 */
package com.mxgraph.view;

import java.util.List;

import com.mxgraph.model.mxGeometry;
import com.mxgraph.model.mxIGraphModel;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxPoint;
import com.mxgraph.util.mxUtils;

/**
 * Provides various edge styles to be used as the values for
 * mxConstants.STYLE_EDGE in a cell style. Alternatevly, the mxConstants.
 * EDGESTYLE_* constants can be used to reference an edge style via the
 * mxStyleRegistry.
 */
public class mxEdgeStyle
{

	/**
	 * Defines the requirements for an edge style function.
	 */
	public interface mxEdgeStyleFunction
	{

		/**
		 * Implements an edge style function. At the time the function is called, the result
		 * array contains a placeholder (null) for the first absolute point,
		 * that is, the point where the edge and source terminal are connected.
		 * The implementation of the style then adds all intermediate waypoints
		 * except for the last point, that is, the connection point between the
		 * edge and the target terminal. The first ant the last point in the
		 * result array are then replaced with mxPoints that take into account
		 * the terminal's perimeter and next point on the edge.
		 * 
		 * @param state Cell state that represents the edge to be updated.
		 * @param source Cell state that represents the source terminal.
		 * @param target Cell state that represents the target terminal.
		 * @param points List of relative control points.
		 * @param result Array of points that represent the actual points of the
		 * edge.
		 */
		void apply(mxCellState state, mxCellState source, mxCellState target,
				List<mxPoint> points, List<mxPoint> result);

	}

	/**
	 * Provides an entity relation style for edges (as used in database
	 * schema diagrams).
	 */
	public static mxEdgeStyleFunction EntityRelation = new mxEdgeStyleFunction()
	{

		/* (non-Javadoc)
		 * @see com.mxgraph.view.mxEdgeStyle.mxEdgeStyleFunction#apply(com.mxgraph.view.mxCellState, com.mxgraph.view.mxCellState, com.mxgraph.view.mxCellState, java.util.List, java.util.List)
		 */
		public void apply(mxCellState state, mxCellState source,
				mxCellState target, List<mxPoint> points, List<mxPoint> result)
		{
			mxGraphView view = state.getView();
			mxIGraphModel model = view.getGraph().getModel();
			double segment = mxUtils.getDouble(state.getStyle(),
					mxConstants.STYLE_SEGMENT, mxConstants.ENTITY_SEGMENT)
					* state.view.getScale();

			mxPoint p0 = state.getAbsolutePoint(0);
			mxPoint pe = state
					.getAbsolutePoint(state.getAbsolutePointCount() - 1);

			boolean isSourceLeft = false;

			if (p0 != null)
			{
				source = new mxCellState();
				source.setX(p0.getX());
				source.setY(p0.getY());
			}
			else if (source != null)
			{
				mxGeometry sourceGeometry = model.getGeometry(source.cell);

				if (sourceGeometry.isRelative())
				{
					isSourceLeft = sourceGeometry.getX() <= 0.5;
				}
				else if (target != null)
				{
					isSourceLeft = target.getX() + target.getWidth() < source
							.getX();
				}
			}

			boolean isTargetLeft = true;

			if (pe != null)
			{
				target = new mxCellState();
				target.setX(pe.getX());
				target.setY(pe.getY());
			}
			else if (target != null)
			{
				mxGeometry targetGeometry = model.getGeometry(target.cell);

				if (targetGeometry.isRelative())
				{
					isTargetLeft = targetGeometry.getX() <= 0.5;
				}
				else if (source != null)
				{
					isTargetLeft = source.getX() + source.getWidth() < target
							.getX();
				}
			}

			if (source != null && target != null)
			{
				double x0 = (isSourceLeft) ? source.getX() : source.getX()
						+ source.getWidth();
				double y0 = view.getRoutingCenterY(source);

				double xe = (isTargetLeft) ? target.getX() : target.getX()
						+ target.getWidth();
				double ye = view.getRoutingCenterY(target);

				double seg = segment;

				double dx = (isSourceLeft) ? -seg : seg;
				mxPoint dep = new mxPoint(x0 + dx, y0);
				result.add(dep);

				dx = (isTargetLeft) ? -seg : seg;
				mxPoint arr = new mxPoint(xe + dx, ye);

				// Adds intermediate points if both go out on same side
				if (isSourceLeft == isTargetLeft)
				{
					double x = (isSourceLeft) ? Math.min(x0, xe) - segment
							: Math.max(x0, xe) + segment;
					result.add(new mxPoint(x, y0));
					result.add(new mxPoint(x, ye));
				}
				else if ((dep.getX() < arr.getX()) == isSourceLeft)
				{
					double midY = y0 + (ye - y0) / 2;
					result.add(new mxPoint(dep.getX(), midY));
					result.add(new mxPoint(arr.getX(), midY));
				}

				result.add(arr);
			}
		}
	};

	/**
	 * Provides a self-reference, aka. loop.
	 */
	public static mxEdgeStyleFunction Loop = new mxEdgeStyleFunction()
	{

		/* (non-Javadoc)
		 * @see com.mxgraph.view.mxEdgeStyle.mxEdgeStyleFunction#apply(com.mxgraph.view.mxCellState, com.mxgraph.view.mxCellState, com.mxgraph.view.mxCellState, java.util.List, java.util.List)
		 */
		public void apply(mxCellState state, mxCellState source,
				mxCellState target, List<mxPoint> points, List<mxPoint> result)
		{
			if (source != null)
			{
				mxGraphView view = state.getView();
				mxGraph graph = view.getGraph();
				mxPoint pt = (points != null && points.size() > 0) ? points
						.get(0) : null;

				if (pt != null)
				{
					pt = view.transformControlPoint(state, pt);

					if (source.contains(pt.getX(), pt.getY()))
					{
						pt = null;
					}
				}

				double x = 0;
				double dx = 0;
				double y = 0;
				double dy = 0;

				double seg = mxUtils.getDouble(state.getStyle(),
						mxConstants.STYLE_SEGMENT, graph.getGridSize())
						* view.getScale();
				String dir = mxUtils
						.getString(state.getStyle(),
								mxConstants.STYLE_DIRECTION,
								mxConstants.DIRECTION_WEST);

				if (dir.equals(mxConstants.DIRECTION_NORTH)
						|| dir.equals(mxConstants.DIRECTION_SOUTH))
				{
					x = view.getRoutingCenterX(source);
					dx = seg;
				}
				else
				{
					y = view.getRoutingCenterY(source);
					dy = seg;
				}

				if (pt == null || pt.getX() < source.getX()
						|| pt.getX() > source.getX() + source.getWidth())
				{
					if (pt != null)
					{
						x = pt.getX();
						dy = Math.max(Math.abs(y - pt.getY()), dy);
					}
					else
					{
						if (dir.equals(mxConstants.DIRECTION_NORTH))
						{
							y = source.getY() - 2 * dx;
						}
						else if (dir.equals(mxConstants.DIRECTION_SOUTH))
						{
							y = source.getY() + source.getHeight() + 2 * dx;
						}
						else if (dir.equals(mxConstants.DIRECTION_EAST))
						{
							x = source.getX() - 2 * dy;
						}
						else
						{
							x = source.getX() + source.getWidth() + 2 * dy;
						}
					}
				}
				else if (pt != null)
				{
					x = view.getRoutingCenterX(source);
					dx = Math.max(Math.abs(x - pt.getX()), dy);
					y = pt.getY();
					dy = 0;
				}

				result.add(new mxPoint(x - dx, y - dy));
				result.add(new mxPoint(x + dx, y + dy));
			}
		}
	};

	/**
	 * Uses either SideToSide or TopToBottom depending on the horizontal
	 * flag in the cell style. SideToSide is used if horizontal is true or
	 * unspecified.
	 */
	public static mxEdgeStyleFunction ElbowConnector = new mxEdgeStyleFunction()
	{

		/* (non-Javadoc)
		 * @see com.mxgraph.view.mxEdgeStyle.mxEdgeStyleFunction#apply(com.mxgraph.view.mxCellState, com.mxgraph.view.mxCellState, com.mxgraph.view.mxCellState, java.util.List, java.util.List)
		 */
		public void apply(mxCellState state, mxCellState source,
				mxCellState target, List<mxPoint> points, List<mxPoint> result)
		{
			mxPoint pt = (points != null && points.size() > 0) ? points.get(0)
					: null;

			boolean vertical = false;
			boolean horizontal = false;

			if (source != null && target != null)
			{
				if (pt != null)
				{
					double left = Math.min(source.getX(), target.getX());
					double right = Math.max(source.getX() + source.getWidth(),
							target.getX() + target.getWidth());

					double top = Math.min(source.getY(), target.getY());
					double bottom = Math.max(
							source.getY() + source.getHeight(), target.getY()
									+ target.getHeight());

					pt = state.getView().transformControlPoint(state, pt);

					vertical = pt.getY() < top || pt.getY() > bottom;
					horizontal = pt.getX() < left || pt.getX() > right;
				}
				else
				{
					double left = Math.max(source.getX(), target.getX());
					double right = Math.min(source.getX() + source.getWidth(),
							target.getX() + target.getWidth());

					vertical = left == right;

					if (!vertical)
					{
						double top = Math.max(source.getY(), target.getY());
						double bottom = Math.min(source.getY()
								+ source.getHeight(), target.getY()
								+ target.getHeight());

						horizontal = top == bottom;
					}
				}
			}

			if (!horizontal
					&& (vertical || mxUtils.getString(state.getStyle(),
							mxConstants.STYLE_ELBOW, "").equals(
							mxConstants.ELBOW_VERTICAL)))
			{
				mxEdgeStyle.TopToBottom.apply(state, source, target, points,
						result);
			}
			else
			{
				mxEdgeStyle.SideToSide.apply(state, source, target, points,
						result);
			}
		}
	};

	/**
	 * Provides a vertical elbow edge.
	 */
	public static mxEdgeStyleFunction SideToSide = new mxEdgeStyleFunction()
	{

		/* (non-Javadoc)
		 * @see com.mxgraph.view.mxEdgeStyle.mxEdgeStyleFunction#apply(com.mxgraph.view.mxCellState, com.mxgraph.view.mxCellState, com.mxgraph.view.mxCellState, java.util.List, java.util.List)
		 */
		public void apply(mxCellState state, mxCellState source,
				mxCellState target, List<mxPoint> points, List<mxPoint> result)
		{
			mxGraphView view = state.getView();
			mxPoint pt = ((points != null && points.size() > 0) ? points.get(0)
					: null);
			mxPoint p0 = state.getAbsolutePoint(0);
			mxPoint pe = state
					.getAbsolutePoint(state.getAbsolutePointCount() - 1);

			if (pt != null)
			{
				pt = view.transformControlPoint(state, pt);
			}

			if (p0 != null)
			{
				source = new mxCellState();
				source.setX(p0.getX());
				source.setY(p0.getY());
			}

			if (pe != null)
			{
				target = new mxCellState();
				target.setX(pe.getX());
				target.setY(pe.getY());
			}

			if (source != null && target != null)
			{
				double l = Math.max(source.getX(), target.getX());
				double r = Math.min(source.getX() + source.getWidth(), target
						.getX()
						+ target.getWidth());

				double x = (pt != null) ? pt.getX() : r + (l - r) / 2;

				double y1 = view.getRoutingCenterY(source);
				double y2 = view.getRoutingCenterY(target);

				if (pt != null)
				{
					if (pt.getY() >= source.getY()
							&& pt.getY() <= source.getY() + source.getHeight())
					{
						y1 = pt.getY();
					}

					if (pt.getY() >= target.getY()
							&& pt.getY() <= target.getY() + target.getHeight())
					{
						y2 = pt.getY();
					}
				}

				if (!target.contains(x, y1) && !source.contains(x, y1))
				{
					result.add(new mxPoint(x, y1));
				}

				if (!target.contains(x, y2) && !source.contains(x, y2))
				{
					result.add(new mxPoint(x, y2));
				}

				if (result.size() == 1)
				{
					if (pt != null)
					{
						result.add(new mxPoint(x, pt.getY()));
					}
					else
					{
						double t = Math.max(source.getY(), target.getY());
						double b = Math.min(source.getY() + source.getHeight(),
								target.getY() + target.getHeight());

						result.add(new mxPoint(x, t + (b - t) / 2));
					}
				}
			}
		}
	};

	/**
	 * Provides a horizontal elbow edge.
	 */
	public static mxEdgeStyleFunction TopToBottom = new mxEdgeStyleFunction()
	{

		/* (non-Javadoc)
		 * @see com.mxgraph.view.mxEdgeStyle.mxEdgeStyleFunction#apply(com.mxgraph.view.mxCellState, com.mxgraph.view.mxCellState, com.mxgraph.view.mxCellState, java.util.List, java.util.List)
		 */
		public void apply(mxCellState state, mxCellState source,
				mxCellState target, List<mxPoint> points, List<mxPoint> result)
		{
			mxGraphView view = state.getView();
			mxPoint pt = ((points != null && points.size() > 0) ? points.get(0)
					: null);
			mxPoint p0 = state.getAbsolutePoint(0);
			mxPoint pe = state
					.getAbsolutePoint(state.getAbsolutePointCount() - 1);

			if (pt != null)
			{
				pt = view.transformControlPoint(state, pt);
			}

			if (p0 != null)
			{
				source = new mxCellState();
				source.setX(p0.getX());
				source.setY(p0.getY());
			}

			if (pe != null)
			{
				target = new mxCellState();
				target.setX(pe.getX());
				target.setY(pe.getY());
			}

			if (source != null && target != null)
			{
				double t = Math.max(source.getY(), target.getY());
				double b = Math.min(source.getY() + source.getHeight(), target
						.getY()
						+ target.getHeight());

				double x = view.getRoutingCenterX(source);

				if (pt != null && pt.getX() >= source.getX()
						&& pt.getX() <= source.getX() + source.getWidth())
				{
					x = pt.getX();
				}

				double y = (pt != null) ? pt.getY() : b + (t - b) / 2;

				if (!target.contains(x, y) && !source.contains(x, y))
				{
					result.add(new mxPoint(x, y));
				}

				if (pt != null && pt.getX() >= target.getX()
						&& pt.getX() <= target.getX() + target.getWidth())
				{
					x = pt.getX();
				}
				else
				{
					x = view.getRoutingCenterX(target);
				}

				if (!target.contains(x, y) && !source.contains(x, y))
				{
					result.add(new mxPoint(x, y));
				}

				if (result.size() == 1)
				{
					if (pt != null)
					{
						result.add(new mxPoint(pt.getX(), y));
					}
					else
					{
						double l = Math.max(source.getX(), target.getX());
						double r = Math.min(source.getX() + source.getWidth(),
								target.getX() + target.getWidth());

						result.add(new mxPoint(l + (r - l) / 2, y));
					}
				}
			}
		}
	};

}
