/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: RoutingMain.java
 * Written by: Christian Harnisch, Ingo Besenfelder, Michael Neumann (Team 3)
 *
 * Copyright (c) 2010, Static Free Software. All rights reserved.
 *
 * Electric(tm) is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.sun.electric.tool.routing.experimentalAStar2.concurrency;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sun.electric.tool.routing.experimentalAStar2.algorithm.AStarMapBase;
import com.sun.electric.tool.routing.experimentalAStar2.algorithm.AStarNode;
import com.sun.electric.tool.routing.experimentalAStar2.algorithm.AStarRegionNode;
import com.sun.electric.tool.routing.experimentalAStar2.datastructures.Point3D;
import com.sun.electric.tool.routing.experimentalAStar2.map.FieldMap;
import com.sun.electric.tool.routing.experimentalAStar2.map.RegionBoundingBox;
import com.sun.electric.tool.routing.experimentalAStar2.memorymanager.AStarNodeObjectPool;
import com.sun.electric.tool.routing.experimentalAStar2.memorymanager.AStarRegionNodeObjectPool;
import com.sun.electric.tool.routing.experimentalAStar2.memorymanager.LinkedListObjectPool;
import com.sun.electric.tool.routing.experimentalAStar2.memorymanager.ObjectFactory;
import com.sun.electric.tool.routing.experimentalAStar2.memorymanager.ObjectPool;

/*
 * We are working only on node-coordinates here.
 */
public class RoutingMain {
	/* Tuning parameters */
	
	private static Logger logger = LoggerFactory.getLogger(RoutingMain.class);

	public static final int MAX_REVOLUTIONS = 300000;

	/**
	 * Maximum number of retries for routing one global job. Set to 0 for
	 * allowing only the initial try.
	 */
	private static final int MAX_RETRIES = 1;

	/* Below: Private members, not for tuning. */

	private int layerCount;

	// private FieldMap<AStarRegionNode> regionMap;

	// private Array2D<Region> regions;

	private AStarMapBase<AStarRegionNode> regionGrid;

	private int numRegionsPerSide;

	public static int regionWidth;

	public static int regionHeight;

	private long maxRuntimeMillis;

	private ExecutorService threadPool;

	private ExecutorCompletionService<GlobalRouteJob> globalCompletionService;

	private ExecutorCompletionService<LocalRouteJob> localCompletionService;

	ObjectPool<GlobalRouteJob> globalRouteJobPool;

	private int routeJobCount;

	private int completedRouteJobCount;

	private List<RouteJob> waitingRouteJobs = new LinkedList<RouteJob>();

	/**
	 * Returns the current routing progress in percent (this means: In the range
	 * from 0 to 100)
	 * 
	 * @return Current progress in percent.
	 */
	public int getProgress() {
		return Math.min((completedRouteJobCount * 100) / routeJobCount, 100);
	}

	public void setMaxRuntimeMillis(long maxRuntimeMillis) {
		this.maxRuntimeMillis = maxRuntimeMillis;
	}

