/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: RoutingFrameLeeMoore.java
 * Written by: Dennis Appelt, Sven Janko (Team 2)
 *
 * 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.experimentalLeeMoore3;

import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.topology.RTBounds;
import com.sun.electric.database.topology.RTNode;
import com.sun.electric.tool.Job;
import com.sun.electric.util.TextUtils;
import com.sun.electric.util.math.DBMath;
import com.sun.electric.util.math.FixpRectangle;

import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Class to do the Lee-Moore routing inside the RoutingFrame.
 */
public class RoutingFrameLeeMoore extends BenchmarkRouter {
	private static final boolean DEBUG = false;
	// route segments with the same net id in parallel
	private static final boolean PARALLEL = true;

	// this option is still very experimental an will not find correct routes
	static final boolean GLOBALDETAILEDROUTING = false;

	/** The cell in which all the routing takes place. */
	private Cell cell;
	private Rectangle2D cellBounds;

	/** List of all metal layers on which the RoutingWires will be placed. */
	private RoutingLayer[] metalLayers;
	private int numMetalLayers;
	private double globalGridGranularity;
	// private double globalGridStepsize;
	private double detailedGridGranularity;
	private Wavefront dummyDetailedWavefront;

	/** vias to use to go up from each metal layer. */
	private MetalVias[] metalVias;
	private Map<RoutingLayer, RTNode<LMBound>> metalBlockages;
	private Map<RoutingLayer, RTNode<LMVia>> viaBlockages;

	/** Rating function used for influencing the quality of the routing */
	RatingFunction[] ratingFunctionsForDetailed = {
			new DistanceRatingFunction(),
			// new MinimalCrossingRatingFunction(),
			// new ShiftInDirectionRatingFunction(),
			new OutOfBoundsRatingFunction(),
			new ForcedDirectionRatingFunction() };

	RatingFunction[] ratingFunctionsForGlobal = {
			new DistanceRatingFunction(),
			// new MinimalCrossingRatingFunction(),
			// new ShiftInDirectionRatingFunction(),
			new OutOfBoundsRatingFunction(),
			new ForcedDirectionRatingFunction() };

	/** used for parallelisation **/
	// private static Object lockObj = new Object();
	private ThreadPoolExecutor workerThreadsExecutor;
	private ArrayBlockingQueue<Runnable> inQueue;
	private ArrayBlockingQueue<Wavefront> outQueue;

	/**
	 * Method to return the name of this routing algorithm.
	 *
	 * @return the name of this routing algorithm.
	 */
	public String getAlgorithmName() {
		return "Lee/Moore - 3";
	}

	/**
	 * Method to do LeeMoore routing.
	 */
	protected void runRouting(Cell cell, List<RoutingSegment> segmentsToRoute,
			List<RoutingLayer> allLayers, List<RoutingContact> allContacts,
			List<RoutingGeometry> blockages) {

		// set default timeout
		if (maxRuntime.getTempIntValue() <= 0) {
			maxRuntime.setTempIntValue(800);
		}

		// stop the time so we can abort the routing if we exceed
		// <code>this.secTimeout</code> seconds
		long timeStart = System.currentTimeMillis();

		// create Workerthreadpool
		// Set max. number of threads according to the parameters
		int maxPoolSize = numThreads.getTempIntValue();
		maxPoolSize = maxPoolSize > 0 ? maxPoolSize : 4;
		int poolSize = maxPoolSize > 1 ? maxPoolSize / 2 : 1;
		long keepAliveTime = 10;
		inQueue = new ArrayBlockingQueue<Runnable>(40);
		outQueue = new ArrayBlockingQueue<Wavefront>(40);
		workerThreadsExecutor = new ThreadPoolExecutor(poolSize, maxPoolSize,
				keepAliveTime, TimeUnit.SECONDS, inQueue);

		Job.getUserInterface().startProgressDialog(
				"Collect startup information", null);
		Job.getUserInterface().setProgressNote("Initialize design roules");
		// initialize information about the technology
		if (initializeDesignRules(allLayers, allContacts))
			return;

		Job.getUserInterface().setProgressNote("Set up data structures");
		// Job.getUserInterface().setProgressValue(0);

		this.cell = cell;
		this.cellBounds = this.cell.getBounds();

		double x = cellBounds.getMaxX() - cellBounds.getMinX();
		double y = cellBounds.getMaxY() - cellBounds.getMinY();
		globalGridGranularity = 1 / (((x < y) ? x : y) / 32);
		// globalGridStepsize = 1 / globalGridGranularity;
		detailedGridGranularity = globalGridGranularity * 32;
		dummyDetailedWavefront = new Wavefront(segmentsToRoute.get(0),
				detailedGridGranularity, cellBounds,
				ratingFunctionsForDetailed, false);

		this.metalBlockages = new HashMap<RoutingLayer, RTNode<LMBound>>();
		this.viaBlockages = new HashMap<RoutingLayer, RTNode<LMVia>>();

		for (RoutingGeometry rg : blockages) {
			RoutingLayer l = rg.getLayer();
			Rectangle2D rect = rg.getBounds();
			if (l.isMetal())
				addRectangle(rect, l, rg.getNetID());
			else
				addVia(
						new Point2D.Double(rect.getCenterX(), rect.getCenterY()),
						l, rg.getNetID());
		}
		addBlockagesAtPorts(segmentsToRoute);

		Job.getUserInterface().stopProgressDialog();
		Job.getUserInterface().startProgressDialog("Routing segments", null);

		int numSegments = segmentsToRoute.size();
		int prevNetId = (numSegments > 0) ? segmentsToRoute.get(0).getNetID()
				: -1;
		for (int i = 0; i < numSegments; i++) {

			Job.getUserInterface().setProgressValue(
					(int) (((double) i / (double) numSegments) * 100));
			Job.getUserInterface().setProgressNote(
					"Routing segment " + (i + 1) + " of " + numSegments + ".");

			RoutingSegment currentSegment = segmentsToRoute.get(i);
			if (PARALLEL) {

				try {
					// if the current segment is from a different net id as the
					// last segment,
					// finish all previous wavefronts and do the backtracking.
					// Otherwise
					// the blockage information wouldn't be consistent.
					if (prevNetId != currentSegment.getNetID()) {
						while (outQueue.size() != 0
								|| workerThreadsExecutor.getQueue().size() != 0) {
							dequeueWavefrontJob();
						}
					}

					// the capacity of the job queue's is full, take some jobs
					// from the out queue
					if (workerThreadsExecutor.getQueue().size() > 39
							|| outQueue.size() > 30) {
						dequeueWavefrontJob();
					}

					if (GLOBALDETAILEDROUTING) {
						Wavefront[] detailedWavefronts = doGlobalRouting(currentSegment);
						for (Wavefront w : detailedWavefronts) {
							enqueueWavefrontJob(w);
						}
					} else {
						Wavefront w = new Wavefront(currentSegment, 0.2,
								cellBounds, ratingFunctionsForDetailed, false);
						enqueueWavefrontJob(w);
					}
				} catch (CouldNotCalculateVirtualTerminalException e2) {
					// computation of detailed segments not possible, try to
					// route
					// the global segment
					if (enableOutput.getTempBooleanValue())
						System.out.print(i
								+ ": (unable to calculate virtual Terminals) ");

					Wavefront w = new Wavefront(currentSegment, 0.2,
							cellBounds, ratingFunctionsForDetailed, false);
					enqueueWavefrontJob(w);
				} catch (RatingNotFoundException e) {
					// caused likely because the global backtracking didn't find
					// a rating
					// for a already visited point
					if (enableOutput.getTempBooleanValue())
						System.out
								.print(i
										+ ": (global backtracking did not find rating) ");

					Wavefront w = new Wavefront(currentSegment, 0.2,
							cellBounds, ratingFunctionsForDetailed, false);
					enqueueWavefrontJob(w);
				}

				prevNetId = currentSegment.getNetID();
			} else {
				// not parallel
				if (enableOutput.getTempBooleanValue())
					System.out.print(i + ": ");
				Wavefront w = new Wavefront(currentSegment, 0.2, cellBounds,
						ratingFunctionsForDetailed, DEBUG);
				w.propagate();

				if (w.foundRoute)
					new Backtracking(w).routeIt();
			}

			if ((System.currentTimeMillis() - timeStart) / 1000 > maxRuntime
					.getTempIntValue()) {
				if (enableOutput.getTempBooleanValue())
					System.out
							.println("Routing aborted. Maximum execution time ("
									+ maxRuntime.getTempIntValue()
									+ " seconds) reached.");
				break;
			}
		}
		// wait until all running wavefront jobs are finished
		while (outQueue.size() != 0
				|| workerThreadsExecutor.getQueue().size() != 0) {
			dequeueWavefrontJob();
		}

		Job.getUserInterface().stopProgressDialog();
	}

