/*******************************************************************************
 * Copyright (c) 2000, 2005 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
 *     Exadel, Inc.
 *     Red Hat, Inc.
 *******************************************************************************/
package org.jboss.tools.seam.ui.pages.editor.edit.xpl;

import java.util.*;

import org.eclipse.draw2d.AbstractRouter;
import org.eclipse.draw2d.Connection;
import org.eclipse.draw2d.IFigure;
//import org.eclipse.draw2d.ConnectionAnchor;
import org.eclipse.draw2d.geometry.*;
import org.jboss.tools.seam.ui.pages.editor.ecore.pages.PagesElement;
import org.jboss.tools.seam.ui.pages.editor.figures.ConnectionFigure;
import org.jboss.tools.seam.ui.pages.editor.figures.ExceptionFigure;
import org.jboss.tools.seam.ui.pages.editor.figures.PageFigure;


final public class PagesConnectionRouter
	extends AbstractRouter
{

private Set<Integer> rowsUsed = new HashSet<Integer>();
private Set<Integer> colsUsed = new HashSet<Integer>();

private final static int STEP = 8;

private final static int STATUS_NOTHING = 0;
private final static int STATUS_HOLD = 2;
private final static int STATUS_SHIFT = 3;

private Map<Connection,ReservedInfo> reservedInfo = new HashMap<Connection,ReservedInfo>();

private class ReservedInfo {
	public List<Integer> reservedRows = new ArrayList<Integer>(2);
	public List<Integer> reservedCols = new ArrayList<Integer>(2);
}

private static Ray 	UP	= new Ray(0,-1),
				DOWN	= new Ray(0,1),
				LEFT	= new Ray(-1,0),
				RIGHT	= new Ray(1,0);


public void invalidate(Connection connection) {
	removeReservedLines(connection);
}

private int getColumnNear(Connection conn, int r, int n, int x, boolean flag){
	int index=0, size=0;
	int min = Math.min(n,x),
		max = Math.max(n,x);
	if (min > r){
		max = min;
		min = r - (min-r);
	}
	if (max < r){
		min = max;
		max = r + (r-max);
	}
	
	if(flag){
		if(conn.getTargetAnchor().getOwner() != null){
			IFigure fig = conn.getTargetAnchor().getOwner();
			PagesElement element=null;
			if(fig instanceof PageFigure)
				element = ((PageFigure)fig).page;
			else if(fig instanceof ExceptionFigure)
				element = ((ExceptionFigure)fig).exc;
			if(element != null){
				index = element.getInputLinks().indexOf(((ConnectionFigure)conn).getLinkModel());
				size = element.getInputLinks().size();
			}else flag = false;
		}else flag = false;
		if(flag && size > 1){
			int value = max-20-(size-index)*STEP;
			if(value <= min) return min+STEP;
			if(value >= max) return max-STEP;
			return value;
		}
		size = 0;
		flag = true;
		if(conn.getSourceAnchor().getOwner() != null){
			IFigure fig = conn.getSourceAnchor().getOwner();
			PagesElement element=null;
			if(fig instanceof PageFigure)
				element = ((PageFigure)fig).page;
			else if(fig instanceof ExceptionFigure)
				element = ((ExceptionFigure)fig).exc;
			if(element != null){
				index = element.getOutputLinks().indexOf(((ConnectionFigure)conn).getLinkModel());
				size = element.getOutputLinks().size();
			}else flag = false;
		}else flag = false;
		if(flag){
			int value = min+100+(size-index)*STEP-size/2*STEP;
			if(value <= min) return min+STEP;
			if(value >= max) return max-STEP;
			return value;
		}
	}
	
	int proximity = 0;
	int direction = -1;
	if (r%2 == 1)
		r--;
	Integer i;
	while (proximity < r){
		i = Integer.valueOf(r + proximity*direction);
		if (!colsUsed.contains(i)){
			colsUsed.add(i);
			reserveColumn(conn, i);
			return i.intValue();
		}
		int j = i.intValue();
		if (j <= min)
			return j+STEP;
		if (j >= max)
			return j-STEP;
		if (direction == 1)
			direction = -1;
		else {
			direction = 1;
			proximity += STEP;
		}
	}
	return r;
}

protected Ray getDirection(Rectangle r, Point p){
	int i, distance = Math.abs(r.x - p.x);
	Ray direction;
	
	direction = LEFT;

	i = Math.abs(r.y - p.y);
	if (i <= distance){
		distance = i;
		direction = UP;
	}

	i = Math.abs(r.bottom()-p.y);
	if (i <= distance){
		distance = i;
		direction = DOWN;
	}

	i = Math.abs(r.right()-p.x);
	if (i < distance){
		distance = i;
		direction = RIGHT;
	}

	return direction;
}

protected Ray getEndDirection(Connection conn){
	return LEFT;
}

protected int getRowNear(Connection connection, int r, int n, int x){
	int min = Math.min(n,x),
		max = Math.max(n,x);
	if (min > r){
		max = min;
		min = r - (min-r);
	}
	if (max < r){
		min = max;
		max = r + (r-max);
	}

	int proximity = 0;
	int direction = -1;
	if (r%2 == 1)
		r--;
	Integer i;
	while (proximity < r){
		i = Integer.valueOf(r + proximity*direction);
		if (!rowsUsed.contains(i)){
			rowsUsed.add(i);
			reserveRow(connection, i);
			return i.intValue();
		}
		int j = i.intValue();
		if (j <= min)
			return j+STEP;
		if (j >= max)
			return j-STEP;
		if (direction == 1)
			direction = -1;
		else {
			direction = 1;
			proximity += STEP;
		}
	}
	return r;
}
protected Ray getStartDirection(Connection conn){
	return RIGHT;
}

protected void processPositions(Ray start, Ray end, List positions, 
					  boolean horizontal, Connection conn) {
	removeReservedLines(conn);

	int pos[] = new int[positions.size()+2];
	if (horizontal)
		pos[0] = start.x;
	else
		pos[0] = start.y;
	int i;
	for (i=0; i< positions.size(); i++){
		pos[i+1] = ((Integer)positions.get(i)).intValue();
	}
	if (horizontal == (positions.size()%2 == 1))
		pos[++i] = end.x;
	else
		pos[++i] = end.y;

	PointList points = new PointList();
	points.addPoint(new Point(start.x, start.y));
	Point p;
	int current, prev, min, max;
	boolean adjust;
	for (i=2; i < pos.length - 1; i++){
		horizontal = !horizontal;
		prev = pos[i-1];
		current = pos[i];

		adjust = (i != pos.length-2);
		if (horizontal){
			if (adjust){
				min = pos[i-2];
				max = pos[i+2];
				pos[i] = current = getRowNear(conn,current,min,max);
				current -= current%STEP;
				pos[i] -= pos[i]%STEP;
			}
			p = new Point(prev,current);
		}
		else{
			if (adjust){
				min = pos[i-2];
				max = pos[i+2];
				boolean flag;
				if(i == 2 && pos.length == 5){ 
					flag = true;
				}else flag = false;
				pos[i] = current = getColumnNear(conn, current,min,max,flag);
				current -= current%STEP;
				pos[i] -= pos[i]%STEP;
			}
			p = new Point(current,prev);
		}
		points.addPoint(p);
	}
	points.addPoint(new Point(end.x, end.y));
	conn.setPoints(points);
}

/**
 * Removes the given connection from the map of constraints.
 *
 * @param connection The connection to remove.
 */
public void remove(Connection connection){
	removeReservedLines(connection);
}

protected void removeReservedLines(Connection connection) {
	ReservedInfo rInfo = (ReservedInfo) reservedInfo.get(connection);
	if (rInfo == null) return;
	
	for (int i = 0; i < rInfo.reservedRows.size(); i++){
		rowsUsed.remove(rInfo.reservedRows.get(i));
	}
	for (int i = 0; i < rInfo.reservedCols.size(); i++){
		colsUsed.remove(rInfo.reservedCols.get(i));
	}
	reservedInfo.remove(connection);
}

protected void reserveColumn(Connection connection, Integer column) {
	ReservedInfo info = (ReservedInfo) reservedInfo.get(connection);
	if (info == null) {
		info = new ReservedInfo();
		reservedInfo.put(connection, info);
	}
	info.reservedCols.add(column);
}

protected void reserveRow(Connection connection, Integer row) {
	ReservedInfo info = (ReservedInfo) reservedInfo.get(connection);
	if (info == null) {
		info = new ReservedInfo();
		reservedInfo.put(connection, info);
	}
	info.reservedRows.add(row);
}

/**
 * Routes the {@link Connection}.
 *
 * @param conn The {@link Connection} to route.
 */
public void route(Connection conn) {
	if ((conn.getSourceAnchor() == null) || (conn.getTargetAnchor() == null)){
			return;
	}
	if(((ConnectionFigure)conn).getLinkModel() != null && ((ConnectionFigure)conn).getLinkModel().isShortcut()){
		Point startPoint = getStartPoint(conn);
		conn.translateToRelative(startPoint);
		List<Integer> positions = new ArrayList<Integer>(5);
		Ray start = new Ray(startPoint);
		positions.add(Integer.valueOf(start.y));
		
		Point endPoint = new Point(startPoint.x+18, startPoint.y);
		Ray end = new Ray(endPoint);
		processPositions(start, end, positions,true, conn);
		return;
	}
		
	if(((ConnectionFigure)conn).isManual()){
		if(conn.getPoints().size() < 4){
			PointList list = ((ConnectionFigure)conn).getLinkModel().getPointList();
			((ConnectionFigure)conn).setOldPoints(list.getFirstPoint(), list.getLastPoint());
			conn.setPoints(list);
		}
		if(hold((ConnectionFigure)conn)) return;
		else{
			((ConnectionFigure)conn).setManual(false);
			((ConnectionFigure)conn).clear();
		}
	}
	int i;
	Point startPoint = getStartPoint(conn);
	conn.translateToRelative(startPoint);
	
	Point endPoint = getEndPoint(conn);
	conn.translateToRelative(endPoint);

	Ray start = new Ray(startPoint);
	Ray end = new Ray(endPoint);
	Ray average = start.getAveraged(end);

	Ray direction = new Ray(start, end);
	Ray startNormal = getStartDirection(conn);
	Ray endNormal   = getEndDirection(conn);

	List<Integer> positions = new ArrayList<Integer>(5);
	boolean horizontal = true;
	// start horizontal segment
	positions.add(Integer.valueOf(start.y));
	horizontal = !horizontal;

	if((start.x > (end.x+20)) && (Math.abs(end.y-start.y) < 100)){
		i = startNormal.similarity(start.getAdded(startNormal.getScaled(10)));
		positions.add(Integer.valueOf(i));
		horizontal = !horizontal;

		if(conn.getSourceAnchor().getOwner() == null){
			i = average.y;
		}else{
			Rectangle rec = conn.getSourceAnchor().getOwner().getBounds();
		
			i = rec.y+rec.height+8;
		}
		i -= i%STEP;
		positions.add(Integer.valueOf(i));
		horizontal = !horizontal;

		i = endNormal.similarity(end.getAdded(endNormal.getScaled(10)));
		i -= i%STEP;
		positions.add(Integer.valueOf(i));
		horizontal = !horizontal;
	}else{
		// vertical segment
		if (startNormal.dotProduct(direction) < STEP*2){
			i = startNormal.similarity(start.getAdded(startNormal.getScaled(10)));
			i -= i%STEP;
			positions.add(Integer.valueOf(i));
			horizontal = !horizontal;
		}

		//	middle vertical or horizontal segment
		if (horizontal) i = average.y;
		else i = average.x;
		i -= i%STEP;
		positions.add(Integer.valueOf(i));
		horizontal = !horizontal;

		//	vertical segment
		if (startNormal.dotProduct(direction) < STEP*2){
			i = endNormal.similarity(end.getAdded(endNormal.getScaled(10)));
			i -= i%STEP;
			positions.add(Integer.valueOf(i));
			horizontal = !horizontal;
		}
	}
	// end horizontal segment
	positions.add(Integer.valueOf(end.y)); 
	
	processPositions(start, end, positions,true, conn);
	((ConnectionFigure)conn).setOldPoints(startPoint, endPoint);
}

private boolean hold(ConnectionFigure conn){
	Point p1,p2;
	
	Point startPoint = getStartPoint(conn);
	conn.translateToRelative(startPoint);
	
	Point endPoint = getEndPoint(conn);
	conn.translateToRelative(endPoint);
	
	int status = check(conn);
	if(status == STATUS_NOTHING) return true;
	else if(status == STATUS_SHIFT){
		shift(conn);
		conn.setOldPoints(startPoint, endPoint);
		conn.save();
		return true; 
	}

	PointList list = conn.getPoints();
	
	list.removePoint(0);
	list.insertPoint(startPoint, 0);
	
	list.removePoint(list.size()-1);
	list.addPoint(endPoint);
	
	if(list.size() > 2){
		p1 = list.getPoint(1);
		
		p1.y = startPoint.y;
		if(p1.x <= startPoint.x) return false;
		
		p2 = list.getPoint(list.size()-2);

		p2.y = endPoint.y;
		if(p2.x >= endPoint.x) return false;
		
		list.removePoint(1);
		list.insertPoint(p1, 1);
		int index = list.size()-2;

		list.removePoint(index);
		list.insertPoint(p2, index);
	}
	conn.setPoints(list);
	conn.setOldPoints(startPoint, endPoint);
	conn.save();
	return true;
}

private void shift(ConnectionFigure conn){
	Point startPoint = getStartPoint(conn);
	conn.translateToRelative(startPoint);
	
	Point oldStartPoint = conn.getOldStartPoint();
	
	Point shiftPoint = new Point(startPoint.x-oldStartPoint.x, startPoint.y-oldStartPoint.y);
	Point point;
	
	PointList list = conn.getPoints();
	if(list.getPoint(0).x == startPoint.x && list.getPoint(0).y == startPoint.y) return;
	
	for(int i=0;i<list.size();i++){
		point = list.getPoint(i);
		point.x += shiftPoint.x;
		point.y += shiftPoint.y;
		list.removePoint(i);
		list.insertPoint(point, i);
	}
	conn.setPoints(list);
}

private int check(ConnectionFigure conn){
	Point startPoint = getStartPoint(conn);
	conn.translateToRelative(startPoint);
	
	Point endPoint = getEndPoint(conn);
	conn.translateToRelative(endPoint);

	Point oldStartPoint = ((ConnectionFigure)conn).getOldStartPoint();
	
	Point oldEndPoint = ((ConnectionFigure)conn).getOldEndPoint();
	
	if(startPoint.x == oldStartPoint.x && startPoint.y == oldStartPoint.y && endPoint.x == oldEndPoint.x && endPoint.y == oldEndPoint.y)
		return STATUS_NOTHING;
	
	if((startPoint.x - oldStartPoint.x) == (endPoint.x - oldEndPoint.x) && (startPoint.y - oldStartPoint.y) == (endPoint.y - oldEndPoint.y)){
		return STATUS_SHIFT;
	}
	return STATUS_HOLD; 
}
}