	public RoutingMain(int width, int height, int layerCount, int threadPoolSize) {
		assert (layerCount > 0);

		this.layerCount = layerCount;
		this.numRegionsPerSide = Math.min(16, Math.max(1, threadPoolSize / 2));
		regionWidth = width / this.numRegionsPerSide;
		regionHeight = height / this.numRegionsPerSide;

		/*
		 * Initialise region grid. The region grid is a map of AStarRegionNodes.
		 * It basically cuts the whole cell into quadratic one-layer submaps.
		 */
		FieldMap<AStarRegionNode> newGrid = new FieldMap<AStarRegionNode>(this.numRegionsPerSide,
				this.numRegionsPerSide, layerCount, 0, 0, 0);
		newGrid.setObjectPool(new AStarRegionNodeObjectPool());
		this.regionGrid = newGrid;
		for (int x = 0; x < this.numRegionsPerSide; x++) {
			for (int y = 0; y < this.numRegionsPerSide; y++) {
				for (int z = 0; z < layerCount; z++) {
					FieldMap<AStarNode> newRoutingMap = new FieldMap<AStarNode>(regionWidth, regionHeight, 1,
							0, 0, 0);
					// Every map has it's own object-pool. This is to avoid to
					// synchronize
					// access to the ObjectPool.
					newRoutingMap.setObjectPool(new AStarNodeObjectPool());

					AStarRegionNode node = new AStarRegionNode(newRoutingMap, regionWidth, regionHeight, x,
							y, z);
					newGrid.setNode(x, y, z, node);
				}
			}
		}

		/*
		 * Initialize Regions.
		 */
		// this.regions = new Array2D<Region>(this.numRegionsPerSide,
		// this.numRegionsPerSide, null, false);
		// for (int xr = 0; xr < this.numRegionsPerSide; ++xr)
		// {
		// for (int yr = 0; yr < this.numRegionsPerSide; ++yr)
		// {
		// Region region = new Region(); // TODO
		// region.coordinateOffsetX = xr * this.regionWidth;
		// region.coordinateOffsetY = yr * this.regionHeight;
		// region.tilePosX = xr;
		// region.tilePosY = yr;
		// region.map = new FieldMap<AStarNode>(this.regionWidth,
		// this.regionHeight,
		// this.layerCount, 0, 0, 0);
		// // Every map has it's own object-pool. This is to avoid to
		// synchronize
		// // access to the ObjectPool.
		// region.map.setObjectPool(new AStarNodeObjectPool());
		// this.regions.setAt(xr, yr, region);
		// }
		// }
		//
		// /*
		// * Initialize regionMap, which is used by the global A* path finding
		// * algorithm.
		// */
		// this.regionMap = new
		// FieldMap<AStarRegionNode>(this.numRegionsPerSide,
		// this.numRegionsPerSide, this.layerCount, 0, 0, 0);
		// this.regionMap.setObjectPool(new AStarRegionNodeObjectPool());
		// for (int x = 0; x < numRegionsPerSide; ++x)
		// {
		// for (int y = 0; y < numRegionsPerSide; ++y)
		// {
		// for (int z = 0; z < this.layerCount; ++z)
		// {
		// AStarRegionNode rn = new AStarRegionNode(this.regions.getAt(x,
		// y).map, z,
		// this.regionWidth, this.regionHeight);
		// rn.initialize(null, 0, 0, 0, x, y, z);
		// regionMap.setNode(x, y, z, rn);
		// }
		// }
		// }
		threadPool = Executors.newFixedThreadPool(threadPoolSize);
		localCompletionService = new ExecutorCompletionService<LocalRouteJob>(threadPool);
		globalCompletionService = new ExecutorCompletionService<GlobalRouteJob>(threadPool);
		globalRouteJobPool = new LinkedListObjectPool<GlobalRouteJob>(50,
				new ObjectFactory<GlobalRouteJob>() {
					public GlobalRouteJob create() {
						return new GlobalRouteJob();
					}
				});

		routeJobCount = 0;
		completedRouteJobCount = 0;
	}

	public void submitRouteJob(RouteJob job) {
		Point3D from = job.from;
		Point3D to = job.to;
		// Set from and to blocked. This has two reasons:
		// 1. Other segments don't accidently block start and end point when
		// routing
		// 2. Multi-segment nets can unblock start and end point before routing
		setTileBlocked(from.getX(), from.getY(), from.getZ(), true);
		setTileBlocked(to.getX(), to.getY(), to.getZ(), true);

		assert (this.regionGrid != null);
		RegionBoundingBox boundingBox = new RegionBoundingBox(this.regionGrid, nodeToRegionX(from.getX()),
				nodeToRegionY(from.getY()), from.getZ(), nodeToRegionX(to.getX()), nodeToRegionY(to.getY()),
				to.getZ());
		job.boundingBox = boundingBox;
		routeJobCount++;
		waitingRouteJobs.add(job);
	}