	private void enqueueWavefrontJob(Wavefront w) {
		boolean submitToWorkpool = false;
		do {
			try {
				workerThreadsExecutor.execute(new WavefrontExecutor(w));
				submitToWorkpool = true;
				// w.propagate();
			}
			// work pool for detailed routing is full
			catch (RejectedExecutionException e) {
				Wavefront doneWavefront;
				try {
					doneWavefront = outQueue.take();
					if (doneWavefront.foundRoute)
						new Backtracking(doneWavefront).routeIt();
				} catch (InterruptedException e1) {
					if (enableOutput.getTempBooleanValue())
						e1.printStackTrace();
				}
			}
		} while (!submitToWorkpool);
	}

	private void dequeueWavefrontJob() {
		Wavefront w;

		try {
			w = outQueue.take();
			if (w.foundRoute)
				new Backtracking(w).routeIt();
		} catch (InterruptedException e) {
			if (enableOutput.getTempBooleanValue())
				e.printStackTrace();
		}
	}

	public Wavefront[] doGlobalRouting(RoutingSegment globalSegment)
			throws CouldNotCalculateVirtualTerminalException,
			RatingNotFoundException {

		Wavefront globalWF = new Wavefront(globalSegment,
				globalGridGranularity, cellBounds, ratingFunctionsForGlobal,
				false, true);

		globalWF.propagate();

		ExperimentalGlobalBacktracking backtracking = new ExperimentalGlobalBacktracking(
				globalWF);
		List<ExperimentalGlobalBacktracking.Point> globalSections = backtracking
				.routeIt();

		ArrayList<Rectangle2D> detailedSegmentBounds = new ArrayList<Rectangle2D>();

		for (ExperimentalGlobalBacktracking.Point p : globalSections)
			detailedSegmentBounds.add(getBoundsForGridPoint(p, globalWF));

		Wavefront[] detailedWavefronts = calcVirtualTerminals(
				detailedSegmentBounds.toArray(new Rectangle2D[0]),
				globalSegment, backtracking, globalWF);

		return detailedWavefronts;
	}

	public Rectangle2D getBoundsForGridPoint(
			ExperimentalGlobalBacktracking.Point p, Wavefront wf) {
		double segmentSize = ((cellBounds.getMaxY() - cellBounds.getMinY()) / 32);
		double diffToBorder = ((cellBounds.getMaxY() - cellBounds.getMinY()) / 32) / 2;

		double min = 0.00000001;
		double sizeOfRect = ((long) ((segmentSize - min) * 100000000)) / 100000000.0;

		Rectangle2D rect = new Rectangle2D.Double(wf.toGrid(p.x) - diffToBorder
				+ min, wf.toGrid(p.y) - diffToBorder + min, sizeOfRect,
				sizeOfRect);

		return rect;
	}

	public Rectangle2D getBoundsForGridPoint(Point2D p, Wavefront wf) {

		double segmentSize = ((cellBounds.getMaxY() - cellBounds.getMinY()) / 32);
		double diffToBorder = ((cellBounds.getMaxY() - cellBounds.getMinY()) / 32) / 2;

		double min = 0.00000001;
		double sizeOfRect = ((long) ((segmentSize - min) * 100000000)) / 100000000.0;

		Rectangle2D rect = new Rectangle2D.Double(wf.toGrid(p.getX())
				- diffToBorder + min, wf.toGrid(p.getY()) - diffToBorder + min,
				sizeOfRect, sizeOfRect);

		return rect;
	}

