/******************************************************************************
 * Copyright (c) 2006, Intalio Inc.
 * 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:
 *     Intalio Inc. - initial API and implementation
 *******************************************************************************/

/** 
 * Date             Author             Changes 
 * 10 Oct 2006      bilchyshyn         Created 
 **/
package org.eclipse.stp.bpmn.tools;

import org.eclipse.draw2d.Connection;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PointList;
import org.eclipse.draw2d.geometry.Ray;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gmf.runtime.draw2d.ui.geometry.PointListUtilities;
import org.eclipse.gmf.runtime.draw2d.ui.internal.routers.OrthogonalRouterUtilities;
import org.eclipse.gmf.runtime.draw2d.ui.internal.routers.RectilinearRouter;

public class MessageRectilinearRouter extends RectilinearRouter {

    @Override
    protected void updateIfNotRectilinear(Connection conn, PointList newLine) {
        boolean isRectilinear = true;

        Rectangle startRect = new Rectangle(conn.getSourceAnchor().getOwner()
                .getBounds());
        conn.getSourceAnchor().getOwner().translateToAbsolute(startRect);
        conn.translateToRelative(startRect);

        Dimension buffer = new Dimension(2, 2);
        conn.translateToRelative(buffer);

        startRect.expand(buffer.width, buffer.height);

        Rectangle endRect = new Rectangle(conn.getTargetAnchor().getOwner()
                .getBounds());
        conn.getTargetAnchor().getOwner().translateToAbsolute(endRect);
        conn.translateToRelative(endRect);
        endRect.expand(buffer.width, buffer.height);

        if (!startRect.contains(newLine.getPoint(0))) {
            newLine.getFirstPoint().setLocation(newLine.getFirstPoint().x,
                    startRect.y + startRect.height);
        }

        for (int i = 0; i < newLine.size() - 1; i++) {
            Ray segVector = new Ray(newLine.getPoint(i), newLine
                    .getPoint(i + 1));

            if (segVector.x != 0 && segVector.y != 0) {
                isRectilinear = false;
                break;
            }
        }

        if (!isRectilinear) {
            // We've got a line that isn't rectilinear, so let's route
            // General rules based on number of segments starting with
            // if starting with two points (one segment) take shortest distance
            // first.
            // if starting with three points (two segments) put longest segment
            // as the middle segment
            OrthogonalRouterUtilities.resetEndPointsToCenter(conn, newLine);

            PointList oldPoints = PointListUtilities.copyPoints(newLine);

            PointList newPoints = new PointList();
            newPoints.addPoint(oldPoints.removePoint(0));
            while (oldPoints.size() > 0) {
                if (oldPoints.size() >= 2) {
                    // This starts at point where last left off,
                    // or the starting point if first time through.
                    Point p0 = newPoints.getLastPoint();
                    Point p1 = oldPoints.removePoint(0);
                    Point p2 = oldPoints.removePoint(0);

                    // make the shortest segment first.
                    if (Math.abs(p2.y - p0.y) > Math.abs(p2.x - p0.x)) {
                        // x has shortest segment
                        newPoints.addPoint(new Point(p1.x, p0.y));
                        newPoints.addPoint(new Point(p1.x, p2.y));
                    } else // y has shortest segment first.
                    {
                        newPoints.addPoint(new Point(p0.x, p1.y));
                        newPoints.addPoint(new Point(p2.x, p1.y));
                    }
                    newPoints.addPoint(p2);
                } else if (oldPoints.size() == 1) {
                    Point p0 = newPoints.getLastPoint();
                    Point p1 = oldPoints.removePoint(0);
                    if (Math.abs(p1.y - p0.y) > Math.abs(p1.x - p0.x)) {
                        newPoints.addPoint(new Point(p1.x, p0.y));
                    } else {
                        newPoints.addPoint(new Point(p0.x, p1.y));
                    }
                    newPoints.addPoint(p1);
                }

            }
            oldPoints.removeAllPoints();
            // Now make a pass through to collapse any redundent segments.
            oldPoints.addPoint(newPoints.removePoint(0));
            while (newPoints.size() >= 2) {
                Point p0 = oldPoints.getLastPoint();
                Point p1 = newPoints.getPoint(0);
                Point p2 = newPoints.getPoint(1);
                if (p0.x == p1.x && p0.x == p2.x) {
                    // Have two vertical segments in a row
                    // get rid of the point between
                    newPoints.removePoint(0);
                } else if (p0.y == p1.y && p0.y == p2.y) {
                    // Have two horizontal segments in a row
                    // get rid of the point between
                    newPoints.removePoint(0);
                } else {
                    oldPoints.addPoint(newPoints.removePoint(0));
                }
            }
            while (newPoints.size() > 0) {
                oldPoints.addPoint(newPoints.removePoint(0));
            }

            // set the newly routed line back into newLine
            newLine.removeAllPoints();
            for (int i = 0; i < oldPoints.size(); i++)
                newLine.addPoint(oldPoints.getPoint(i));
        }
    }