	private void submitPossibleGlobalJobs() {
		// Search through list of waiting jobs
		Iterator<RouteJob> i = waitingRouteJobs.iterator();
		while (i.hasNext()) {
			RouteJob job = i.next();
			// See if its bounding box is free
			if (job.boundingBox.isBoundingBoxFree()) {
				// If yes, init and enqueue new GlobalRouteJob,
				// and remove waiting RouteJob from waiting list
				i.remove();
				job.boundingBox.occupyBoundingBox();
				GlobalRouteJob globalJob = globalRouteJobPool.acquire();

				// Unblock start and end point
				setTileBlocked(job.from.getX(), job.from.getY(), job.from.getZ(), false);
				setTileBlocked(job.to.getX(), job.to.getY(), job.to.getZ(), false);

				Point3D from = new Point3D(nodeToRegionX(job.from.getX()), nodeToRegionY(job.from.getY()),
						job.from.getZ());
				Point3D to = new Point3D(nodeToRegionX(job.to.getX()), nodeToRegionY(job.to.getY()),
						job.to.getZ());
				Point3D fromInsideRegion = new Point3D(nodeGlobalToLocalX(job.from.getX()),
						nodeGlobalToLocalY(job.from.getY()), 0);
				Point3D toInsideRegion = new Point3D(nodeGlobalToLocalX(job.to.getX()),
						nodeGlobalToLocalY(job.to.getY()), 0);

				globalJob.initialize(job, from, to, fromInsideRegion, toInsideRegion);

				globalCompletionService.submit(globalJob);
				// System.out.println("Global job started");
			}
			// If no, go to next iteration
		}
	}

	/**
	 * Handler methods for finished jobs. Called by the main thread.
	 */
	public void handleLocalRouteJobFinished(LocalRouteJob finishedJob) {
		// System.out.println("RoutingMain: handleLocalRouteJobFinished for region ("
		// + finishedJob.regionalNode.getX() + "/"
		// + finishedJob.regionalNode.getY() + "/" +
		// finishedJob.regionalNode.getZ()
		// + ")");

		if (finishedJob.path == null) {
			// System.out.println("RoutingMain: Null path.");
		} else {
			for (Point3D pt : finishedJob.path) {
				// Since local path was routed without layers, set the correct
				// z-coordinate here.
				pt.setZ(finishedJob.regionalNode.getZ());
				// System.out.println("RoutingMain: " + pt.toString());
			}
		}

		finishedJob.routeJob.localRouteJobsCompleted.add(finishedJob);

		if (finishedJob.routeJob.localRouteJobsList.size() != finishedJob.routeJob.localRouteJobsCompleted
				.size()) {
			// System.out.println("RoutingMain: Not complete yet");
			return;
		}

		// System.out.println("RoutingMain: All local paths done");

		// Block start and end point again
		setTileBlocked(finishedJob.routeJob.from.getX(), finishedJob.routeJob.from.getY(),
				finishedJob.routeJob.from.getZ(), true);
		setTileBlocked(finishedJob.routeJob.to.getX(), finishedJob.routeJob.to.getY(),
				finishedJob.routeJob.to.getZ(), true);

		//
		// Build global path
		//
		Collections.sort(finishedJob.routeJob.localRouteJobsCompleted, new LocalRouteJobComparator());
		List<Point3D> totalPath = new ArrayList<Point3D>();

		for (LocalRouteJob lrj : finishedJob.routeJob.localRouteJobsCompleted) {
			// TODO: Release regions on regionMap when all local route jobs are
			// done
			// for one path

			if (lrj.getPath() == null) {
				// System.out.println("RoutingMain: SHIT");
				// if one of the local routes is null, we have to throw away the
				// whole
				// global
				// routing job
				finishedJob.path = null;
				finishedJob.routeJob.path = null;
				++completedRouteJobCount;
				finishedJob.routeJob.onCompletion();
				finishedJob.routeJob.boundingBox.releaseBoundingBox();

				// As one job is finished now, try to submit new ones.
				submitPossibleGlobalJobs();
				return;
			}
			// Append the local path to the global path, converting all
			// coordinates to
			// global coordinates.
			for (Point3D pt : lrj.getPath()) {
				Point3D pt2 = new Point3D(nodeLocalToGlobalX(pt.getX(), lrj.regionalNode),
						nodeLocalToGlobalY(pt.getY(), lrj.regionalNode), pt.getZ());
				totalPath.add(pt2);
			}
		}

		// Set blockages. All coordinates are global.
		for (Point3D pt : totalPath) {
			setTileBlocked(pt.getX(), pt.getY(), pt.getZ(), true);
		}

		finishedJob.routeJob.path = totalPath;
		++completedRouteJobCount;
		finishedJob.routeJob.boundingBox.releaseBoundingBox();
		// As one global job is finished now, try to submit new ones.
		submitPossibleGlobalJobs();
		finishedJob.routeJob.onCompletion();
	}