	public Wavefront[] calcVirtualTerminals(
			Rectangle2D[] detailedSegmentBounds, RoutingSegment globalSegment,
			ExperimentalGlobalBacktracking dummy, Wavefront dummyWF)
			throws CouldNotCalculateVirtualTerminalException {
		Wavefront[] detailedSegments = new Wavefront[detailedSegmentBounds.length];

		Point2D startPoint = null, finishPoint = null, nextStartPoint = null;

		startPoint = globalSegment.getFinishEnd().getLocation();

		for (int i = 0; i + 1 < detailedSegmentBounds.length; i++) {
			boolean blockage = false;
			double rnd = Math.random();

			for (int j = 0; j < 5; j++) {
				double tmp = (rnd + (0.2 * j)) / 1.0;
				// calculate the X- and Y-coordinate for the virtual terminals
				Point2D[] points = calcPointsForVirtualTerminals(
						detailedSegmentBounds[i], detailedSegmentBounds[i + 1],
						tmp);
				finishPoint = points[0];
				nextStartPoint = points[1];

				// check if the virtual terminal is located within a blockage
				double minWidth = globalSegment.getWidestArcAtStart();
				RoutingLayer currentLayer = globalSegment.getStartLayers().get(
						0);
				double surround = currentLayer.getMinSpacing(currentLayer);
				double metalSpacing = Math.max(currentLayer.getMinWidth(),
						minWidth) / 2;

				blockage = false;
				// if the calculated virtual terminals are within blockages,
				// calculate new
				// virtual terminals
				for (Point2D p : points) {
					LMBound block = getMetalBlockage(globalSegment.getNetID(),
							currentLayer.getMetalNumber(), metalSpacing,
							metalSpacing, surround, p.getX(), p.getY());
					if (block != null) {
						if (j == 4)
							throw new CouldNotCalculateVirtualTerminalException();

						blockage = true;
						break;
					}
				}
				// found a valid virtual terminals, stop the searching
				if (!blockage)
					break;
			}

			detailedSegments[i] = new Wavefront(globalSegment, startPoint,
					finishPoint, globalSegment.getStartLayers().get(0),
					globalSegment.getStartLayers().get(0),
					detailedGridGranularity, detailedSegmentBounds[i],
					ratingFunctionsForDetailed, false, false);

			startPoint = nextStartPoint;
		}

		// add the wavefront which contains the finish end
		finishPoint = globalSegment.getStartEnd().getLocation();
		detailedSegments[detailedSegmentBounds.length - 1] = new Wavefront(
				globalSegment, startPoint, finishPoint, globalSegment
						.getStartLayers().get(0), globalSegment
						.getFinishLayers().get(0), detailedGridGranularity,
				detailedSegmentBounds[detailedSegmentBounds.length - 1],
				ratingFunctionsForDetailed, false, false);

		return detailedSegments;
	}

	private Point2D[] calcPointsForVirtualTerminals(
			Rectangle2D firstSementBounds, Rectangle2D secondSementBounds,
			double randomDouble) {
		double crossingXforp1 = 0, crossingXforp2 = 0;
		double crossingYforp1 = 0, crossingYforp2 = 0;

		Point2D finishPoint = null, nextStartPoint = null;

		if (firstSementBounds.getMinX() == secondSementBounds.getMinX()) {

			boolean firstSmaller = firstSementBounds.getMaxY() < secondSementBounds
					.getMaxY();

			if (firstSmaller) {
				crossingYforp1 = firstSementBounds.getMaxY();
				crossingYforp2 = secondSementBounds.getMinY();
			} else {
				crossingYforp1 = firstSementBounds.getMinY();
				crossingYforp2 = secondSementBounds.getMaxY();
			}

			// limit resolution of the random xCoord, otherwise there may be
			// rounding errors if transforming the xCoord to a grid point
			double xCoord = ((long) ((firstSementBounds.getMaxX() - firstSementBounds
					.getMinX())
					* randomDouble * 100000000)) / 100000000.0;
			xCoord += firstSementBounds.getMinX();

			xCoord = dummyDetailedWavefront.toGrid(xCoord);
			crossingYforp1 = dummyDetailedWavefront.toGrid(crossingYforp1);
			crossingYforp2 = dummyDetailedWavefront.toGrid(crossingYforp2);

			finishPoint = new Point2D.Double(xCoord, crossingYforp1);
			nextStartPoint = new Point2D.Double(xCoord, crossingYforp2);
		} else {
			boolean firstSmaller = firstSementBounds.getMaxX() < secondSementBounds
					.getMaxX();

			if (firstSmaller) {
				crossingXforp1 = firstSementBounds.getMaxX();
				crossingXforp2 = secondSementBounds.getMinX();
			} else {
				crossingXforp1 = firstSementBounds.getMinX();
				crossingXforp2 = secondSementBounds.getMaxX();
			}

			// limit resolution of the random yCoord, otherwise there may be
			// rounding errors if transforming the yCoord to a grid point
			double yCoord = ((long) ((firstSementBounds.getMaxY() - firstSementBounds
					.getMinY())
					* randomDouble * 100000000)) / 100000000.0;
			yCoord += firstSementBounds.getMinY();

			yCoord = dummyDetailedWavefront.toGrid(yCoord);
			crossingYforp1 = dummyDetailedWavefront.toGrid(crossingXforp1);
			crossingYforp2 = dummyDetailedWavefront.toGrid(crossingXforp2);

			finishPoint = new Point2D.Double(crossingXforp1, yCoord);
			nextStartPoint = new Point2D.Double(crossingXforp2, yCoord);
		}

		return new Point2D[] { finishPoint, nextStartPoint };
	}

	/**
	 * Class to find a route on a segment.
	 */
	private class Wavefront {

		private boolean debug;
		RoutingSegment segment;
		private RoutingLayer rlStart, rlFinish;
		private double xStart, yStart;
		/** These are the coords of the grid point next to the start point. */
		double xGridStart, yGridStart;
		int zStart;
		double xFinish, yFinish;
		/** These are the coords of the grid point next to the finish point. */
		double xGridFinish, yGridFinish;
		int zFinish;
		private String netName;
		private int netID;

		RatingFunction[] ratingFunctions;

		// variables related to the grid
		private double granularity;
		private double stepsize;
		private Rectangle2D bounds;

		boolean foundRoute;
		boolean runInGlobalMode;

		// the number of points the wavefront will visit (at most)
		private int maxPointsLimit;
		DataGrid[] grid;

		/**
		 * The first point in this queue is the next that will be visited by the
		 * wavefront.<br>
		 * Sort criterion is the ranking of the grid point. The higher the
		 * ranking of a point the faster that point will be visited.
		 */
		PriorityQueue<Gridpoint> pointsToVisit;

		Wavefront(RoutingSegment s, double granularity, Rectangle2D bounds,
				RatingFunction[] ratingFunctions, boolean debug) {

			// set start and finish layer
			this.rlStart = s.getStartLayers().get(0);
			this.rlFinish = s.getFinishLayers().get(0);

			// set start and finish coordinates
			this.xStart = s.getStartEnd().getLocation().getX();
			this.yStart = s.getStartEnd().getLocation().getY();
			this.xFinish = s.getFinishEnd().getLocation().getX();
			this.yFinish = s.getFinishEnd().getLocation().getY();

			// init everything else
			init(s, granularity, bounds, ratingFunctions, debug, false);

			// printInfo();
		}

		Wavefront(RoutingSegment s, Point2D startPoint, Point2D finishPoint,
				RoutingLayer startLayer, RoutingLayer finishLayer,
				double granularity, Rectangle2D bounds,
				RatingFunction[] ratingFunctions, boolean debug,
				boolean runInGlobalMode) {

			// set start and finish layer
			this.rlStart = startLayer;
			this.rlFinish = finishLayer;

			// set start and end point
			this.xStart = startPoint.getX();
			this.yStart = startPoint.getY();
			this.xFinish = finishPoint.getX();
			this.yFinish = finishPoint.getY();

			// init everything else
			init(s, granularity, bounds, ratingFunctions, debug,
					runInGlobalMode);
			// printInfo();
		}