    protected void straightenPoints(PointList newLine) {
        if (newLine.size() == 2) {
            Point ptCurrent = newLine.getPoint(0);
            Point ptNext = newLine.getPoint(1);
            if (ptNext.x != ptCurrent.x) {
                newLine.removeAllPoints();
                newLine.addPoint(ptCurrent);
                newLine.addPoint(ptNext.x, ptCurrent.y);
                newLine.addPoint(ptNext);
            }
        }
    }

    protected boolean checkEndSegments(Connection conn, PointList newLine) {
        boolean bOk = true;

        Dimension connection_offset = new Dimension(26, 0);
        conn.translateToRelative(connection_offset);

        // now check for end segments length and fix up after.
        if (newLine.size() > 2) {
            Point ptFix = new Point(newLine.getPoint(1));
            if (!checkEndSegment(conn, conn.getSourceAnchor(), ptFix,
                    connection_offset.width / 2)) {
                newLine.setPoint(ptFix, 1);
                // check next point to ensure rectilinear
                Point ptNext = newLine.getPoint(2);
                makeOrthogonal(ptFix, ptNext);

                newLine.setPoint(ptNext, 2);
                bOk = false;
            }

            ptFix = new Point(newLine.getPoint(newLine.size() - 2));
            if (!checkEndSegment(conn, conn.getTargetAnchor(), ptFix,
                    connection_offset.width / 2)) {
                newLine.setPoint(ptFix, newLine.size() - 2);
                // check next point to ensure rectilinear
                Point ptNext = newLine.getPoint(newLine.size() - 3);
                makeOrthogonal(ptFix, ptNext);

                newLine.setPoint(ptNext, newLine.size() - 3);
                bOk = false;
            }
        }

        return bOk;
    }

    private void makeOrthogonal(Point ptCurrent, Point ptNext) {
        if (Math.abs(ptNext.x - ptCurrent.x) < Math.abs(ptNext.y - ptCurrent.y)) {
            ptNext.x = ptCurrent.x;
        } else {
            ptNext.y = ptCurrent.y;
        }
    }

    @Override
    public void routeLine(Connection conn, int nestedRoutingDepth,
            PointList newLine) {
        super.routeLine(conn, nestedRoutingDepth, newLine);

        // make the length of connection shorter if possible.
        if (newLine.size() >= 3) {
            Rectangle startRect = new Rectangle(conn.getSourceAnchor()
                    .getOwner().getBounds());
            conn.getSourceAnchor().getOwner().translateToAbsolute(startRect);
            conn.translateToRelative(startRect);

            Rectangle endRect = new Rectangle(conn.getTargetAnchor().getOwner()
                    .getBounds());
            conn.getTargetAnchor().getOwner().translateToAbsolute(endRect);
            conn.translateToRelative(endRect);

            Point spt = newLine.getFirstPoint();
            Point mpt = newLine.getMidpoint();
            Point ept = newLine.getLastPoint();

            if (spt.y < ept.y) {
                if (spt.y < startRect.y + startRect.height / 2) {
                    spt.setLocation(spt.x, startRect.y + startRect.height);
                    if (spt.x == mpt.x) {
                        newLine.removePoint(0);
                        newLine.insertPoint(spt, 0);
                    } else {
                        mpt.setLocation(mpt.x, startRect.y + startRect.height);
                        newLine.removePoint(0);
                        newLine.insertPoint(spt, 0);
                        newLine.removePoint(1);
                        newLine.insertPoint(mpt, 1);
                    }
                }

                if (ept.y > endRect.y + endRect.height / 2) {
                    ept.setLocation(ept.x, endRect.y);
                    if (ept.x == mpt.x) {
                        newLine.removePoint(2);
                        newLine.insertPoint(ept, 2);
                    } else {
                        mpt.setLocation(mpt.x, endRect.y);
                        newLine.removePoint(2);
                        newLine.insertPoint(ept, 2);
                        newLine.removePoint(1);
                        newLine.insertPoint(mpt, 1);
                    }
                }
            } else if (spt.y > ept.y) {
                if (spt.y > startRect.y + startRect.height / 2) {
                    spt.setLocation(spt.x, startRect.y);
                    if (spt.x == mpt.x) {
                        newLine.removePoint(0);
                        newLine.insertPoint(spt, 0);
                    } else {
                        mpt.setLocation(mpt.x, startRect.y);
                        newLine.removePoint(0);
                        newLine.insertPoint(spt, 0);
                        newLine.removePoint(1);
                        newLine.insertPoint(mpt, 1);
                    }
                }

                if (ept.y < endRect.y + endRect.height / 2) {
                    ept.setLocation(ept.x, endRect.y + endRect.height);
                    if (ept.x == mpt.x) {
                        newLine.removePoint(2);
                        newLine.insertPoint(ept, 2);
                    } else {
                        mpt.setLocation(mpt.x, endRect.y + endRect.height);
                        newLine.removePoint(2);
                        newLine.insertPoint(ept, 2);
                        newLine.removePoint(1);
                        newLine.insertPoint(mpt, 1);
                    }
                }
            }
        }
    }
}