	public void handleGlobalRouteJobFinished(GlobalRouteJob finishedJob) {
		// System.out.println("RoutingMain: handleGlobalRouteJobFinished");

		List<AStarRegionNode> path = finishedJob.resultPath;

		// No path found: Enlarge bounding box and re-schedule if maximum number
		// of
		// retries is not reached yet
		if (path == null) {
			if (finishedJob.routeJob.retries < RoutingMain.MAX_RETRIES) {
				finishedJob.routeJob.retries++;
				finishedJob.routeJob.boundingBox.releaseBoundingBox();
				finishedJob.routeJob.boundingBox.enlarge();
				waitingRouteJobs.add(finishedJob.routeJob);
			} else {
				// System.out.println("RoutingMain: Cannot find global path");
				// Block start and end point again
				setTileBlocked(finishedJob.routeJob.from.getX(), finishedJob.routeJob.from.getY(),
						finishedJob.routeJob.from.getZ(), true);
				setTileBlocked(finishedJob.routeJob.to.getX(), finishedJob.routeJob.to.getY(),
						finishedJob.routeJob.to.getZ(), true);

				completedRouteJobCount++;
				finishedJob.routeJob.boundingBox.releaseBoundingBox();
				finishedJob.routeJob.path = null;
				finishedJob.routeJob.onCompletion();
			}
		}
		// Otherwise, dispatch individual detailed routing jobs
		else {
			// System.out.println("RoutingMain: Found global path");

			// XXX: Release bounding box again and just occupy individual
			// regions
			// finishedJob.getRouteJob().boundingBox.releaseBoundingBox();

			for (int i = 0; i < path.size(); i++) {
				AStarRegionNode rn = path.get(i);
				if (i < (path.size() - 1)) {
					AStarRegionNode nextRegion = path.get(i + 1);
					AStarNode entryPoint = path.get(i + 1).getEntryPoint();

					// If next region west of this
					if (nextRegion.getX() < rn.getX()) {
						rn.setExitPoint(rn.getMap(true).nodeAt(0, entryPoint.getY(), 0));
					} else
					// Next region east?
					if (nextRegion.getX() > rn.getX()) {
						rn.setExitPoint(rn.getMap(true).nodeAt(regionWidth - 1, entryPoint.getY(), 0));
					} else
					// Next region north?
					if (nextRegion.getY() < rn.getY()) {
						rn.setExitPoint(rn.getMap(true).nodeAt(entryPoint.getX(), 0, 0));
					} else
					// Next region south?
					if (nextRegion.getY() > rn.getY()) {
						rn.setExitPoint(rn.getMap(true).nodeAt(entryPoint.getX(), regionHeight - 1, 0));
					} else
					// Next region different layer?
					if (nextRegion.getZ() != rn.getZ()) {
						rn.setExitPoint(rn.getMap(true).nodeAt(entryPoint.getX(), entryPoint.getY(), 0));
					}
					// else
					// System.out.println("RoutingMain: Region not one of the 6 manhattan neighbours!");
				}

				// System.out.println("(" + rn.getX() + "/" + rn.getY() + "/" +
				// rn.getZ() + ")");

				// Mark this region as occupied on the regionMap,
				// so other routing jobs don't interfere
				// regionMap.setTileBlocked(rn.getX(), rn.getY(), rn.getZ(),
				// true);

				LocalRouteJob localRoute = new LocalRouteJob();
				// XXX
				localRoute.from = new Point3D(rn.getEntryPoint().getX(), rn.getEntryPoint().getY(), rn
						.getEntryPoint().getZ());
				// if (rn.getExitPoint() == null)
				// System.out.println("ExitPoint == null! Region (" + rn.getX()
				// + "/" +
				// rn.getY() + "/" + rn.getZ() + ")");
				localRoute.to = new Point3D(rn.getExitPoint().getX(), rn.getExitPoint().getY(), rn
						.getExitPoint().getZ());
				localRoute.routeJob = finishedJob.getRouteJob();
				// localRoute.region = regions.getAt(rn.getX(), rn.getY());
				localRoute.regionalNode = rn;
				localRoute.numberInGlobalPath = i;
				// localRoute.routeJob.localRouteJobs.push(localRoute);
				localRoute.routeJob.localRouteJobsList.add(localRoute);
				localCompletionService.submit(localRoute);
			}
		}
		globalRouteJobPool.release(finishedJob);
		// As one global job is finished now, try to submit new ones.
		submitPossibleGlobalJobs();
	}