		Wavefront(RoutingSegment s, double granularity, Rectangle2D bounds,
				RatingFunction[] ratingFunctions, boolean debug,
				boolean runInGlobalMode) {

			// set start and finish layer
			this.rlStart = s.getStartLayers().get(0);
			this.rlFinish = s.getFinishLayers().get(0);

			// set start and finish coordinates
			this.xStart = s.getStartEnd().getLocation().getX();
			this.yStart = s.getStartEnd().getLocation().getY();
			this.xFinish = s.getFinishEnd().getLocation().getX();
			this.yFinish = s.getFinishEnd().getLocation().getY();

			// init everything else
			init(s, granularity, bounds, ratingFunctions, debug,
					runInGlobalMode);
			// printInfo();
		}

		// helper method for constructor
		private void init(RoutingSegment s, double granularity,
				Rectangle2D bounds, RatingFunction[] ratingFunctions,
				boolean debug, boolean runInGlobalMode) {
			// set grid size
			this.granularity = granularity;
			stepsize = 1 / granularity;
			this.bounds = bounds;

			// default is detailed mode
			this.runInGlobalMode = runInGlobalMode;

			this.ratingFunctions = ratingFunctions;
			this.segment = s;
			this.debug = debug;
			this.netName = this.segment.getNetName();
			this.netID = this.segment.getNetID();
			this.foundRoute = false;

			// set grid start and finish coordinates
			this.xGridStart = toGrid(this.xStart);
			this.yGridStart = toGrid(this.yStart);
			this.xGridFinish = toGrid(this.xFinish);
			this.yGridFinish = toGrid(this.yFinish);
			this.zStart = rlStart.getMetalNumber() - 1;
			this.zFinish = rlFinish.getMetalNumber() - 1;

			// if the Start-/End Points are virtual Terminals, transform the
			// Start-/Endpoints to grid points
			if (GLOBALDETAILEDROUTING) {
				Point2D startPoint = segment.getStartEnd().getLocation();
				if (!this.runInGlobalMode && xFinish != startPoint.getX()
						&& yFinish != startPoint.getY()) {
					xFinish = toGrid(xFinish);
					yFinish = toGrid(yFinish);
				}
				Point2D finishPoint = segment.getFinishEnd().getLocation();
				if (!this.runInGlobalMode && xStart != finishPoint.getX()
						&& yStart != finishPoint.getY()) {
					xStart = toGrid(xStart);
					yStart = toGrid(yStart);
				}
			}

			// generate a DataGrid for each metal layer
			this.grid = new DataGrid[numMetalLayers];
			for (int i = 0; i < numMetalLayers; i++) {
				this.grid[i] = new DataGrid();
			}

			this.pointsToVisit = new PriorityQueue<Gridpoint>();
			// add the start point to the list
			this.pointsToVisit.add(new Gridpoint(this.xGridStart,
					this.yGridStart, this.zStart, null));
			this.grid[this.zStart].visit(this.xGridStart, this.yGridStart,
					new Rating());

			double tmp = DBMath.distBetweenPoints(this.segment.getStartEnd()
					.getLocation(), this.segment.getFinishEnd().getLocation());
			this.maxPointsLimit = (int) (tmp * this.granularity * 1800);
		}

		// helper method for constructor
		private void printInfo() {
			if (enableOutput.getTempBooleanValue()) {
				System.out.println((runInGlobalMode ? "global" : "detailed")
						+ " Wavefront \"" + this.netName + "\" started at ("
						+ TextUtils.formatDouble(this.xStart) + ", "
						+ TextUtils.formatDouble(this.yStart) + ", "
						+ this.zStart + ") heading for ("
						+ TextUtils.formatDouble(this.xFinish) + ", "
						+ TextUtils.formatDouble(this.yFinish) + ", "
						+ this.zFinish + ")" + " - NetID=" + this.netID + ", "
						+ "Thread ID: " + TextUtils.getThreadID(Thread.currentThread()));
			}
		}

		private void propagate() {
			printInfo();

			double x = this.xStart, y = this.yStart, nextX, nextY;
			int z = this.zStart, nextZ;

			RTNode<LMBound> blockages = metalBlockages.get(this.rlStart);

			// check if the finish point can be reached
			double minWidth = metalLayers[this.zFinish].getMinWidth();
			double minSpacing = metalLayers[this.zFinish]
					.getMinSpacing(metalLayers[this.zFinish]);
			double halfWidth = minWidth / 2;

			LMBound block = getMetalBlockage(netID, this.zFinish, halfWidth,
					halfWidth, minSpacing, this.xGridFinish, this.yGridFinish);
			if (block != null) {
				if (enableOutput.getTempBooleanValue())
					System.out
							.println("There is a blockage at the finish point. Wavefront will not start.");
				return;
			}

			Gridpoint curPoint = null;
			// count the number of visited grid points
			int counter = 0;
			while ((curPoint = this.pointsToVisit.poll()) != null) {
				counter++;

				if (counter > this.maxPointsLimit) {
					if (enableOutput.getTempBooleanValue())
						System.out
								.println("Wavefront did not find finish point (maximum number of visited points ("
										+ this.maxPointsLimit + ") reached).");
					return;

				}
				x = curPoint.getX();
				y = curPoint.getY();
				z = curPoint.getZ();
				if (this.debug) {
					this.segment.addWireEnd(new RoutePoint(metalLayers[z]
							.getPin(), new Point2D.Double(x, y), 0));
				}

				// see if the wavefront reached the finish point
				if (z == this.zFinish && x == this.xGridFinish
						&& y == this.yGridFinish) {

					this.foundRoute = true;
					if (enableOutput.getTempBooleanValue() && this.debug) {
						System.out.println("routing found");
						System.out.println("Visited " + counter
								+ " Grid Points.");
					}
					return;
				}

				// spread in all six directions
				for (int i = 0; i < 6; i++) {

					double dx = 0, dy = 0;
					int dz = 0;

					switch (i) {
					// 0-3: the 4 directions on the same layer
					case 0:
						dy = stepsize;
						break;
					case 1:
						dx = stepsize;
						break;
					case 2:
						dy = -stepsize;
						break;
					case 3:
						dx = -stepsize;
						break;
					// 4,5: change layer
					case 4:
						dz = -1;
						break;
					case 5:
						dz = 1;
						break;
					}
					nextX = toGrid(x + dx);
					nextY = toGrid(y + dy);
					nextZ = z + dz;
					if (nextZ < 0 || nextZ >= numMetalLayers
							|| this.grid[nextZ].isPointVisited(nextX, nextY))
						continue; // continue with next direction

					// if running in global Mode, ignore blockages
					if (!runInGlobalMode) {
						minWidth = metalLayers[nextZ].getMinWidth();
						minSpacing = metalLayers[nextZ]
								.getMinSpacing(metalLayers[nextZ]);

						// add some extra space (this.stepsize / 2)
						// to avoid spacing errors when we move wires
						// at start and finish ends
						// (see adjustment in backtracking)
						halfWidth = (minWidth + this.stepsize) / 2;

						block = getMetalBlockage(netID, nextZ, halfWidth,
								halfWidth, minSpacing, nextX, nextY);
						if (block != null) {
							continue;
						}
					}

					Gridpoint nextPoint = new Gridpoint(nextX, nextY, nextZ,
							curPoint);
					for (RatingFunction f : ratingFunctions) {
						f.doRating(nextPoint, curPoint, this.xFinish,
								this.yFinish, blockages, bounds);
					}
					nextPoint.getRating().calcRating();

					this.pointsToVisit.add(nextPoint);

					this.grid[nextZ].visit(nextX, nextY, nextPoint.getRating());
				}
			}
			if (enableOutput.getTempBooleanValue())
				System.out
						.println("Wavefront did not find finish point (stuck).");
		}

		public double getGranularity() {
			return granularity;
		}

		/**
		 * Method to round a value to the nearest point of our routing grid.
		 *
		 * @param d
		 *            The value to round.
		 * @return The grid value.
		 */
		private double toGrid(double d) {
			return Math.round(d * granularity) * stepsize;
		}
	} // end class Wavefront

	/**
	 * Class to to the backtracking after the wavefront reached the finish
	 * point.
	 */
	private class Backtracking {
		private Wavefront w;
		private double granularity;
		private double stepsize;

		public Backtracking(Wavefront w) {
			this.w = w;
			granularity = w.getGranularity();
			stepsize = 1 / granularity;
		}

		private class Point {
			double x, y;
			int zLast, z;
			boolean isVia = false;

			Point(double x, double y, int zLast, int z) {
				this.x = x;
				this.y = y;
				this.zLast = zLast;
				this.z = z;
				this.isVia = true;
			}

			Point(double x, double y, int z) {
				this.x = x;
				this.y = y;
				this.z = z;
			}
		}

		public void routeIt() {
			List<Point> points = new ArrayList<Point>();

			double x = this.w.xGridFinish, y = this.w.yGridFinish;
			double xLast = x, yLast = y;
			double xNextToLast = xLast, yNextToLast = yLast;

			int z = w.zFinish, zLast = w.zFinish;

			// add finish route point
			points.add(new Point(this.w.xFinish, this.w.yFinish, z));

			int lastRating = 0;
			Rating tmp = w.grid[z].getRating(x, y);
			if (tmp != null)
				lastRating = tmp.getRating();
			else {
				if (enableOutput.getTempBooleanValue())
					System.out
							.println("Backtracking did not find a route (Nullpointer Rating)!");
				return;
			}

			Rating r = tmp;
			// loop until start point is reached
			while ((x != this.w.xGridStart) || (y != this.w.yGridStart)
					|| z != w.zStart) {

				// if there was a layer change
				// or if there was a shift in direction
				if (z != zLast) {
					points.add(new Point(x, y, zLast, z));
				} else if (x != xNextToLast && y != yNextToLast) {
					points.add(new Point(xLast, yLast, z));
				}

				for (int i = 0; i < 6; i++) {
					double dx = 0, dy = 0;
					int dz = 0;
					switch (i) {
					// 0-3: the 4 directions on the same layer
					case 0:
						dy = stepsize;
						break;
					case 1:
						dx = stepsize;
						break;
					case 2:
						dy = -stepsize;
						break;
					case 3:
						dx = -stepsize;
						break;
					// 4,5: change layer
					case 4:
						dz = -1;
						break;
					case 5:
						dz = 1;
						break;
					}

					// check bounds
					if (z + dz < 0 || z + dz >= numMetalLayers)
						continue;

					r = this.w.grid[z + dz].getRating(toGrid(x + dx), toGrid(y
							+ dy));
					if (r == null || r.getRating() > lastRating) {
						if (i == 5) {
							if (enableOutput.getTempBooleanValue())
								System.out
										.println("Backtracking did not find a route!");
							return;
						} else
							continue;
					}

					xNextToLast = xLast;
					xLast = x;
					yNextToLast = yLast;
					yLast = y;
					zLast = z;
					lastRating = r.getRating();
					x = toGrid(x + dx);
					y = toGrid(y + dy);
					z += dz;
					break;
				}
			}
			if (z != zLast) {
				points.add(new Point(this.w.xStart, this.w.yStart, zLast, z));
			} else {
				points.add(new Point(this.w.xStart, this.w.yStart, z));
			}

			int size = points.size();
			if (size == 2) {
				Point p1 = points.get(0);
				Point p2 = points.get(1);
				if (p1.x != p2.x && p1.y != p2.y) {
					points.add(1, new Point(p1.x, p2.y, p1.z));
				}
			} else if (size == 3) {
				Point p = points.get(1);
				if (Math.abs(p.x - this.w.xFinish) < Math.abs(p.y
						- this.w.yFinish)) {
					p.x = this.w.xFinish;
					p.y = this.w.yStart;
				} else {
					p.x = this.w.xStart;
					p.y = this.w.yFinish;
				}
			} else if (size == 4) {
				Point p1 = points.get(1), p2 = points.get(2);
				if (Math.abs(p1.x - this.w.xFinish) < Math.abs(p1.y
						- this.w.yFinish)) {
					p1.x = this.w.xFinish;
					if (p2.x != p1.x && p2.y != p1.y) {
						p2.x = p1.x;
						p2.y = this.w.yStart;
					} else {
						p2.x = this.w.xStart;
					}
				} else {
					p1.y = this.w.yFinish;
					if (p2.x != p1.x && p2.y != p1.y) {
						p2.y = p1.y;
						p2.x = this.w.xStart;
					} else {
						p2.y = this.w.yStart;
					}
				}
			} else if (size > 4) {
				Point p1 = points.get(1), p2 = points.get(size - 2);
				boolean adjustX = false, adjustY = false;
				if (Math.abs(p1.x - this.w.xFinish) < Math.abs(p1.y
						- this.w.yFinish)) {
					p1.x = this.w.xFinish;
					Point p = points.get(2);
					if (p1.isVia || (p.x != p1.x && p.y != p1.y))
						adjustX = true;
				} else {
					p1.y = this.w.yFinish;
					Point p = points.get(2);
					if (p1.isVia || (p.x != p1.x && p.y != p1.y))
						adjustY = true;
				}
				if (adjustX || adjustY) {
					x = p1.x;
					y = p1.y;
					int i = 2;
					while (true) {
						p1 = points.get(i);
						if (p1.x == x || p1.y == y || i == size - 1)
							break;
						if (adjustX)
							p1.x = this.w.xFinish;
						else
							p1.y = this.w.yFinish;
						if (!p1.isVia)
							break;
						x = p1.x;
						y = p1.y;
						i++;
					}
				}
				adjustX = adjustY = false;
				if (Math.abs(p2.x - this.w.xStart) < Math.abs(p2.y
						- this.w.yStart)) {
					p2.x = this.w.xStart;
					Point p = points.get(size - 3);
					if (p2.isVia || (p.x != p2.x && p.y != p2.y))
						adjustX = true;
				} else {
					p2.y = this.w.yStart;
					Point p = points.get(size - 3);
					if (p2.isVia || (p.x != p2.x && p.y != p2.y))
						adjustY = true;
				}

				if (adjustX || adjustY) {
					x = p2.x;
					y = p2.y;
					int i = size - 3;
					while (true) {
						p2 = points.get(i);
						if (p2.x == x || p2.y == y || i == 0)
							break;
						if (adjustX)
							p2.x = this.w.xStart;
						else
							p2.y = this.w.yStart;
						if (!p2.isVia)
							break;
						x = p2.x;
						y = p2.y;
						i--;
					}
				}
			}
			RoutingContact pin;

			RoutePoint rpCurrent = null, rpLast = null, rpFix = null;
			RoutingLayer l = null;
			int i = 0;
			for (Point p : points) {
				if (i == 0) {
					if (GLOBALDETAILEDROUTING) {
						Point2D startPoint = this.w.segment.getStartEnd()
								.getLocation();
						if (p.x == startPoint.getX()
								&& p.y == startPoint.getY()) {
							pin = RoutingContact.STARTPOINT;
							l = metalLayers[p.z];
						} else {
							if (p.isVia) {
								pin = metalVias[Math.min(p.z, p.zLast)]
										.getVias().get(0).via;
								l = metalLayers[p.zLast];
							} else {
								l = metalLayers[p.z];
								pin = l.getPin();
							}
						}
					} else {
						pin = RoutingContact.FINISHPOINT;
						l = metalLayers[p.z];
					}
				} else if (i == points.size() - 1) {
					if (GLOBALDETAILEDROUTING) {
						Point2D finishPoint = this.w.segment.getFinishEnd()
								.getLocation();
						if (p.x == finishPoint.getX()
								&& p.y == finishPoint.getY()) {
							pin = RoutingContact.FINISHPOINT;
							l = metalLayers[p.z];
						} else {
							if (p.isVia) {
								pin = metalVias[Math.min(p.z, p.zLast)]
										.getVias().get(0).via;
								l = metalLayers[p.zLast];
							} else {
								l = metalLayers[p.z];
								pin = l.getPin();
							}
						}
					} else {
						pin = RoutingContact.STARTPOINT;
						l = metalLayers[p.z];
					}
				} else {
					if (p.isVia) {
						pin = metalVias[Math.min(p.z, p.zLast)].getVias()
								.get(0).via;
						l = metalLayers[p.zLast];
					} else {
						l = metalLayers[p.z];
						pin = l.getPin();
					}
				}

				rpCurrent = new RoutePoint(pin, new Point2D.Double(p.x, p.y), 0);
				if (p.isVia) {
					this.addWireEndVia(rpCurrent);
				} else {
					this.addWireEnd(rpCurrent);
				}
				if (i == 0 && p.isVia) {
					rpFix = new RoutePoint(metalVias[Math.min(p.z, p.zLast)]
							.getVias().get(0).via,
							new Point2D.Double(p.x, p.y), 0);
					this.addWireEndVia(rpFix);
					l = metalLayers[p.zLast];
					this.addWire(l, l.getMinWidth(), rpCurrent, rpFix,
							this.w.netID);
					rpCurrent = rpFix;
				} else if (i == points.size() - 1 && p.isVia) {
					rpFix = new RoutePoint(metalVias[Math.min(p.z, p.zLast)]
							.getVias().get(0).via,
							new Point2D.Double(p.x, p.y), 0);
					this.addWireEndVia(rpFix);
					l = metalLayers[p.zLast];
					this.addWire(l, l.getMinWidth(), rpLast, rpFix,
							this.w.netID);
					rpLast = rpFix;
					l = metalLayers[p.z];
				}
				if (rpLast != null) {
					this.addWire(l, l.getMinWidth(), rpLast, rpCurrent,
							this.w.netID);
				}
				rpLast = rpCurrent;
				i++;
			}
		}