	public void waitForCompletion() {
		// Store start time
		long startTimeMillis = System.currentTimeMillis();
		long currentTimeMillis = startTimeMillis;
		// "Bootstrap" one time
		submitPossibleGlobalJobs();
		// Now, spin on completion events

		// XXX [fschmidt] check this for timeouts & add timeout logging
		// information
		while (completedRouteJobCount < routeJobCount) {
			
			if(!((currentTimeMillis - startTimeMillis) <= maxRuntimeMillis)) {
				logger.debug("timeout ...");
				break;
			}

			try {
				Future<LocalRouteJob> localJob = localCompletionService.poll();
				if (localJob != null)
					handleLocalRouteJobFinished(localJob.get());

				Future<GlobalRouteJob> globalJob = globalCompletionService.poll();
				if (globalJob != null)
					handleGlobalRouteJobFinished(globalJob.get());

				currentTimeMillis = System.currentTimeMillis();
			} catch (InterruptedException e) {
				e.printStackTrace();
			} catch (ExecutionException e) {
				e.printStackTrace();
			}
		}
		this.shutDown();
		// System.out.println("RoutingMain: Routing done.");
	}

	public void shutDown() {
		// Tell workers to cease operating.
		// Use shutdownNow() because we really want to shutdown ASAP
		this.threadPool.shutdownNow();
	}

	/**
	 * Sets the rectangle defined by (sx, sy) to (ex, ex) in <em>all</em> layers
	 * as a blockage.
	 */
	public void setBlockage(int sx, int sy, int ex, int ey) {
		for (int x = sx; x <= ex; ++x) {
			setTileBlockedAllLayers(x, sy);
			setTileBlockedAllLayers(x, ey);
		}
		for (int y = sy; y <= ey; ++y) {
			setTileBlockedAllLayers(sx, y);
			setTileBlockedAllLayers(ex, y);
		}
	}

	/**
	 * Sets the rectangle defined by (sx, sy) to (ex, ex) in <em>all</em> layers
	 * as a blockage.
	 */
	public void setBlockage(int sx, int sy, int ex, int ey, int z) {
		for (int x = sx; x <= ex; ++x) {
			setTileBlocked(x, sy, z, true);
			setTileBlocked(x, ey, z, true);
		}
		for (int y = sy; y <= ey; ++y) {
			setTileBlocked(sx, y, z, true);
			setTileBlocked(ex, y, z, true);
		}
	}

	// private void setTileBlocked(int x, int y, int z)
	// {
	// AStarRegionNode node = nodeCoordsToRegion(x, y, z);
	// node.routingMap.setTileBlocked(nodeGlobalToLocalX(x),
	// nodeGlobalToLocalY(y), 0, true);
	// }

	private void setTileBlocked(int x, int y, int z, boolean blockedStatus) {
		AStarRegionNode node = nodeCoordsToRegion(x, y, z);
		assert (node != null);
		node.routingMap.setTileBlocked(nodeGlobalToLocalX(x), nodeGlobalToLocalY(y), 0, blockedStatus);
	}

	private void setTileBlockedAllLayers(int x, int y) {
		for (int z = 0; z < this.layerCount; z++) {
			AStarRegionNode node = nodeCoordsToRegion(x, y, z);
			assert (node != null);
			node.routingMap.setTileBlocked(nodeGlobalToLocalX(x), nodeGlobalToLocalY(y), 0, true);
		}
	}