		/**
		 * Method to round a value to the nearest point of our routing grid.
		 *
		 * @param d
		 *            The value to round.
		 * @return The corresponding grid value.
		 */
		private double toGrid(double d) {
			return Math.round(d * granularity) * stepsize;
		}

		private void addWireEnd(RoutePoint p) {
			this.w.segment.addWireEnd(p);
		}

		private void addWireEndVia(RoutePoint p) {
			this.w.segment.addWireEnd(p);
			RoutingContact rc = p.getContact();
			Point2D loc = p.getLocation();
			Rectangle2D rect = this
					.makeArcBox(loc, loc, rc.getViaSpacing() * 4);

			addRectangle(rect, rc.getFirstLayer(), -this.w.netID);
			addRectangle(rect, rc.getSecondLayer(), -this.w.netID);
		}

		/**
		 * Method to add a wire to the segment and update the R-Trees
		 * (blockages).
		 *
		 * @param l
		 *            The routing layer.
		 * @param width
		 *            The width of the wire.
		 * @param from
		 *            Start point of the wire.
		 * @param to
		 *            End point of the wire.
		 * @param netID
		 *            The network ID.
		 * @return The RouteWire that was added.
		 */
		private RouteWire addWire(RoutingLayer l, double width,
				RoutePoint from, RoutePoint to, int netID) {
			RouteWire rw = new RouteWire(l, from, to, width);
			this.w.segment.addWire(rw);
			Rectangle2D box = makeArcBox(from.getLocation(), to.getLocation(),
					width);
			if (box != null)
				addRectangle(box, l, netID);

			return rw;
		}

		private Rectangle2D makeArcBox(Point2D from, Point2D to, double width) {
			double w2 = width / 2;
			int angle = DBMath.figureAngle(from, to);
			switch (angle) {
			case 0:
			case 1800:
				double y = to.getY();
				double x1 = to.getX();
				double x2 = from.getX();
				return new Rectangle2D.Double(Math.min(x1, x2) - w2, y - w2,
						Math.abs(x1 - x2) + width, width);
			case 900:
			case 2700:
				double x = to.getX();
				double y1 = to.getY();
				double y2 = from.getY();
				return new Rectangle2D.Double(x - w2, Math.min(y1, y2) - w2,
						width, Math.abs(y1 - y2) + width);
			}
			if (enableOutput.getTempBooleanValue())
				System.out.println("ERROR: Angle not allowed!");
			return null;
		}
	} // end class Backtracking

	/**
	 * Class to to the backtracking after the wavefront reached the finish
	 * point.
	 */
	private class ExperimentalGlobalBacktracking {
		private Wavefront w;
		private double granularity;
		private double stepsize;

		public ExperimentalGlobalBacktracking(Wavefront w) {
			this.w = w;
			granularity = w.getGranularity();
			stepsize = 1 / granularity;
		}

		protected class Point {
			double x, y;

			Point(double x, double y, int zLast, int z) {
				this.x = x;
				this.y = y;
			}

			Point(double x, double y, int z) {
				this.x = x;
				this.y = y;
			}
		}

		public List<Point> routeIt() throws RatingNotFoundException {

			List<Point> points = new ArrayList<Point>();

			double x = this.w.xGridFinish, y = this.w.yGridFinish;

			int z = w.zFinish, zLast = w.zFinish;

			// add finish route point
			// points.add(new Point(this.w.xFinish, this.w.yFinish, z));

			int lastRating = 0;
			Rating tmp = w.grid[z].getRating(x, y);
			if (tmp != null)
				lastRating = tmp.getRating();
			else {
				if (enableOutput.getTempBooleanValue())
					System.out
							.println("Backtracking did not find a route (Nullpointer Rating)!");
				throw new RatingNotFoundException();
			}

			// loop until start point is reached
			outer: while ((x != this.w.xGridStart) || (y != this.w.yGridStart)
					|| z != w.zStart) {

				// Save the visited point if there wasn't a layer change
				// (layer changes doesnt matter in global routing)
				// if (z == zLast && (x != xNextToLast || y != yNextToLast)) {
				if (z == zLast) {
					points.add(new Point(x, y, z));
				}

				for (int i = 0; i < 6; i++) {
					double dx = 0, dy = 0;
					int dz = 0;
					switch (i) {
					// 0-3: the 4 directions on the same layer
					case 0:
						dy = stepsize;
						break;
					case 1:
						dx = stepsize;
						break;
					case 2:
						dy = -stepsize;
						break;
					case 3:
						dx = -stepsize;
						break;
					// 4,5: change layer
					case 4:
						dz = -1;
						break;
					case 5:
						dz = 1;
						break;
					}

					// check bounds
					if (z + dz < 0 || z + dz >= numMetalLayers)
						continue;

					Rating r = this.w.grid[z + dz].getRating(toGrid(x + dx),
							toGrid(y + dy));
					if (r == null || r.getRating() >= lastRating) {
						if (i == 5) {
							if (enableOutput.getTempBooleanValue())
								System.out
										.println("Backtracking did not find a route!");
							break outer;
						} else
							continue;
					}

					zLast = z;
					lastRating = r.getRating();
					x = toGrid(x + dx);
					y = toGrid(y + dy);
					z += dz;
					break;
				}
			}
			if (z != zLast) {
				points.add(new Point(this.w.xStart, this.w.yStart, zLast, z));
			} else {
				points.add(new Point(this.w.xStart, this.w.yStart, z));
			}

			return points;
		}

		/**
		 * Method to round a value to the nearest point of our routing grid.
		 *
		 * @param d
		 *            The value to round.
		 * @return The grid value.
		 */
		private double toGrid(double d) {
			return Math.round(d * granularity) * stepsize;
		}

	} // end class Backtracking

	private boolean initializeDesignRules(List<RoutingLayer> allLayers,
			List<RoutingContact> allContacts) {
		// find the metal layers, arcs, and contacts
		this.numMetalLayers = 0;
		for (RoutingLayer rl : allLayers)
			if (rl.isMetal())
				this.numMetalLayers++;
		this.metalLayers = new RoutingLayer[this.numMetalLayers];
		int metNo = 0;
		for (RoutingLayer rl : allLayers) {
			if (rl.isMetal())
				this.metalLayers[metNo++] = rl;
		}
		this.metalVias = new MetalVias[this.numMetalLayers - 1];
		for (int i = 0; i < this.numMetalLayers - 1; i++)
			this.metalVias[i] = new MetalVias();

		for (RoutingContact rc : allContacts) {
			for (int i = 0; i < this.numMetalLayers - 1; i++) {
				if ((rc.getFirstLayer() == this.metalLayers[i] && rc
						.getSecondLayer() == this.metalLayers[i + 1])
						|| (rc.getSecondLayer() == this.metalLayers[i] && rc
								.getFirstLayer() == this.metalLayers[i + 1])) {
					this.metalVias[i].addVia(rc);

					break;
				}
			}
		}
		for (int i = 0; i < numMetalLayers; i++) {
			if (metalLayers[i] == null) {
				if (enableOutput.getTempBooleanValue())
					System.out.println("ERROR: Cannot find layer for Metal "
							+ (i + 1));
				return true;
			}
			if (i < numMetalLayers - 1) {
				if (metalVias[i].getVias().size() == 0) {
					if (enableOutput.getTempBooleanValue())
						System.out
								.println("ERROR: Cannot find contact node between Metal "
										+ (i + 1) + " and Metal " + (i + 2));
					return true;
				}
			}
		}
		return false;
	}

	private class WavefrontExecutor implements Runnable {
		private Wavefront w;

		public WavefrontExecutor(Wavefront w) {
			this.w = w;
		}

		public void run() {
			w.propagate();
			try {
				outQueue.put(w);
			} catch (InterruptedException e) {
				if (enableOutput.getTempBooleanValue())
					e.printStackTrace();
			}
		}
	}