	/**
	 * Places the portals on the borders of the maps of all regions. This must
	 * be done <em>after</em> the blockages have been set.
	 */
	public void placePortals() {
		AStarRegionNode myRegion;
		AStarRegionNode neighbourRegion;
		int maxXNodes = this.regionGrid.getMaxXNodes();
		int maxYNodes = this.regionGrid.getMaxYNodes();
		int maxZNodes = this.regionGrid.getMaxZNodes();
		for (int x = 0; x < maxXNodes; x++)
			for (int y = 0; y < maxYNodes; y++)
				for (int z = 0; z < maxZNodes; z++) {
					myRegion = this.regionGrid.nodeAt(x, y, z);

					// to the east
					if (x + 1 < maxXNodes) {
						neighbourRegion = this.regionGrid.nodeAt(x + 1, y, z);
						for (int rY = 0; rY < regionHeight; rY++)
							if (!myRegion.isTileBlocked(regionWidth - 1, rY)
									&& !neighbourRegion.isTileBlocked(0, rY)) {
								myRegion.setPortal(regionWidth - 1, rY, false);
								neighbourRegion.setPortal(0, rY, false);
							}

						// To the west, if this is a west-most region
						if (x == 0) {
							for (int rY = 0; rY < regionHeight; rY++)
								if (!myRegion.isTileBlocked(0, rY)) {
									myRegion.setPortal(0, rY, false);
								}
						}

					} else
					// if (x == (maxXNodes - 1))
					{
						for (int rY = 0; rY < regionHeight; rY++)
							if (!myRegion.isTileBlocked(regionWidth - 1, rY)) {
								myRegion.setPortal(regionWidth - 1, rY, false);
							}
					}

					// to the north
					if (y > 0) {
						neighbourRegion = this.regionGrid.nodeAt(x, y - 1, z);
						for (int rX = 0; rX < regionWidth; rX++)
							if (!myRegion.isTileBlocked(rX, 0)
									&& !neighbourRegion.isTileBlocked(rX, regionHeight - 1)) {
								myRegion.setPortal(rX, 0, true);
								neighbourRegion.setPortal(rX, regionHeight - 1, true);
							}

						// To the south, if this is a region on the southern
						// border of the
						// region grid
						if (y == (maxYNodes - 1)) {
							for (int rX = 0; rX < regionWidth; rX++)
								if (!myRegion.isTileBlocked(rX, regionHeight - 1)) {
									myRegion.setPortal(rX, regionHeight - 1, true);
								}
						}

					} else
					// if (y == 0)
					{
						for (int rX = 0; rX < regionWidth; rX++)
							if (!myRegion.isTileBlocked(rX, 0)) {
								myRegion.setPortal(rX, 0, true);
							}
					}
				}
	}

	/* Methods concerned with the region grid. */

	private int nodeToRegionX(int x) {
		return (int) Math.floor(x / regionWidth);
	}

	private int nodeToRegionY(int y) {
		return (int) Math.floor(y / regionHeight);
	}

	private int nodeGlobalToLocalX(int x) {
		return x % regionWidth;
	}

	private int nodeGlobalToLocalY(int y) {
		return y % regionHeight;
	}

	private int nodeLocalToGlobalX(int x, AStarRegionNode whichRegion) {
		return (x + regionToNodeX(whichRegion.getX()));
	}

	private int nodeLocalToGlobalY(int y, AStarRegionNode whichRegion) {
		return (y + regionToNodeY(whichRegion.getY()));
	}

	private int regionToNodeX(int x) {
		return x * regionWidth;
	}

	private int regionToNodeY(int y) {
		return y * regionHeight;
	}

	private AStarRegionNode nodeCoordsToRegion(int x, int y, int z) {
		return this.regionGrid.nodeAt(nodeToRegionX(x), nodeToRegionY(y), z);
	}
}

class LocalRouteJobComparator implements Comparator<LocalRouteJob> {

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
	 */
	public int compare(LocalRouteJob arg0, LocalRouteJob arg1) {
		if (arg0.numberInGlobalPath < arg1.numberInGlobalPath)
			return -1;
		if (arg0.numberInGlobalPath == arg1.numberInGlobalPath)
			return 0;
		return 1;
	}
}