	/**
	 * Method to see if a proposed piece of metal has DRC errors (ignoring
	 * notches).
	 *
	 * @param netID
	 *            the network ID of the desired metal (blockages on this netID
	 *            are ignored).
	 * @param metNo
	 *            the level of the metal.
	 * @param halfWidth
	 *            half of the width of the metal.
	 * @param halfHeight
	 *            half of the height of the metal.
	 * @param surround
	 *            is the maximum possible DRC surround around the metal.
	 * @param x
	 *            the X coordinate at the center of the metal.
	 * @param y
	 *            the Y coordinate at the center of the metal.
	 * @return a blocking SOGBound object that is in the area. Returns null if
	 *         the area is clear.
	 */
	private LMBound getMetalBlockage(int netID, int metNo, double halfWidth,
			double halfHeight, double surround, double x, double y) {
		// get the R-Tree data for the metal layer
		RoutingLayer layer = metalLayers[metNo];
		RTNode<LMBound> rtree = metalBlockages.get(layer);
		if (rtree == null)
			return null;

		// compute the area to search
		double lX = x - halfWidth - surround, hX = x + halfWidth + surround;
		double lY = y - halfHeight - surround, hY = y + halfHeight + surround;
		Rectangle2D searchArea = new Rectangle2D.Double(lX, lY, hX - lX, hY
				- lY);

		try {
			// see if there is anything in that area
			for (Iterator<LMBound> sea = new RTNode.Search<LMBound>(searchArea, rtree, true); sea
					.hasNext();) {
				LMBound sBound = sea.next();

				// ignore if on the same net
				if (sBound.getNetID() == netID)
					continue;

				return sBound;
			}
		} catch (NullPointerException e) {
			return new LMBound(new Rectangle2D.Double(), -1);
		}
		return null;
	}

	/**
	 * Method to add extra blockage information that corresponds to ends of
	 * unrouted arcs.
	 *
	 * @param segmentsToRoute
	 *            The list of segments to route.
	 * @param tech
	 *            The technology to use.
	 */
	private void addBlockagesAtPorts(List<RoutingSegment> segmentsToRoute) {
		for (RoutingSegment rs : segmentsToRoute) {
			addBlockage(rs.getStartEnd().getLocation(), rs.getStartLayers(), rs
					.getStartLayers().get(0).getMinWidth() + 2, rs.getNetID());
			addBlockage(rs.getFinishEnd().getLocation(), rs.getFinishLayers(),
					rs.getFinishLayers().get(0).getMinWidth() + 2, rs
							.getNetID());
		}
	}

	private void addBlockage(Point2D loc, List<RoutingLayer> layers,
			double minWidth, int netID) {
		int lowMetal = -1, highMetal = -1;
		for (RoutingLayer rl : layers) {
			int level = rl.getMetalNumber();
			if (lowMetal < 0)
				lowMetal = highMetal = level;
			else {
				lowMetal = Math.min(lowMetal, level);
				highMetal = Math.max(highMetal, level);
			}
		}
		if (lowMetal < 0)
			return;
		// reserve space on layers above and below
		for (int via = lowMetal - 2; via < highMetal; via++) {
			if (via < 0 || via >= numMetalLayers - 1)
				continue;
			MetalVia mv = metalVias[via].getVias().get(0);
			for (RoutingGeometry rg : mv.via.getGeometry()) {
				Rectangle2D bounds = new Rectangle2D.Double(loc.getX()
						- minWidth, loc.getY() - minWidth, 2 * minWidth,
						2 * minWidth);
				addRectangle(bounds, rg.getLayer(), netID);
			}
		}
	}

	/**
	 * Method to add a rectangle to the metal R-Tree.
	 *
	 * @param bounds
	 *            the rectangle to add.
	 * @param layer
	 *            the metal layer on which to add the rectangle.
	 * @param netID
	 *            the global network ID of the geometry.
	 */
	private void addRectangle(Rectangle2D bounds, RoutingLayer layer, int netID) {
		RTNode<LMBound> root = this.metalBlockages.get(layer);
		if (root == null) {
			root = RTNode.makeTopLevel();
			this.metalBlockages.put(layer, root);
		}
		RTNode<LMBound> newRoot = RTNode
				.linkGeom(null, root, new LMBound(bounds, netID));
		if (newRoot != root)
			this.metalBlockages.put(layer, newRoot);
	}

	/**
	 * Method to add a point to the via R-Tree.
	 *
	 * @param loc
	 *            The point to add.
	 * @param layer
	 *            The layer on which to add the point.
	 * @param netID
	 *            The global network ID of the geometry.
	 */
	private void addVia(Point2D loc, RoutingLayer layer, int netID) {
		RTNode<LMVia> root = this.viaBlockages.get(layer);
		if (root == null) {
			root = RTNode.makeTopLevel();
			this.viaBlockages.put(layer, root);
		}
		RTNode<LMVia> newRoot = RTNode.linkGeom(null, root, new LMVia(loc, netID));
		if (newRoot != root)
			this.viaBlockages.put(layer, newRoot);
	}

	/**
	 * Class to define a list of possible nodes that can connect two layers.
	 */
	private static class MetalVia {
		RoutingContact via;

		MetalVia(RoutingContact v) {
			via = v;
		}
	}

	/**
	 * Class to define a list of possible nodes that can connect two layers.
	 * This includes orientation
	 */
	private static class MetalVias {
		List<MetalVia> vias = new ArrayList<MetalVia>();

		void addVia(RoutingContact pn) {
			vias.add(new MetalVia(pn));
			Collections.sort(vias, new PrimsBySize());
		}

		List<MetalVia> getVias() {
			return vias;
		}
	}

	/**
	 * Comparator class for sorting primitives by their size.
	 */
	private static class PrimsBySize implements Comparator<MetalVia> {
		/**
		 * Method to sort primitives by their size.
		 */
		public int compare(MetalVia mv1, MetalVia mv2) {
			RoutingContact pn1 = mv1.via;
			RoutingContact pn2 = mv2.via;
			double sz1 = pn1.getDefWidth() * pn1.getDefHeight();
			double sz2 = pn2.getDefWidth() * pn2.getDefHeight();
			if (sz1 < sz2)
				return -1;
			if (sz1 > sz2)
				return 1;
			return 0;
		}
	}

	/**
	 * Class to define an R-Tree leaf node for geometry in the blockage data
	 * structure.
	 */
	static class LMBound implements RTBounds {
		private FixpRectangle bound;
		private int netID;

		LMBound(Rectangle2D bound, int netID) {
			this.bound = FixpRectangle.from(bound);
			this.netID = netID;
		}

        @Override
		public FixpRectangle getBounds() {
			return bound;
		}

		public int getNetID() {
			return netID;
		}

		// public String toString() { return "LMBound on net " + netID; }
	}

	/**
	 * Class to define an R-Tree leaf node for vias in the blockage data
	 * structure.
	 */
	private static class LMVia implements RTBounds {
        private FixpRectangle bound;
//		private Point2D loc;
		private int netID;

		LMVia(Point2D loc, int netID) {
            bound = FixpRectangle.from(new Rectangle2D.Double(loc.getX(), loc.getY(), 0, 0));
//			this.loc = loc;
			this.netID = netID;
		}

        @Override
		public FixpRectangle getBounds() {
            return bound;
		}

        @Override
		public String toString() {
			return "LMVia on net " + netID;
		}
	}
}
