/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: RoutingDebug.java
 *
 * Copyright (c) 2011, Oracle and/or its affiliates. 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.user.ui;

import com.sun.electric.database.geometry.EGraphics;
import com.sun.electric.database.geometry.EPoint;
import com.sun.electric.database.geometry.ERectangle;
import com.sun.electric.database.geometry.Poly.Type;
import com.sun.electric.database.geometry.Poly3D;
import com.sun.electric.database.geometry.PolyBase;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.text.TextUtils;
import com.sun.electric.database.topology.ArcInst;
import com.sun.electric.database.topology.PortInst;
import com.sun.electric.database.variable.EditWindow_;
import com.sun.electric.database.variable.UserInterface;
import com.sun.electric.technology.Layer;
import com.sun.electric.technology.Technology;
import com.sun.electric.technology.technologies.Generic;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.JobException;
import com.sun.electric.tool.routing.Routing;
import com.sun.electric.tool.routing.SeaOfGates;
import com.sun.electric.tool.routing.SeaOfGates.SeaOfGatesCellParameters;
import com.sun.electric.tool.routing.SeaOfGates.SeaOfGatesOptions;
import com.sun.electric.tool.routing.seaOfGates.SeaOfGatesEngine;
import com.sun.electric.tool.routing.seaOfGates.SeaOfGatesEngine.GRBucket;
import com.sun.electric.tool.routing.seaOfGates.SeaOfGatesEngine.GRNet;
import com.sun.electric.tool.routing.seaOfGates.SeaOfGatesEngine.GRWire;
import com.sun.electric.tool.routing.seaOfGates.SeaOfGatesEngine.GlobalRouter;
import com.sun.electric.tool.routing.seaOfGates.SeaOfGatesEngine.NeededRoute;
import com.sun.electric.tool.routing.seaOfGates.SeaOfGatesEngine.SOGBound;
import com.sun.electric.tool.routing.seaOfGates.SeaOfGatesEngine.SOGVia;
import com.sun.electric.tool.routing.seaOfGates.SeaOfGatesEngine.SearchVertex;
import com.sun.electric.tool.routing.seaOfGates.SeaOfGatesEngine.Wavefront;
import com.sun.electric.tool.routing.seaOfGates.SeaOfGatesEngineFactory;
import com.sun.electric.tool.routing.seaOfGates.SeaOfGatesEngineFactory.SeaOfGatesEngineType;
import com.sun.electric.tool.routing.seaOfGates.SeaOfGatesHandlers;
import com.sun.electric.tool.routing.seaOfGates.SeaOfGatesHandlers.Save;
import com.sun.electric.tool.user.Highlight;
import com.sun.electric.tool.user.Highlighter;
import com.sun.electric.tool.user.User;
import com.sun.electric.tool.user.UserInterfaceMain;
import com.sun.electric.tool.user.dialogs.EModelessDialog;
import com.sun.electric.tool.user.ui.ToolBar.CursorMode;
import com.sun.electric.util.math.DBMath;
import com.sun.electric.util.math.GenMath;
import com.sun.electric.util.math.MutableInteger;

import java.awt.Color;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;

/**
 * Class to handle routing mode.
 */
public class RoutingDebug
{
	private static final double goalWidth = 0.005;
	private static final double layerOffset = 0.04;

	private static RoutingDialog debugDialog = null;
	private static SVState currentSVHighlight = null;
	private static boolean endADebug, debugIn3D;
	private static Map<Integer,Color> netColors;
	private static Map<Integer,Integer> netAngles;
	private static int angleAssigned;
	private static DebugType debuggingType;
	private static enum DebugType {NONE, DISPLAYROUTING, DISPLAYENDBLOCKAGES, DISPLAYAREABLOCKAGES, DISPLAYROUTINGGRID, REWIRENETS, RUNGLOBALROUTING};

	/************************************* CONTROL *************************************/

	/**
	 * Method to do bring up the dialog for interactive routing.
	 */
	public static void startDebugging()
	{
		// enable routing mode in click/zoom/wire listener
		User.setRoutingMode(true);
		ToolBar.setCursorMode(CursorMode.ROUTING);

		debugDialog = new RoutingDialog();
	}

	/**
	 * Method to close the interactive routing dialog.
	 */
	private static void endDebugging()
	{
		// disable routing mode in click/zoom/wire listener
		User.setRoutingMode(false);
		if (ToolBar.getCursorMode() == CursorMode.ROUTING)
			ToolBar.setCursorMode(CursorMode.CLICKZOOMWIRE);

		if (debugDialog != null)
		{
			// remove dialog
			debugDialog.setVisible(false);
			debugDialog.dispose();
			debugDialog = null;
		}
	}

	/**
	 * Method to tell whether the interactive routing dialog is present.
	 * @return true if the interactive routing dialog is present.
	 */
	public static boolean isActive() { return debugDialog != null; }

	/************************************* DEBUGGING ROUTING *************************************/

	/**
	 * Method called when a route is to be debugged.
	 * @param endA true to debug the A->B wavefront, false for the B->A wavefront.
	 * @param threeD true to show results in 3D.
	 */
	private static void showRouting(boolean endA, boolean threeD)
	{
		debuggingType = DebugType.DISPLAYROUTING;
		endADebug = endA;
		debugIn3D = threeD;
		debugDialog.router = SeaOfGatesEngineFactory.createSeaOfGatesEngine(SeaOfGatesEngineType.defaultVersion);
		SeaOfGatesOptions prefs = new SeaOfGatesOptions();
		prefs.getOptionsFromPreferences(false);
		debugDialog.router.setPrefs(prefs);
		SeaOfGates.seaOfGatesRoute(UserInterfaceMain.getEditingPreferences(), debugDialog.router);
	}

	/**
	 * Method to tell whether a route is being debugged.
	 * @return true if a route is being debugged.
	 */
	public static boolean isDisplayRouting() { return debugDialog != null && debuggingType == DebugType.DISPLAYROUTING; }

	/**
	 * Method to run the routing and show the results.
	 * @param nr the NeededRoute to run.
	 */
	public static void debugRoute(NeededRoute nr, List<NeededRoute> allRoutes)
	{
		if (debugDialog == null) return;
		DebugThread runnable = new DebugThread(nr, allRoutes);
		runnable.startJob();
	}

	private static class DebugThread extends Job {
		private NeededRoute nr;
		private final List<NeededRoute> allRoutes;

		private DebugThread(NeededRoute nr, List<NeededRoute> allRoutes) {
            super("Debug Sea-Of-Gates Route", User.getUserTool(), Job.Type.CLIENT_EXAMINE, null, null, Job.Priority.USER);
			this.nr = nr;
			this.allRoutes = allRoutes;
		}

		@Override
		public boolean doIt() throws JobException
		{
			int totalRoutes = allRoutes.size();
			for (int r = 0; r < totalRoutes; r++)
			{
				// get information on the segment to be routed
				NeededRoute nrThis = allRoutes.get(r);
				if (nrThis != nr)
				{
					if (!SeaOfGatesEngine.DEBUGGRIDDING) continue;
					if (nrThis.checkEndSurround()) continue;
					System.out.println("Routing network " + nrThis.getName() + "...");
					RoutingDialog save = debugDialog;
					debugDialog = null;
					Wavefront[] wavefronts = nrThis.makeWavefronts();
					SearchVertex result = null;
					while (result == null)
					{
						SearchVertex resultA = wavefronts[0].advanceWavefront();
						SearchVertex resultB = wavefronts[1].advanceWavefront();
						if (resultA != null || resultB != null)
						{
							if (resultA == SeaOfGatesEngine.svAborted || resultB == SeaOfGatesEngine.svAborted)
							{
								result = SeaOfGatesEngine.svAborted;
								break;
							}
							if (resultA == SeaOfGatesEngine.svLimited || resultB == SeaOfGatesEngine.svLimited)
							{
								result = SeaOfGatesEngine.svLimited;
								break;
							}
							if (resultA == SeaOfGatesEngine.svExhausted || resultB == SeaOfGatesEngine.svExhausted)
							{
								result = SeaOfGatesEngine.svExhausted;
								break;
							}
							result = resultA;
							if (result == null)
								result = resultB;
						}
					}
					nrThis.completeRoute(result);
					debugDialog = save;
					continue;
				}

				EditWindow wnd = EditWindow.getCurrent();
				Cell cell = wnd.getCell();
				Highlighter h = wnd.getRulerHighlighter();
				h.clear();
showGeometryInArea(nr);
				// do the routing
				debugDialog.svInfo = new HashMap<SearchVertex,SVState>();
				SearchVertex result = null;
				int svOrder = 1;
				Wavefront wf = nr.makeWavefronts()[endADebug ? 0 : 1];
				while (result == null)
				{
					// label the next point at the start of the wavefront
					SearchVertex sv = wf.getNextSearchVertex();
					if (sv != SeaOfGatesEngine.svExhausted && sv != SeaOfGatesEngine.svLimited)
					{
						SVState svs = SVState.ensure(sv, cell);
						if (sv.getLast() == null) svs.changeLabel("START", h); else
							svs.changeLabel((svOrder++) + "", h);
					}

					// advance the wavefront
					result = wf.advanceWavefront();
				}

				if (debugIn3D)
				{
					SwingUtilities.invokeLater(new Show3DRoute(wf, nr, debugDialog.router, cell));
					return true;
				}

				// show goal
				debugDialog.showPathToGoal(result, cell, h);

				// draw the search vertices
				debugDialog.showSearchVertices(cell, h, wf);

				// show Global Routing information
				if (debugDialog.globalRoutingResults != null)
				{
					debugDialog.showGlobalRoutingGrid();
					debugDialog.showGlobalRoutingPath(nr);
				}

				h.finished();
				EditWindow.repaintAllContents();
				break;
			}
			return true;
		}

		@Override
		public void terminateOK()
		{
			// highlight the starting vertex
			SearchVertex svStart = null;
			for(SearchVertex sv : debugDialog.svInfo.keySet())
			{
				if (sv.getLast() == null) { svStart = sv;  break; }
			}
			showSelectedSV(svStart);
		}
	}

	/**
	 * Method called during search to record a directional link between two SearchVertex objects.
	 * @param sv the SearchVertex that was created.
	 * @param lastDirection the direction in which the last SearchVertex came to this one.
	 */
	public static void saveSVLink(SearchVertex sv, int lastDirection)
	{
		if (debugDialog != null)
		{
			if (lastDirection >= 0)
			{
				EditWindow wnd = EditWindow.getCurrent();
				Cell cell = wnd.getCell();
				SVState svs = SVState.ensure(sv.getLast(), cell);
				svs.nextVertices[lastDirection] = sv;
			}
		}
	}

	/**
	 * Method called during search to save cost information about a SearchVertex.
	 * @param sv the SearchVertex that was just analyzed.
	 * @param details a description of the cost and other information.
	 */
	public static void saveSVDetails(SearchVertex sv, String[] details)
	{
		if (debugDialog != null)
		{
			EditWindow wnd = EditWindow.getCurrent();
			Cell cell = wnd.getCell();
			SVState svs = SVState.ensure(sv, cell);
			svs.details = details;
		}
	}

	/**
	 * Method called from user interface to identify a SearchVertex at a given coordinate.
	 * @param evt the coordinates of the mouse.
	 * @return SearchVertex that was clicked.
	 */
	public static SearchVertex findDebugSearchVertex(MouseEvent evt)
	{
		if (debugDialog == null || debugDialog.svInfo == null) return null;
		EditWindow wnd = (EditWindow)evt.getSource();
		if (wnd.getScale() < 25) return null;
		Point2D dbClick = wnd.screenToDatabase(evt.getX(), evt.getY());

		// cycle through objects
		double bestDist = Double.MAX_VALUE;
		SearchVertex bestSV = null;
		for(SearchVertex sv : debugDialog.svInfo.keySet())
		{
			double off = sv.getZ() * layerOffset;
			double dX = sv.getX() + off - dbClick.getX();
			double dY = sv.getY() + off - dbClick.getY();
			double dist = Math.sqrt(dX*dX + dY*dY);
			if (dist < bestDist)
			{
				bestDist = dist;
				bestSV = sv;
			}
		}
		if (bestDist < 1) return bestSV;
		return null;
	}

	/**
	 * Method called from user interface to show when the mouse hovers over a SearchVertex.
	 * @param sv the SearchVertex to highlight.
	 */
	public static void previewSelectedSV(SearchVertex sv, boolean center)
	{
		if (debugDialog == null) return;
		EditWindow wnd = EditWindow.getCurrent();
		Highlighter h = wnd.getRulerHighlighter();
		SVState svs = debugDialog.svInfo.get(sv);
		if (currentSVHighlight != null) currentSVHighlight.setBackgroundColor(null, h);
		if (debugDialog.currentSV != null)
		{
			SVState svsHigh = debugDialog.svInfo.get(debugDialog.currentSV);
			if (svsHigh != null)
			{
				svsHigh.setBackgroundColor(Color.WHITE, h);
				h.finished();
			}
		}
		currentSVHighlight = svs;
		if (currentSVHighlight != null)
		{
			currentSVHighlight.setBackgroundColor(Color.RED, h);
			h.finished();
			if (center)
			{
				Rectangle2D windowBound = wnd.getDisplayedBounds();
				if (sv.getX() < windowBound.getMinX() || sv.getX() > windowBound.getMaxX() ||
					sv.getY() < windowBound.getMinY() || sv.getY() > windowBound.getMaxY())
						wnd.setOffset(new Point2D.Double(sv.getX(), sv.getY()));
			}
			wnd.fullRepaint();
		}
	}

	/**
	 * Method called from user interface to show a SearchVertex that was clicked.
	 * @param sv SearchVertex that was clicked.
	 */
	public static void showSelectedSV(SearchVertex sv)
	{
		debugDialog.seeSelectedSV(sv);
	}

	/************************************* DEBUGGING BLOCKAGES AT START AND END OF ROUTE *************************************/

	/**
	 * Method called when blockages at the ends of a route are to be viewed.
	 */
	private static void showEndBlockages()
	{
		debugDialog.router = SeaOfGatesEngineFactory.createSeaOfGatesEngine(SeaOfGatesEngineType.defaultVersion);
		SeaOfGatesOptions prefs = new SeaOfGatesOptions();
		prefs.getOptionsFromPreferences(false);
		debuggingType = DebugType.DISPLAYENDBLOCKAGES;
		debugDialog.router.setPrefs(prefs);
		SeaOfGates.seaOfGatesRoute(UserInterfaceMain.getEditingPreferences(), debugDialog.router);
	}

	/**
	 * Method to tell whether blockages at the ends of a route are to be viewed.
	 * @return true if blockages at the ends of a route are to be viewed.
	 */
	public static boolean isDisplayEndBlockages() { return debugDialog != null && debuggingType == DebugType.DISPLAYENDBLOCKAGES; }

	/**
	 * Method to show blockages at the ends of a route.
	 * @param nr the NeededRoute to view.
	 */
	public static void showGeometryAtRouteEnds(NeededRoute nr)
	{
		debuggingType = DebugType.NONE;
		if (debugDialog == null) return;

		EditWindow wnd = EditWindow.getCurrent();
		Cell cell = wnd.getCell();
		Highlighter h = wnd.getRulerHighlighter();
		h.clear();
		double examineSurround = 10;
		angleAssigned = 45;
		netColors = new HashMap<Integer,Color>();
		netAngles = new HashMap<Integer,Integer>();
		double lowestX = Double.MAX_VALUE, lowestY = Double.MAX_VALUE;
		debugDialog.setRouteDescription("Netlist information at ends of selected route", null);

		// show the bounds of the route
		Rectangle2D limit = nr.getBounds();
		showBounds(cell, limit, h, Color.ORANGE);
		Map<Layer,List<SOGBound>> allAssigned = new HashMap<Layer,List<SOGBound>>();

		// show geometry around the A end
		Rectangle2D headBound = new Rectangle2D.Double(nr.getAX()-examineSurround, nr.getAY()-examineSurround,
			examineSurround*2, examineSurround*2);
		for(int i=0; i<debugDialog.router.getNumMetals(); i++)
		{
			Layer layer = debugDialog.router.getMetalLayer(i);
			List<SOGBound> assigned = new ArrayList<SOGBound>();
			allAssigned.put(layer, assigned);
			for (Iterator<SOGBound> sea = debugDialog.router.searchMetalTree(layer, headBound); sea.hasNext();)
			{
				SOGBound sBound = sea.next();
				if (sBound.getNetID() != null && sBound.getNetID().intValue() != 0) assigned.add(sBound); else
				{
					ERectangle drawn = showGeometryPiece(sBound, limit, layer);
					if (drawn != null)
					{
						if (drawn.getMinX() < lowestX) lowestX = drawn.getMinX();
						if (drawn.getMinY() < lowestY) lowestY = drawn.getMinY();
					}
				}
			}
		}

		// show geometry around the B end
		Rectangle2D tailBound = new Rectangle2D.Double(nr.getBX()-examineSurround, nr.getBY()-examineSurround,
			examineSurround*2, examineSurround*2);
		for(int i=0; i<debugDialog.router.getNumMetals(); i++)
		{
			Layer layer = debugDialog.router.getMetalLayer(i);
			List<SOGBound> assigned = allAssigned.get(layer);
			for (Iterator<SOGBound> sea = debugDialog.router.searchMetalTree(layer, tailBound); sea.hasNext();)
			{
				SOGBound sBound = sea.next();
				if (sBound.getNetID() != null && sBound.getNetID().intValue() != 0) assigned.add(sBound); else
				{
					ERectangle drawn = showGeometryPiece(sBound, limit, layer);
					if (drawn != null)
					{
						if (drawn.getMinX() < lowestX) lowestX = drawn.getMinX();
						if (drawn.getMinY() < lowestY) lowestY = drawn.getMinY();
					}
				}
			}
		}

		// show assigned geometry last
		for(int i=0; i<debugDialog.router.getNumMetals(); i++)
		{
			Layer layer = debugDialog.router.getMetalLayer(i);
			List<SOGBound> assigned = allAssigned.get(layer);
			for(SOGBound sBound : assigned)
			{
				ERectangle drawn = showGeometryPiece(sBound, limit, layer);
				if (drawn != null)
				{
					if (drawn.getMinX() < lowestX) lowestX = drawn.getMinX();
					if (drawn.getMinY() < lowestY) lowestY = drawn.getMinY();
				}
			}
		}

		// show key
		double pos = lowestY - 10;
		for(Integer netIDI : netColors.keySet())
		{
			Color color = netColors.get(netIDI);
			Integer angle = netAngles.get(netIDI);
			Rectangle2D bound = new Rectangle2D.Double(lowestX, pos, 4, 2);
			showStripedRect(bound, color, angle.intValue());
			h.addMessage(cell, getNetName(netIDI), EPoint.fromLambda(lowestX-10, pos+1));
			pos -= 3;
		}
		h.finished();
		EditWindow.repaintAllContents();
	}

	/************************************* DEBUGGING BLOCKAGES IN AN AREA *************************************/

	/**
	 * Method called when blockages in a routing area are to be viewed.
	 */
	private static void showAreaBlockages()
	{
		debugDialog.router = SeaOfGatesEngineFactory.createSeaOfGatesEngine(SeaOfGatesEngineType.defaultVersion);
		SeaOfGatesOptions prefs = new SeaOfGatesOptions();
		prefs.getOptionsFromPreferences(false);
		debuggingType = DebugType.DISPLAYAREABLOCKAGES;
		debugDialog.router.setPrefs(prefs);
		SeaOfGates.seaOfGatesRoute(UserInterfaceMain.getEditingPreferences(), debugDialog.router);
	}

	/**
	 * Method to tell whether blockages in a routing area are to be viewed.
	 * @return true if blockages in a routing area are to be viewed.
	 */
	public static boolean isDisplayAreaBlockages() { return debugDialog != null && debuggingType == DebugType.DISPLAYAREABLOCKAGES; }

	/**
	 * Method to show blockages in a routing area.
	 * @param nr the NeededRoute to view.
	 */
	public static void showGeometryInArea(NeededRoute nr)
	{
		debuggingType = DebugType.NONE;
		if (debugDialog == null) return;

		EditWindow wnd = EditWindow.getCurrent();
		Cell cell = wnd.getCell();
		Rectangle2D limit = nr.getBounds();
		Highlighter h = wnd.getRulerHighlighter();
		h.clear();
		angleAssigned = 45;
		netColors = new HashMap<Integer,Color>();
		netAngles = new HashMap<Integer,Integer>();
		double lowestX = Double.MAX_VALUE, lowestY = Double.MAX_VALUE;
		debugDialog.setRouteDescription("Netlist information for selected area", null);

		// show the bounds of the route
		showBounds(cell, limit, h, Color.ORANGE);

		// show geometry in area
		Map<Layer,List<SOGBound>> allAssigned = new HashMap<Layer,List<SOGBound>>();
		for(int i=0; i<debugDialog.router.getNumMetals(); i++)
		{
			Layer layer = debugDialog.router.getMetalLayer(i);
			List<SOGBound> assigned = new ArrayList<SOGBound>();
			allAssigned.put(layer, assigned);
			for (Iterator<SOGBound> sea = debugDialog.router.searchMetalTree(layer, limit); sea.hasNext();)
			{
				SOGBound sBound = sea.next();
				if (sBound.getNetID() != null && sBound.getNetID().intValue() != 0) assigned.add(sBound); else
				{
					ERectangle drawn = showGeometryPiece(sBound, limit, layer);
					if (drawn != null)
					{
						if (drawn.getMinX() < lowestX) lowestX = drawn.getMinX();
						if (drawn.getMinY() < lowestY) lowestY = drawn.getMinY();
					}
				}
			}
		}
		for(int i=0; i<debugDialog.router.getNumMetals(); i++)
		{
			Layer layer = debugDialog.router.getMetalLayer(i);
			List<SOGBound> assigned = allAssigned.get(layer);
			for(SOGBound sBound : assigned)
			{
				ERectangle drawn = showGeometryPiece(sBound, limit, layer);
				if (drawn != null)
				{
					if (drawn.getMinX() < lowestX) lowestX = drawn.getMinX();
					if (drawn.getMinY() < lowestY) lowestY = drawn.getMinY();
				}
			}
		}

		// show key
		double pos = lowestY - 10;
		for(Integer netIDI : netColors.keySet())
		{
			Color color = netColors.get(netIDI);
			Integer angle = netAngles.get(netIDI);
			Rectangle2D bound = new Rectangle2D.Double(lowestX, pos, 4, 2);
			showStripedRect(bound, color, angle.intValue());
			h.addMessage(cell, getNetName(netIDI), EPoint.fromLambda(lowestX-10, pos+1));
			pos -= 3;
		}
		h.finished();
		EditWindow.repaintAllContents();
	}

	/************************************* ROUTING DEBUGGING GRID *************************************/

	private static void showRoutingGrid()
	{
		debugDialog.router = SeaOfGatesEngineFactory.createSeaOfGatesEngine(SeaOfGatesEngineType.defaultVersion);
		SeaOfGatesOptions prefs = new SeaOfGatesOptions();
		prefs.getOptionsFromPreferences(false);
		debuggingType = DebugType.DISPLAYROUTINGGRID;
		debugDialog.router.setPrefs(prefs);
		SeaOfGates.seaOfGatesRoute(UserInterfaceMain.getEditingPreferences(), debugDialog.router);
	}

	public static boolean isTestRoutingGrid() { return debugDialog != null && debuggingType == DebugType.DISPLAYROUTINGGRID; }

	public static void showRoutingGrid(NeededRoute nr)
	{
		int metNum = TextUtils.atoi(debugDialog.whichLayer.getText());
		int numMetalLayers = debugDialog.router.getNumMetals();
		if (metNum <= 0 || metNum > numMetalLayers)
		{
			Job.getUserInterface().showErrorMessage("Invalid layer number (" + metNum + ") must be between 1 and " + numMetalLayers, "Invalid Layer");
			return;
		}
		debuggingType = DebugType.NONE;
		if (debugDialog == null) return;

		EditWindow wnd = EditWindow.getCurrent();
		Cell cell = wnd.getCell();
		Rectangle2D limit = nr.getBounds();
		Highlighter h = wnd.getRulerHighlighter();
		h.clear();
		debugDialog.setRouteDescription("Routing grid for Metal-" + metNum, null);

		// show the bounds of the route
		showBounds(cell, limit, h, Color.ORANGE);

		// show the routing grid
		double[][] gridLocationsX = nr.getXRoutingGrid();
		double[][] gridLocationsY = nr.getYRoutingGrid();
		SeaOfGatesCellParameters sogp = new SeaOfGatesCellParameters(cell);
		boolean hor = true;
		if (sogp.isHorizontalEven())
		{
			if ((metNum%2) != 0) hor = false;
		} else
		{
			if ((metNum%2) == 0) hor = false;
		}
		if (hor)
		{
			// draw horizontal grid lines
			double[] gridLines = gridLocationsY[metNum-1];
			if (gridLines != null)
			{
				for(int i=0; i<gridLines.length; i++)
				{
					Point2D p1 = new Point2D.Double(limit.getMinX(), gridLines[i]);
					Point2D p2 = new Point2D.Double(limit.getMaxX(), gridLines[i]);
					h.addLine(p1, p2, cell, false, Color.WHITE, false);
					if (metNum > 1)
					{
						// show down-layer stops
						double[] gridDown = gridLocationsX[metNum-2];
						if (gridDown != null)
						{
							for(int j=0; j<gridDown.length; j++)
							{
								p1 = new Point2D.Double(gridDown[j], gridLines[i]);
								p2 = new Point2D.Double(gridDown[j], gridLines[i]-5);
								h.addLine(p1, p2, cell, false, Color.WHITE, false);								
							}
						}
					}
					if (metNum < numMetalLayers)
					{
						// show up-layer stops
						double[] gridUp = gridLocationsX[metNum];
						if (gridUp != null)
						{
							for(int j=0; j<gridUp.length; j++)
							{
								p1 = new Point2D.Double(gridUp[j], gridLines[i]);
								p2 = new Point2D.Double(gridUp[j], gridLines[i]+5);
								h.addLine(p1, p2, cell, false, Color.WHITE, false);								
							}
						}
					}
				}
			}
		} else
		{
			// draw vertical grid lines
			double[] gridLines = gridLocationsX[metNum-1];
			if (gridLines != null)
			{
				for(int i=0; i<gridLines.length; i++)
				{
					Point2D p1 = new Point2D.Double(gridLines[i], limit.getMinY());
					Point2D p2 = new Point2D.Double(gridLines[i], limit.getMaxY());
					h.addLine(p1, p2, cell, false, Color.WHITE, false);
					if (metNum > 1)
					{
						// show down-layer stops
						double[] gridDown = gridLocationsY[metNum-2];
						if (gridDown != null)
						{
							for(int j=0; j<gridDown.length; j++)
							{
								p1 = new Point2D.Double(gridLines[i], gridDown[j]);
								p2 = new Point2D.Double(gridLines[i]-5, gridDown[j]);
								h.addLine(p1, p2, cell, false, Color.WHITE, false);								
							}
						}
					}
					if (metNum < numMetalLayers)
					{
						// show up-layer stops
						double[] gridUp = gridLocationsY[metNum];
						if (gridUp != null)
						{
							for(int j=0; j<gridUp.length; j++)
							{
								p1 = new Point2D.Double(gridLines[i], gridUp[j]);
								p2 = new Point2D.Double(gridLines[i]+5, gridUp[j]);
								h.addLine(p1, p2, cell, false, Color.WHITE, false);								
							}
						}
					}
				}
			}
		}

		h.finished();
		EditWindow.repaintAllContents();
	}

	/************************************* DEBUGGING GLOBAL ROUTING *************************************/

	/**
	 * Method called when global routing results are to be viewed.
	 */
	public static void doGlobalRouting()
	{
		debugDialog.router = SeaOfGatesEngineFactory.createSeaOfGatesEngine(SeaOfGatesEngineType.defaultVersion);
		SeaOfGatesOptions prefs = new SeaOfGatesOptions();
		prefs.getOptionsFromPreferences(false);
		debuggingType = DebugType.RUNGLOBALROUTING;
		debugDialog.router.setPrefs(prefs);
		SeaOfGates.seaOfGatesRoute(UserInterfaceMain.getEditingPreferences(), debugDialog.router);
	}

	public static int getDesiredRouteToDebug()
	{
		String selection = debugDialog.whichOne.getText().trim();
		if (selection.length() == 0) return 0;
		return TextUtils.atoi(selection);
	}

	public static boolean isTestGlobalRouting() { return debugDialog != null && debuggingType == DebugType.RUNGLOBALROUTING; }

	public static void setGlobalRouting(GlobalRouter gr) { debugDialog.globalRoutingResults = gr; }

	public static void showGlobalRouting()
	{
		EditWindow wnd = EditWindow.getCurrent();
		Highlighter h = wnd.getRulerHighlighter();
		h.clear();

		// show grid lines around buckets
		debugDialog.showGlobalRoutingGrid();

		// show global routes
		debugDialog.showGlobalRoutingPath(null);

		// redraw
		h.finished();
		wnd.repaint();
	}

	private static class SVState
	{
		Cell cell;
		EPoint anchor;
		String msg;
		Highlight label;
		String[] details;
		SearchVertex[] nextVertices = new SearchVertex[6];

		SVState(SearchVertex sv, Cell cell)
		{
			this.cell = cell;
			double off = sv.getZ() * layerOffset;
			anchor = EPoint.fromLambda(sv.getX()+off, sv.getY()+off);
			msg = "M" + (sv.getZ()+1);
			label = null;
		}

		void showLabel(Highlighter h)
		{
			if (label != null) h.remove(label);
			label = h.addMessage(cell, msg, anchor);
		}

		void changeLabel(String msg, Highlighter h)
		{
			this.msg = msg;
			showLabel(h);
		}

		void setBackgroundColor(Color backgroundColor, Highlighter h)
		{
			if (label != null) h.remove(label);
			label = h.addMessage(cell, msg, anchor, 0, backgroundColor);
		}

		public static SVState ensure(SearchVertex sv, Cell cell)
		{
			SVState svs = debugDialog.svInfo.get(sv);
			if (svs == null)
			{
				svs = new SVState(sv, cell);
				debugDialog.svInfo.put(sv, svs);
			}
			return svs;
		}
	}

	/************************************* REWIRE NETWORKS *************************************/

	private static void rewireNets()
	{
		EditWindow wnd = EditWindow.getCurrent();
		Cell cell = wnd.getCell();
		if (cell == null) return;

		// get list of selected nets
		List<ArcInst> selected = new ArrayList<ArcInst>();
		for(Iterator<ArcInst> it = cell.getArcs(); it.hasNext(); )
		{
			ArcInst ai = it.next();
			if (ai.getProto() == Generic.tech().unrouted_arc) selected.add(ai);
		}
		if (selected.isEmpty()) return;

		debuggingType = DebugType.REWIRENETS;
		SeaOfGatesHandlers.startInJob(cell, selected, SeaOfGatesEngineType.defaultVersion, Save.SAVE_PERIODIC);
	}

	/**
	 * Method to tell whether networks are being rewired for least-distance.
	 * @return true if networks are being rewired for least-distance.
	 */
	public static boolean isRewireNetworks() { return debugDialog != null && debuggingType == DebugType.REWIRENETS; }

	/************************************* ROUTING DEBUGGING DIALOG *************************************/

	/**
	 * Class to handle the "Routing control" dialog.
	 */
	private static class RoutingDialog extends EModelessDialog
	{
		private SeaOfGatesEngine router;
		private Map<SearchVertex,SVState> svInfo;
		private SearchVertex[] seeSV;
		private SearchVertex currentSV;
		private GlobalRouter globalRoutingResults;
		private JLabel routeDescriptionFrom, routeDescriptionTo;
		private JLabel routeResult;
		private JLabel labValue;
		private JLabel grInfo;
		private JTextField whichOne, whichLayer, whichStep;
		private JTextArea[] dirData;
		private JButton[] dirShow;
		private JLabel[] costShow;

		/** Creates new form Debug-Routing */
		public RoutingDialog()
		{
			super(TopLevel.getCurrentJFrame());
			seeSV = new SearchVertex[6];
			currentSV = null;

			// fill in the debug dialog
			getContentPane().setLayout(new GridBagLayout());
			setTitle("Debug Routing");
			setName("");
			addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent evt) { endDebugging(); } });
			GridBagConstraints gbc;
			int yPos = 0;

			JButton runA = new JButton("Run From A");
			runA.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { showRouting(true, false); } });
			gbc = new GridBagConstraints();
			gbc.gridx = 0;   gbc.gridy = yPos;
			gbc.weightx = 0.5;
			gbc.insets = new Insets(4, 4, 4, 4);
			getContentPane().add(runA, gbc);

			JButton run3DA = new JButton("Run 3D From A");
			run3DA.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { showRouting(true, true); } });
			gbc = new GridBagConstraints();
			gbc.gridx = 1;   gbc.gridy = yPos;
			gbc.weightx = 0.5;
			gbc.insets = new Insets(4, 4, 4, 4);
			getContentPane().add(run3DA, gbc);

			JLabel lab1 = new JLabel("Route to Debug:");
			gbc = new GridBagConstraints();
			gbc.gridx = 2;   gbc.gridy = yPos;
			gbc.insets = new Insets(4, 4, 4, 4);
			getContentPane().add(lab1, gbc);
			yPos++;


			JButton runB = new JButton("Run From B");
			runB.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { showRouting(false, false); } });
			gbc = new GridBagConstraints();
			gbc.gridx = 0;   gbc.gridy = yPos;
			gbc.weightx = 0.5;
			gbc.insets = new Insets(4, 4, 4, 4);
			getContentPane().add(runB, gbc);

			JButton run3DB = new JButton("Run 3D From B");
			run3DB.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { showRouting(false, true); } });
			gbc = new GridBagConstraints();
			gbc.gridx = 1;   gbc.gridy = yPos;
			gbc.weightx = 0.5;
			gbc.insets = new Insets(4, 4, 4, 4);
			getContentPane().add(run3DB, gbc);

			whichOne = new JTextField("");
			gbc = new GridBagConstraints();
			gbc.gridx = 2;   gbc.gridy = yPos;
			gbc.fill = GridBagConstraints.HORIZONTAL;
			gbc.insets = new Insets(4, 4, 4, 4);
			getContentPane().add(whichOne, gbc);
			yPos++;


			JButton showEndBlockage = new JButton("Show End Blockage");
			showEndBlockage.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { showEndBlockages(); } });
			gbc = new GridBagConstraints();
			gbc.gridx = 0;   gbc.gridy = yPos;
			gbc.weightx = 0.5;
			gbc.insets = new Insets(4, 4, 4, 4);
			getContentPane().add(showEndBlockage, gbc);

			JButton doGlobalRouting = new JButton("Show Global Routing");
			doGlobalRouting.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { doGlobalRouting(); } });
			gbc = new GridBagConstraints();
			gbc.gridx = 1;   gbc.gridy = yPos;
			gbc.insets = new Insets(4, 4, 4, 4);
			getContentPane().add(doGlobalRouting, gbc);

			JButton unrouteSeg = new JButton("Unroute Segment");
			unrouteSeg.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { Routing.unrouteCurrentSegment(); } });
			gbc = new GridBagConstraints();
			gbc.gridx = 2;   gbc.gridy = yPos;
			gbc.weightx = 0.5;
			gbc.insets = new Insets(4, 4, 4, 4);
			getContentPane().add(unrouteSeg, gbc);
			yPos++;


			JButton showAreaBlockage = new JButton("Show Area Blockage");
			showAreaBlockage.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { showAreaBlockages(); } });
			gbc = new GridBagConstraints();
			gbc.gridx = 0;   gbc.gridy = yPos;
			gbc.weightx = 0.5;
			gbc.insets = new Insets(4, 4, 4, 4);
			getContentPane().add(showAreaBlockage, gbc);

			JButton rewire = new JButton("Rewire for Routing");
			rewire.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { rewireNets(); } });
			gbc = new GridBagConstraints();
			gbc.gridx = 1;   gbc.gridy = yPos;
			gbc.weightx = 0.5;
			gbc.insets = new Insets(4, 4, 4, 4);
			getContentPane().add(rewire, gbc);

			JButton unrouteNet = new JButton("Unroute Network");
			unrouteNet.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { Routing.unrouteCurrent(); } });
			gbc = new GridBagConstraints();
			gbc.gridx = 2;   gbc.gridy = yPos;
			gbc.weightx = 0.5;
			gbc.insets = new Insets(4, 4, 4, 4);
			getContentPane().add(unrouteNet, gbc);
			yPos++;


			JButton showRoutingGrid = new JButton("Show Routing Grid");
			showRoutingGrid.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { showRoutingGrid(); } });
			gbc = new GridBagConstraints();
			gbc.gridx = 0;   gbc.gridy = yPos;
			gbc.weightx = 0.5;
			gbc.insets = new Insets(4, 4, 4, 4);
			getContentPane().add(showRoutingGrid, gbc);

			JLabel lab2 = new JLabel("Layer to grid:");
			gbc = new GridBagConstraints();
			gbc.gridx = 1;   gbc.gridy = yPos;
			gbc.insets = new Insets(4, 4, 4, 4);
			getContentPane().add(lab2, gbc);

			whichLayer = new JTextField("1");
			gbc = new GridBagConstraints();
			gbc.gridx = 2;   gbc.gridy = yPos;
			gbc.fill = GridBagConstraints.HORIZONTAL;
			gbc.insets = new Insets(4, 4, 4, 4);
			getContentPane().add(whichLayer, gbc);
			yPos++;


			routeDescriptionFrom = new JLabel("");
			gbc = new GridBagConstraints();
			gbc.gridx = 0;   gbc.gridy = yPos++;
			gbc.gridwidth = 3;
			gbc.fill = GridBagConstraints.HORIZONTAL;
			gbc.insets = new Insets(4, 4, 4, 4);
			gbc.weightx = 1;
			getContentPane().add(routeDescriptionFrom, gbc);

			routeDescriptionTo = new JLabel("");
			gbc = new GridBagConstraints();
			gbc.gridx = 0;   gbc.gridy = yPos++;
			gbc.gridwidth = 3;
			gbc.fill = GridBagConstraints.HORIZONTAL;
			gbc.insets = new Insets(4, 4, 4, 4);
			gbc.weightx = 1;
			getContentPane().add(routeDescriptionTo, gbc);

			routeResult = new JLabel("");
			gbc = new GridBagConstraints();
			gbc.gridx = 0;   gbc.gridy = yPos++;
			gbc.gridwidth = 3;
			gbc.fill = GridBagConstraints.HORIZONTAL;
			gbc.insets = new Insets(4, 4, 4, 4);
			gbc.weightx = 1;
			getContentPane().add(routeResult, gbc);

			JPanel panel = makeSVPanel();
			gbc = new GridBagConstraints();
			gbc.gridx = 0;   gbc.gridy = yPos++;
			gbc.gridwidth = 3;
			gbc.fill = GridBagConstraints.BOTH;
			gbc.insets = new Insets(4, 4, 4, 4);
			gbc.weightx = 1;  gbc.weighty = 0.5;
			getContentPane().add(panel, gbc);

			JPanel grPanel = new JPanel();
			grPanel.setLayout(new GridBagLayout());
			grPanel.setBorder(BorderFactory.createTitledBorder("Global Routing"));
			gbc = new GridBagConstraints();
			gbc.gridx = 0;   gbc.gridy = yPos++;
			gbc.gridwidth = 3;
			gbc.fill = GridBagConstraints.BOTH;
			gbc.insets = new Insets(4, 4, 4, 4);
			gbc.weightx = 1;
			getContentPane().add(grPanel, gbc);
			grInfo = new JLabel("");
			gbc = new GridBagConstraints();
			gbc.gridx = 0;   gbc.gridy = 0;
			gbc.fill = GridBagConstraints.BOTH;
			gbc.weightx = gbc.weighty = 1;
			gbc.insets = new Insets(4, 4, 4, 4);
			grPanel.add(grInfo, gbc);

			pack();
			finishInitialization();
			setVisible(true);
		}

		private JPanel makeSVPanel()
		{
			JPanel panel = new JPanel();
			panel.setLayout(new GridBagLayout());
			panel.setBorder(BorderFactory.createTitledBorder("Forward and Backward Propagation"));
			GridBagConstraints gbc;

			JButton backButton = new JButton("Back");
			backButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { showBack(); } });
			gbc = new GridBagConstraints();
			gbc.gridx = 0;   gbc.gridy = 0;
			gbc.fill = GridBagConstraints.HORIZONTAL;
			gbc.insets = new Insets(4, 4, 4, 4);
			panel.add(backButton, gbc);

			JButton nextButton = new JButton("Next");
			nextButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { showNext(); } });
			gbc = new GridBagConstraints();
			gbc.gridx = 1;   gbc.gridy = 0;
			gbc.fill = GridBagConstraints.HORIZONTAL;
			gbc.insets = new Insets(4, 4, 4, 4);
			panel.add(nextButton, gbc);

			whichStep = new JTextField("");
			whichStep.setColumns(5);
			gbc = new GridBagConstraints();
			gbc.gridx = 2;   gbc.gridy = 0;
			gbc.fill = GridBagConstraints.HORIZONTAL;
			gbc.anchor = GridBagConstraints.EAST;
			gbc.insets = new Insets(4, 4, 4, 0);
			panel.add(whichStep, gbc);

			JButton goButton = new JButton("Show Routing Step");
			goButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { showStepButton(); } });
			gbc = new GridBagConstraints();
			gbc.gridx = 3;   gbc.gridy = 0;
			gbc.anchor = GridBagConstraints.WEST;
			gbc.insets = new Insets(4, 0, 4, 4);
			panel.add(goButton, gbc);

			labValue = new JLabel("");
			gbc = new GridBagConstraints();
			gbc.gridx = 0;   gbc.gridy = 1;
			gbc.gridwidth = 4;
			gbc.weightx = 1;
			gbc.fill = GridBagConstraints.HORIZONTAL;
			gbc.anchor = GridBagConstraints.WEST;
			gbc.insets = new Insets(4, 4, 4, 4);
			panel.add(labValue, gbc);

			dirData = new JTextArea[6];
			dirShow = new JButton[6];
			costShow = new JLabel[6];
			for(int i=0; i<6; i++)
			{
				dirShow[i] = new JButton("See");
				gbc = new GridBagConstraints();
				gbc.gridx = 0;   gbc.gridy = i*2+2;
				gbc.gridheight = 2;
				gbc.anchor = GridBagConstraints.NORTH;
				gbc.fill = GridBagConstraints.HORIZONTAL;
				gbc.insets = new Insets(4, 4, 4, 4);
				panel.add(dirShow[i], gbc);

				String lab = "";
				switch (i)
				{
					case 0: lab = "-X";   break;
					case 1: lab = "+X";   break;
					case 2: lab = "-Y";   break;
					case 3: lab = "+Y";   break;
					case 4: lab = "Down";   break;
					case 5: lab = "Up";   break;
				}
				JLabel dirLabel = new JLabel(lab);
				gbc = new GridBagConstraints();
				gbc.gridx = 1;   gbc.gridy = i*2+2;
				gbc.weightx = 0.1;
				gbc.anchor = GridBagConstraints.SOUTH;
				gbc.fill = GridBagConstraints.HORIZONTAL;
				gbc.insets = new Insets(4, 4, 0, 4);
				panel.add(dirLabel, gbc);

				costShow[i] = new JLabel("");
				gbc = new GridBagConstraints();
				gbc.gridx = 1;   gbc.gridy = i*2+3;
				gbc.weightx = 0.1;
				gbc.anchor = GridBagConstraints.NORTH;
				gbc.insets = new Insets(0, 4, 4, 4);
				panel.add(costShow[i], gbc);

				dirData[i] = new JTextArea("");
				dirData[i].setEditable(false);
				dirData[i].setCursor(null);
				dirData[i].setOpaque(false);
				dirData[i].setFocusable(false);
				dirData[i].setLineWrap(true);
				dirData[i].setFont(UIManager.getFont("Label.font"));
				gbc = new GridBagConstraints();
				gbc.gridx = 2;   gbc.gridy = i*2+2;
				gbc.gridwidth = 2;
				gbc.gridheight = 2;
				gbc.weightx = 0.9;
				gbc.weighty = 0.2;
				gbc.anchor = GridBagConstraints.NORTHWEST;
				gbc.fill = GridBagConstraints.BOTH;
				gbc.insets = new Insets(4, 4, 4, 4);
				panel.add(dirData[i], gbc);
			}
			dirShow[0].addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { see(0); } });
			dirShow[1].addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { see(1); } });
			dirShow[2].addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { see(2); } });
			dirShow[3].addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { see(3); } });
			dirShow[4].addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { see(4); } });
			dirShow[5].addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { see(5); } });
			return panel;
		}

		private void setRouteDescription(String desc1, String desc2)
		{
			routeDescriptionFrom.setText(desc1);
			routeDescriptionTo.setText(desc2);
		}

		/**
		 * Method called when user clicks on one of the six "See" buttons to see a next direction.
		 * @param index the index (0-5) of the "See" button that was clicked.
		 */
		private void see(int index)
		{
			SearchVertex sv = seeSV[index];
			if (sv == null) return;
			seeSelectedSV(sv);
		}

		/**
		 * Method called when user clicks on the "Back" button to see the previous SearchVertex.
		 */
		private void showBack()
		{
			if (currentSV == null || currentSV.getLast() == null) return;
			seeSelectedSV(currentSV.getLast());
		}

		/**
		 * Method called when user clicks on the "Next" button to see the previous SearchVertex.
		 */
		private void showNext()
		{
			if (debugDialog == null || debugDialog.svInfo == null) return;

			// cycle through objects
			SearchVertex foundSV = null;
			if (currentSV ==  null) return;
			SVState svs = svInfo.get(currentSV);
			String stepNum;
			if (svs.msg.equals("START")) stepNum = "1"; else
				stepNum = (TextUtils.atoi(svs.msg) + 1) + "";
			for(SearchVertex sv : debugDialog.svInfo.keySet())
			{
				svs = svInfo.get(sv);
				if (svs.msg.equals(stepNum)) { foundSV = sv;   break; }
			}
			if (foundSV != null)
			{
				seeSelectedSV(foundSV);
			} else
			{
				System.out.println("No Routing Step numbered " + stepNum);
			}
		}

		/**
		 * Method called when user clicks the "Show Routing Step" button to see a specified point in the route plan.
		 */
		private void showStepButton()
		{
			if (debugDialog == null || debugDialog.svInfo == null) return;

			// find the specified routing step
			String stepNum = whichStep.getText().trim();
			SearchVertex foundSV = null;
			for(SearchVertex sv : debugDialog.svInfo.keySet())
			{
				SVState svs = svInfo.get(sv);
				if (svs.msg.equals(stepNum)) { foundSV = sv;   break; }
			}
			if (foundSV != null)
			{
				seeSelectedSV(foundSV);
			} else
			{
				System.out.println("No Routing Step numbered " + stepNum);
			}
		}

		/**
		 * Method to load information about a SearchVertex into the dialog.
		 * @param sv the SearchVertex that is being explained.
		 */
		private void seeSelectedSV(SearchVertex sv)
		{
			if (debugDialog == null) return;
			EditWindow wnd = EditWindow.getCurrent();
			Highlighter h = wnd.getRulerHighlighter();
			if (currentSV != null)
			{
				SVState oldSVS = svInfo.get(currentSV);
				if (oldSVS != null) oldSVS.setBackgroundColor(null, h);
			}
			currentSV = sv;
			SVState svs = svInfo.get(sv);
			svs.setBackgroundColor(Color.WHITE, h);
			
			for(int i=0; i<6; i++)
			{
				dirData[i].setText("");
				dirShow[i].setText("See");
				dirShow[i].setEnabled(false);
				costShow[i].setText("");
			}
			Highlight.Message hMsg = (Highlight.Message)svs.label;
			if (svs.details == null)
			{
				String msg = "At (" + TextUtils.formatDouble(sv.getX()) + "," + TextUtils.formatDouble(sv.getY()) + ",M" + (sv.getZ()+1)+
					"), Cost=" + sv.getCost();
				if (sv.getLast() != null)
				{
					SVState svsLast = svInfo.get(sv.getLast());
					Highlight.Message hMsgLast = (Highlight.Message)svsLast.label;
					if (sv.getGRBucket() < 0) msg += ", NO Global Routing"; else
						msg += ", Global Routing Bucket: " + sv.getGRBucket();
					msg += ", previous point " + hMsgLast.getInfo() + " at (" + TextUtils.formatDouble(sv.getLast().getX()) + "," +
						TextUtils.formatDouble(sv.getLast().getY()) + ",M" + (sv.getLast().getZ()+1) + ")";
				}
				labValue.setText(hMsg.getInfo() + ": " + msg + ", DID NOT GET PROPAGATED");
			} else
			{
				String lab = hMsg.getInfo() + ": " + svs.details[0];
				if (sv.getLast() != null)
				{
					SVState svsLast = svInfo.get(sv.getLast());
					Highlight.Message hMsgLast = (Highlight.Message)svsLast.label;
					lab += ", previous point " + hMsgLast.getInfo() + " at (" + TextUtils.formatDouble(sv.getLast().getX()) + "," +
						TextUtils.formatDouble(sv.getLast().getY()) + ",M" + (sv.getLast().getZ()+1) + ")";
				}
				labValue.setText(lab);
				for(int i=0; i<6; i++)
				{
					if (svs.details[i+1] == null) continue;
					if (svs.details[i+1].indexOf('|') >= 0)
					{
						String leading = "> ";
						String [] subParts = svs.details[i+1].split("\\|");
						String msg = subParts[0];
						for(int j=1; j<subParts.length; j++)
							msg += "\n" + (leading + subParts[j]);
						dirData[i].setText(msg);
					} else
						dirData[i].setText(svs.details[i+1]);

					seeSV[i] = svs.nextVertices[i];
					if (seeSV[i] != null)
					{
						SVState svsNext = svInfo.get(seeSV[i]);
						if (svsNext == null) dirShow[i].setText("?"); else
						{
							Highlight.Message hMsgNext = (Highlight.Message)svsNext.label;
							dirShow[i].setText(hMsgNext.getInfo());
							dirShow[i].setEnabled(true);
						}
						costShow[i].setText("Cost: " + seeSV[i].getCost());
					}
				}
			}
			if (sv.getWavefront().getGRDirection() == 0)
			{
				grInfo.setText("No Global Routing data");
			} else
			{
				Wavefront wf = sv.getWavefront();
				NeededRoute nr = wf.getNeededRoute();
				Rectangle2D[] buckets = nr.getGRBuckets();
				Rectangle2D[] orderedBuckets = wf.getOrderedBuckets();
				String msg = "<html>";
				for(int b=0; b<orderedBuckets.length; b++)
					msg += "Bucket "+b+" is "+TextUtils.formatDouble(buckets[b].getMinX())+"&lt;=X&lt;="+TextUtils.formatDouble(buckets[b].getMaxX())+" and "+
						TextUtils.formatDouble(buckets[b].getMinY())+"&lt;=Y&lt;="+TextUtils.formatDouble(buckets[b].getMaxY()) + "<p>";
				for(int b=0; b<orderedBuckets.length; b++)
					msg += "Ordered Bucket "+b+" is "+TextUtils.formatDouble(orderedBuckets[b].getMinX())+"&lt;=X&lt;="+TextUtils.formatDouble(orderedBuckets[b].getMaxX())+" and "+
						TextUtils.formatDouble(orderedBuckets[b].getMinY())+"&lt;=Y&lt;="+TextUtils.formatDouble(orderedBuckets[b].getMaxY()) + "<p>";
				msg += "</html>";
				grInfo.setText(msg);
			}

			wnd.fullRepaint();
			pack();
		}

		protected void escapePressed() { endDebugging(); }

		private void showGlobalRoutingGrid()
		{
			// show grid lines around buckets
			EditWindow wnd = EditWindow.getCurrent();
			Cell cell = wnd.getCell();
			Highlighter h = wnd.getRulerHighlighter();
			ERectangle bounds = cell.getBounds();
			double bucketWidth = bounds.getWidth() / globalRoutingResults.getXBuckets();
			double bucketHeight = bounds.getHeight() / globalRoutingResults.getYBuckets();
			for(int x=0; x<=globalRoutingResults.getXBuckets(); x++)
			{
				double xPos = bounds.getMinX() + x * bucketWidth;
				h.addLine(EPoint.fromLambda(xPos, bounds.getMinY()), EPoint.fromLambda(xPos, bounds.getMaxY()), cell, false, Color.RED, false);
			}
			for(int y=0; y<=globalRoutingResults.getYBuckets(); y++)
			{
				double yPos = bounds.getMinY() + y * bucketHeight;
				h.addLine(EPoint.fromLambda(bounds.getMinX(), yPos), EPoint.fromLambda(bounds.getMaxX(), yPos), cell, false, Color.RED, false);
			}
		}

		private void showGlobalRoutingPath(NeededRoute nr)
		{
			// show global routes
			EditWindow wnd = EditWindow.getCurrent();
			Cell cell = wnd.getCell();
			Highlighter h = wnd.getRulerHighlighter();
			Set<Integer> xUsed = new HashSet<Integer>();
			Set<Integer> yUsed = new HashSet<Integer>();
			for (GRNet net : debugDialog.globalRoutingResults.getNets())
			{
				for (GRWire w : net.getWires())
				{
					if (nr != null && w.getNeededRoute() != nr) continue;
					EPoint p1 = w.getPoint1();
					EPoint p2 = w.getPoint2();
					GRBucket n1 = w.getBucket1();
					GRBucket n2 = w.getBucket2();

					GRBucket prev = null;
					double prevX = 0, prevY = 0;
					for (int i=0; i<w.getNumPathElements(); i++)
					{
						GRBucket n = w.getPathBucket(i);
						Rectangle2D bucketBound = n.getBounds();
						double x = bucketBound.getCenterX(), y = bucketBound.getCenterY();
						boolean adjusted = false;
						if (n == n1) { x = p1.getX();   y = p1.getY();  adjusted = true; }
						if (n == n2) { x = p2.getX();   y = p2.getY();  adjusted = true; }
						if (!adjusted)
						{
							if (i > 0)
							{
								if (w.getPathBucket(i-1).getBounds().getCenterX() == x)
								{
									if (w.getPathBucket(i-1) == n1) x = p1.getX();
									if (w.getPathBucket(i-1) == n2) x = p2.getX();	
								}
								if (w.getPathBucket(i-1).getBounds().getCenterY() == y)
								{
									if (w.getPathBucket(i-1) == n1) y = p1.getY();
									if (w.getPathBucket(i-1) == n2) y = p2.getY();	
								}
							}
							if (i < w.getNumPathElements()-1)
							{
								if (w.getPathBucket(i+1).getBounds().getCenterX() == x)
								{
									if (w.getPathBucket(i+1) == n1) x = p1.getX();
									if (w.getPathBucket(i+1) == n2) x = p2.getX();	
								}
								if (w.getPathBucket(i+1).getBounds().getCenterY() == y)
								{
									if (w.getPathBucket(i+1) == n1) y = p1.getY();
									if (w.getPathBucket(i+1) == n2) y = p2.getY();	
								}
							}
							for(;;)
							{
								Integer xi = Integer.valueOf((int)x);
								if (xUsed.contains(xi)) { x++; continue; }
								xUsed.add(xi);
								break;
							}
							for(;;)
							{
								Integer yi = Integer.valueOf((int)y);
								if (yUsed.contains(yi)) { y++; continue; }
								yUsed.add(yi);
								break;
							}
						}
						if (prev != null)
						{
							h.addLine(EPoint.fromLambda(prevX, prevY), EPoint.fromLambda(x, y), cell, false, Color.GREEN, false);
						}
						if (i == 0 || i == w.getNumPathElements()-1)
						{
							int xSize = 2;
							h.addLine(EPoint.fromLambda(x-xSize, y-xSize), EPoint.fromLambda(x+xSize, y+xSize), cell, false, Color.GREEN, false);
							h.addLine(EPoint.fromLambda(x-xSize, y+xSize), EPoint.fromLambda(x+xSize, y-xSize), cell, false, Color.GREEN, false);
						}
						prev = n;
						prevX = x;   prevY = y;
					}
				}
			}
			h.finished();
			wnd.repaint();
		}

		/**
		 * Method to show the resulting path (which may be a failure).
		 * @param result the SearchVertex at the end (may indicate failure).
		 * @param cell the Cell in which routing happened.
		 * @param h the Highlighter for showing the result.
		 */
		private void showPathToGoal(SearchVertex result, Cell cell, Highlighter h)
		{
			if (result == SeaOfGatesEngine.svAborted)
			{
				routeResult.setText("Result: Aborted by user");
			} else if (result == SeaOfGatesEngine.svExhausted)
			{
				routeResult.setText("Result: Examined all possibilities");
			} else if (result == SeaOfGatesEngine.svLimited)
			{
				routeResult.setText("Result: Stopped after " + router.getPrefs().complexityLimit + " steps");
			} else
			{
				routeResult.setText("Result: Success!");
				SVState svs = SVState.ensure(result, cell);
				svs.changeLabel("!!GOAL!!", h);
				for(;;)
				{
					SearchVertex svLast = result.getLast();
					if (svLast == null) break;
					if (result.getZ() != svLast.getZ())
					{
						int lowZ = Math.min(result.getZ(), svLast.getZ());
						int highZ = Math.max(result.getZ(), svLast.getZ());
						double lowOff = lowZ * layerOffset;
						double highOff = highZ * layerOffset;
						h.addLine(EPoint.fromLambda(result.getX()+lowOff, result.getY()+lowOff+goalWidth),
								EPoint.fromLambda(result.getX()+highOff-goalWidth, result.getY()+highOff), cell, true, Color.WHITE, false);
						h.addLine(EPoint.fromLambda(result.getX()+lowOff+goalWidth, result.getY()+lowOff),
								EPoint.fromLambda(result.getX()+highOff, result.getY()+highOff-goalWidth), cell, true, Color.WHITE, false);
					} else
					{
						double off = result.getZ() * layerOffset;
						if (result.getX() != svLast.getX())
						{
							// horizontal line
							h.addLine(EPoint.fromLambda(result.getX()+off, result.getY()+off-goalWidth),
									EPoint.fromLambda(svLast.getX()+off, svLast.getY()+off-goalWidth), cell, true, Color.WHITE, false);
							h.addLine(EPoint.fromLambda(result.getX()+off, result.getY()+off+goalWidth),
									EPoint.fromLambda(svLast.getX()+off, svLast.getY()+off+goalWidth), cell, true, Color.WHITE, false);
						} else
						{
							// vertical line
							h.addLine(EPoint.fromLambda(result.getX()+off-goalWidth, result.getY()+off),
									EPoint.fromLambda(svLast.getX()+off-goalWidth, svLast.getY()+off), cell, true, Color.WHITE, false);
							h.addLine(EPoint.fromLambda(result.getX()+off+goalWidth, result.getY()+off),
									EPoint.fromLambda(svLast.getX()+off+goalWidth, svLast.getY()+off), cell, true, Color.WHITE, false);
						}
					}
					result = svLast;
				}
			}
		}

		private void showSearchVertices(Cell cell, Highlighter h, Wavefront wf)
		{
			PortInst piF = wf.getFromPortInst();
			PortInst piT = wf.getToPortInst();
			String fromMsg = "A: (" + TextUtils.formatDouble(wf.getFromX()) + "," + TextUtils.formatDouble(wf.getFromY()) +
				", M" + (wf.getFromZ() + 1) + "): port " + piF.getPortProto().getName() + " of node " + piF.getNodeInst().describe(false);
			String toMsg = "B: (" + TextUtils.formatDouble(wf.getToX()) + "," + TextUtils.formatDouble(wf.getToY()) +
				", M" + (wf.getToZ() + 1) + "): port " + piT.getPortProto().getName() + " of node " + piT.getNodeInst().describe(false);
			setRouteDescription(fromMsg, toMsg);

			// draw the search vertices
			Map<String,Integer> lowestZ = new HashMap<String,Integer>();
			Map<Integer, Map<Integer,SearchVertex>>[] searchVertexPlanes = wf.getSearchVertexPlanes();
			for(int z=0; z<router.getNumMetals(); z++)
			{
				Map<Integer, Map<Integer,SearchVertex>> plane = searchVertexPlanes[z];
				if (plane == null) continue;
				for(Integer y : plane.keySet())
				{
					Map<Integer,SearchVertex> row = plane.get(y);
					for(Integer x : row.keySet())
					{
						SearchVertex sv = row.get(x);
						SVState svs = SVState.ensure(sv, cell);
						svs.showLabel(h);

						if (sv.getLast() == null) continue;

						if (sv.getZ() != sv.getLast().getZ())
						{
							// draw white line at angle showing change of layer
							int lowZ = Math.min(sv.getZ(), sv.getLast().getZ());
							int highZ = Math.max(sv.getZ(), sv.getLast().getZ());
							double lowOff = lowZ * layerOffset;
							double highOff = highZ * layerOffset;
							h.addLine(EPoint.fromLambda(sv.getX()+lowOff, sv.getY()+lowOff),
									EPoint.fromLambda(sv.getX()+highOff, sv.getY()+highOff), cell, true, Color.WHITE, false);
						} else
						{
							// draw line in proper metal color showing the motion
							double off = sv.getZ() * layerOffset;
							Color col = router.getMetalLayer(sv.getZ()).getGraphics().getColor();
							h.addLine(EPoint.fromLambda(sv.getX()+off, sv.getY()+off),
									EPoint.fromLambda(sv.getLast().getX()+off, sv.getLast().getY()+off), cell, false, col, false);
						}

						// remember lowest Z coordinate at this place so that anchor line can be drawn if it is above Metal-1
						String coordLoc = TextUtils.formatDouble(sv.getX()) + "/" + TextUtils.formatDouble(sv.getY());
						Integer height = lowestZ.get(coordLoc);
						int lowZ = Math.min(sv.getZ(), sv.getLast().getZ());
						if (height == null) height = Integer.valueOf(lowZ); else
						{
							int lowest = Math.min(height.intValue(), lowZ);
							height = Integer.valueOf(lowest);
						}
						lowestZ.put(coordLoc, height);
					}
				}
			}

			// draw anchor lines from the lowest Z coordinates if they are not at Metal-1
			for(String loc : lowestZ.keySet())
			{
				Integer height = lowestZ.get(loc);
				if (height.intValue() <= 0) continue;
				String [] locCoords = loc.split("/");
				double x = TextUtils.atof(locCoords[0]);
				double y = TextUtils.atof(locCoords[1]);
				double off = height.intValue() * layerOffset;
				h.addLine(EPoint.fromLambda(x, y), EPoint.fromLambda(x+off, y+off), cell, false, Color.BLACK, false);
			}
		}
	}

	/************************************* DEBUGGING SUPPORT *************************************/

	private static String getNetName(Integer netIDI)
	{
		return "Net " + netIDI.intValue();
	}

	private static void showBounds(Cell cell, Rectangle2D bounds, Highlighter h, Color col)
	{
		Point2D p1 = new Point2D.Double(bounds.getMinX(), bounds.getMinY());
		Point2D p2 = new Point2D.Double(bounds.getMinX(), bounds.getMaxY());
		Point2D p3 = new Point2D.Double(bounds.getMaxX(), bounds.getMaxY());
		Point2D p4 = new Point2D.Double(bounds.getMaxX(), bounds.getMinY());
		h.addLine(p1, p2, cell, true, col, false);
		h.addLine(p2, p3, cell, true, col, false);
		h.addLine(p3, p4, cell, true, col, false);
		h.addLine(p4, p1, cell, true, col, false);
	}

	private static void showStripedRect(Rectangle2D bound, Color color, int angle)
	{
		EditWindow wnd = EditWindow.getCurrent();
		Cell cell = wnd.getCell();
		Highlighter h = wnd.getRulerHighlighter();
		h.addArea(bound, color, cell);
		double gapX = 0.5, gapY = 0.5;
		double lX = bound.getMinX(), hX = bound.getMaxX();
		double lY = bound.getMinY(), hY = bound.getMaxY();
		if (angle == 0)
		{
			// horizontal lines
			for(double y = gridValue(lY, gapY); y < hY; y += gapY)
				h.addLine(new Point2D.Double(lX, y), new Point2D.Double(hX, y), cell, false, color, false);
			return;
		}
		if (angle == 90)
		{
			// vertical lines
			for(double x = gridValue(lX, gapX); x < hX; x += gapX)
				h.addLine(new Point2D.Double(x, lY), new Point2D.Double(x, hY), cell, false, color, false);
			return;
		}

		// prepare for angled lines
		double s = DBMath.sin(angle*10);
		double c = DBMath.cos(angle*10);
		gapX = gapX / Math.abs(s);
		gapY = gapY / Math.abs(c);
		if (angle <= 45 || angle >= 135)
		{
			// lines that are closer to horizontal
			double pos, length;
			if (angle < 90)
			{
				pos = gridValue(lX-gapX, gapX);
				length = (hX - pos) / c;
			} else
			{
				pos = gridValue(hX+gapX, gapX);
				length = (lX - pos) / c;
			}
			for(double y = gridValue(lY - bound.getWidth() - gapY, gapY); y < hY + bound.getWidth(); y += gapY)
			{
				Point2D pt1 = new Point2D.Double(pos, y);
				Point2D pt2 = new Point2D.Double(pos + c * length, y + s * length);
				boolean gone = DBMath.clipLine(pt1, pt2, lX, hX, lY, hY);
				if (gone) continue;
				h.addLine(pt1, pt2, cell, false, color, false);
			}
		} else
		{
			// lines that are closer to vertical
			double pos = gridValue(lY - gapY, gapY);
			double length = (hY - pos) / s;
			for(double x = gridValue(lX - bound.getHeight() - gapX, gapX); x < hX + bound.getHeight(); x += gapX)
			{
				Point2D pt1 = new Point2D.Double(x, pos);
				Point2D pt2 = new Point2D.Double(x + c * length, pos + s * length);
				boolean gone = DBMath.clipLine(pt1, pt2, lX, hX, lY, hY);
				if (gone) continue;
				h.addLine(pt1, pt2, cell, false, color, false);
			}
		}
	}

	private static double gridValue(double v, double gap)
	{
		int iv = (int)Math.round(v / gap);
		return iv * gap;
	}

	private static ERectangle showGeometryPiece(SOGBound sBound, Rectangle2D limit, Layer lay)
	{
		Integer netIDI;
		MutableInteger mi = sBound.getNetID();
		if (mi == null) netIDI = Integer.valueOf(0); else
			netIDI = Integer.valueOf(mi.intValue());
		Color color;
		Integer angle;
		if (netIDI.intValue() == 0)
		{
			color = lay.getGraphics().getColor();
			angle = Integer.valueOf(90);
		} else
		{
			color = Color.BLACK;
color = lay.getGraphics().getColor();
			if ((netIDI.intValue()&1) != 0) color = Color.GRAY;
			angle = netAngles.get(netIDI);
			if (angle == null)
			{
				angleAssigned = (angleAssigned + 41) % 180;
				netAngles.put(netIDI, angle = Integer.valueOf(angleAssigned));
			}
		}
		ERectangle draw = sBound.getBounds();
		double lX = draw.getMinX();
		double hX = draw.getMaxX();
		double lY = draw.getMinY();
		double hY = draw.getMaxY();
		if (lX < limit.getMinX()) { lX = limit.getMinX();   draw = null; }
		if (hX > limit.getMaxX()) { hX = limit.getMaxX();   draw = null; }
		if (lY < limit.getMinY()) { lY = limit.getMinY();   draw = null; }
		if (hY > limit.getMaxY()) { hY = limit.getMaxY();   draw = null; }
		if (draw == null) draw = ERectangle.fromLambda(lX, lY, hX-lX, hY-lY);
		
		showStripedRect(draw, color, angle.intValue());
		return draw;
	}

	/************************************** 3D DEBUGGING **************************************/

	/**
	 * Class to render a route in 3D.
	 */
	private static class Show3DRoute implements Runnable
	{
		private Wavefront wf;
		private NeededRoute nr;
		private SeaOfGatesEngine router;
		private Cell cell;

		/**
		 * Constructor to show a failed route in 3D.
		 * @param metalLayers the actual metal Layers.
		 * @param cell the Cell being rendered.
		 */
		Show3DRoute(Wavefront wf, NeededRoute nr, SeaOfGatesEngine router, Cell cell)
		{
			this.wf = wf;
			this.nr = nr;
			this.router = router;
			this.cell = cell;
		}

		public void run()
		{
			// see if 3D is available
			Method showPolysMethod = null;
			try
			{
				Class<?> threeDClass = Class.forName("com.sun.electric.plugins.j3d.View3DWindow");
				showPolysMethod = threeDClass.getMethod("show3DPolygons", new Class[] {ArrayList.class});
			} catch (Exception e)
			{
				System.out.println("Problem with 3D view: "+e.getMessage());
			}
//showPolysMethod = null;
			if (showPolysMethod == null)
			{
				EditWindow_ wnd = Job.getUserInterface().getCurrentEditWindow_();
				Cell drawCell = wnd.getCell();
				wnd.clearHighlighting();
				for(SearchVertex sv : wf.getActive())
					showSV(sv, wnd, drawCell);
				for(SearchVertex sv : wf.getInactive())
					showSV(sv, wnd, drawCell);
				wnd.finishedHighlighting();
				return;
			}

			List<PolyBase> polys = new ArrayList<PolyBase>();

			// show the search vertices
			for(SearchVertex sv : wf.getActive())
				displaySearchVertex(sv, polys);
			for(SearchVertex sv : wf.getInactive())
				displaySearchVertex(sv, polys);

//			// aggregate the search points in areas
//			PolyMerge pm = new PolyMerge();
//			for (int i = 0; i < planes.length; i++)
//			{
//				int numDots = 0;
//				Map<Double, Set<Double>> plane = planes[i];
//				if (plane == null) continue;
//				for (Double y : plane.keySet())
//				{
//					double yv = y.doubleValue();
//					Set<Double> row = plane.get(y);
//					for (Double x : row)
//					{
//						double xv = x.doubleValue();
//						Point2D[] showPT = new Point2D[4];
//						showPT[0] = new Point2D.Double(xv - 1, yv - 1);
//						showPT[1] = new Point2D.Double(xv - 1, yv + 1);
//						showPT[2] = new Point2D.Double(xv + 1, yv + 1);
//						showPT[3] = new Point2D.Double(xv + 1, yv - 1);
//						Poly poly = new Poly(showPT);
//						poly.setLayer(layers[i]);
//						poly.setStyle(Type.FILLED);
//						pm.addPolygon(poly.getLayer(), poly);
//						numDots++;
//						if (i > 0 && numDots > 100) break;
//					}
//					if (i > 0 && numDots > 100) break;
//				}
//			}
//			for (Layer layer : pm.getKeySet())
//			{
//				double thickness = layer.getThickness();
//				double lowZ = layer.getDistance();
//				double highZ = lowZ + thickness;
//				double inset = thickness/3;
//				lowZ += inset;   highZ -= inset;
//				List<PolyBase> merges = pm.getMergedPoints(layer, true);
//				for(PolyBase merged : merges)
//				{
//					Poly3D p3d = new Poly3D(merged.getPoints(), lowZ, highZ);
//					p3d.setLayer(layer);
//					p3d.setStyle(Type.FILLED);
//					polys.add(p3d);
//				}
//			}

			// show all blockages in the area
			int highestUsedLayer = 0;
			for(int lay = 0; lay < router.getNumMetals(); lay++)
			{
				Layer layer = router.getMetalLayer(lay);
				for (Iterator<SOGBound> sea = debugDialog.router.searchMetalTree(layer, nr.getBounds()); sea.hasNext(); )
				{
					SOGBound sBound = sea.next();
					double lX = sBound.getBounds().getMinX();
					double hX = sBound.getBounds().getMaxX();
					double lY = sBound.getBounds().getMinY();
					double hY = sBound.getBounds().getMaxY();
					if (hX <= nr.getBounds().getMinX() || lX >= nr.getBounds().getMaxX() ||
						hY <= nr.getBounds().getMinY() || lY >= nr.getBounds().getMaxY()) continue;
					if (lX < nr.getBounds().getMinX()) lX = nr.getBounds().getMinX();
					if (hX > nr.getBounds().getMaxX()) hX = nr.getBounds().getMaxX();
					if (lY < nr.getBounds().getMinY()) lY = nr.getBounds().getMinY();
					if (hY > nr.getBounds().getMaxY()) hY = nr.getBounds().getMaxY();
					double lowZ = layer.getDistance();
					Poly3D poly = new Poly3D((lX+hX)/2, (lY+hY)/2, hX-lX, hY-lY, lowZ, lowZ);
					poly.setStyle(Type.FILLED);
					EGraphics graphics = layer.getGraphics();
					poly.setColor(graphics.getColor());
					poly.setTransparency(0.1f);
					polys.add(poly);
					if (lay > highestUsedLayer) highestUsedLayer = lay;
				}
			}

			// show vias
			for(int i=0; i<highestUsedLayer; i++)
			{
				Layer layer = router.getMetalLayer(i);
				Layer m1Layer = router.getMetalLayer(i);
				Layer m2Layer = router.getMetalLayer(i+1);
				for (Iterator<SOGBound> sea = nr.searchViaTree(layer, nr.getBounds()); sea.hasNext(); )
				{
					SOGVia sBound = (SOGVia)sea.next();
					double cX = sBound.getBounds().getCenterX();
					double cY = sBound.getBounds().getCenterY();
					if (cX <= nr.getBounds().getMinX() || cX >= nr.getBounds().getMaxX() ||
						cY <= nr.getBounds().getMinY() || cY >= nr.getBounds().getMaxY()) continue;
					double lowZ = m1Layer.getDistance();
					double highZ = m2Layer.getDistance();
					Poly3D poly = new Poly3D(cX, cY, 1, 1, lowZ, highZ);
					poly.setStyle(Type.FILLED);
					poly.setColor(Color.BLACK);
					poly.setTransparency(0.1f);
					polys.add(poly);
				}
			}

			// draw the line from start to end of routing path
			showRoutingPath(polys, highestUsedLayer);

			// render it
			try
			{
				showPolysMethod.invoke(null, new Object[] {polys});
			} catch (Exception e)
			{
				System.out.println("3D rendering error: "+e.getMessage());
			}
		}

		private void showSV(SearchVertex sv, EditWindow_ wnd, Cell cellToUse)
		{
			String message = "M" + (sv.getZ()+1) + "=" + sv.getCost();
			Point2D loc = new Point2D.Double(sv.getX(), sv.getY());
			wnd.addHighlightMessage(cellToUse, message, loc);
		}

		private void showRoutingPath(List<PolyBase> polys, int highestUsedLayer)
		{
			double overallDepth = 20;
			Technology tech = cell.getTechnology();

			// include a line indicating the desired route
			Poly3D.Point fromPT = Poly3D.fromLambda(wf.getFromX(), wf.getFromY());
			Poly3D.Point toPT = Poly3D.fromLambda(wf.getToX(), wf.getToY());
			Poly3D.Point[] twoPT = new Poly3D.Point[2];
			twoPT[0] = fromPT;   twoPT[1] = toPT;
			double lowZ =router.getMetalLayer(0).getDistance() - overallDepth;
			double highZ = router.getMetalLayer(highestUsedLayer).getDistance() + router.getMetalLayer(highestUsedLayer).getThickness() + overallDepth;
			Poly3D routePoly = new Poly3D(twoPT, lowZ, lowZ);
			routePoly.setLayer(tech.findLayer("Metal-1"));
			routePoly.setStyle(Type.OPENED);
			routePoly.setColor(Color.WHITE);
			polys.add(routePoly);

			// draw an arrowhead showing the routing direction
			double cX = (wf.getFromX() + wf.getToX()) / 2, cY = (wf.getFromY() + wf.getToY()) / 2;
			Poly3D.Point ctr = Poly3D.fromLambda(cX, cY);
			int angle = DBMath.figureAngle(toPT, fromPT);
			int arrowAngle = (angle + 300) % 3600;
			double arrowHeadLength = GenMath.distBetweenPoints(fromPT, toPT) / 10;
			double endX = DBMath.cos(arrowAngle) * arrowHeadLength + cX;
			double endY = DBMath.sin(arrowAngle) * arrowHeadLength + cY;
			twoPT = new Poly3D.Point[2];
			twoPT[0] = ctr;   twoPT[1] = Poly3D.fromLambda(endX, endY);
			routePoly = new Poly3D(twoPT, lowZ, lowZ);
			routePoly.setLayer(tech.findLayer("Metal-1"));
			routePoly.setStyle(Type.OPENED);
			routePoly.setColor(Color.WHITE);
			polys.add(routePoly);
			arrowAngle = (angle + 3300) % 3600;
			endX = DBMath.cos(arrowAngle) * arrowHeadLength + cX;
			endY = DBMath.sin(arrowAngle) * arrowHeadLength + cY;
			twoPT = new Poly3D.Point[2];
			twoPT[0] = ctr;   twoPT[1] = Poly3D.fromLambda(endX, endY);
			routePoly = new Poly3D(twoPT, lowZ, lowZ);
			routePoly.setLayer(tech.findLayer("Metal-1"));
			routePoly.setStyle(Type.OPENED);
			routePoly.setColor(Color.WHITE);
			polys.add(routePoly);

			// draw vertical posts at the ends
			Poly3D.Point[] onePT = new Poly3D.Point[1];
			onePT[0] = fromPT;
			routePoly = new Poly3D(onePT, lowZ, highZ);
			routePoly.setLayer(tech.findLayer("Metal-1"));
			routePoly.setStyle(Type.OPENED);
			routePoly.setColor(Color.WHITE);
			polys.add(routePoly);
			onePT = new Poly3D.Point[1];
			onePT[0] = toPT;
			routePoly = new Poly3D(onePT, lowZ, highZ);
			routePoly.setLayer(tech.findLayer("Metal-1"));
			routePoly.setStyle(Type.OPENED);
			routePoly.setColor(Color.WHITE);
			polys.add(routePoly);
		}

		private void displaySearchVertex(SearchVertex sv, List<PolyBase> polys)
		{
			if (sv.getLast() == null) return;
			Technology tech = cell.getTechnology();
			double pathSize = 0.75;
			if (sv.getZ() == sv.getLast().getZ())
			{
				double z = router.getMetalLayer(sv.getZ()).getDistance() - 5;
				Poly3D.Point[] pts = new Poly3D.Point[4];
				double lX = Math.min(sv.getX(), sv.getLast().getX());
				double hX = Math.max(sv.getX(), sv.getLast().getX());
				double lY = Math.min(sv.getY(), sv.getLast().getY());
				double hY = Math.max(sv.getY(), sv.getLast().getY());
				pts[0] = Poly3D.fromLambda(lX-pathSize, lY-pathSize);
				pts[1] = Poly3D.fromLambda(lX-pathSize, hY+pathSize);
				pts[2] = Poly3D.fromLambda(hX+pathSize, hY+pathSize);
				pts[3] = Poly3D.fromLambda(hX+pathSize, lY-pathSize);
				Poly3D routePoly = new Poly3D(pts, z, z);
				routePoly.setLayer(tech.findLayer("Metal-1"));
				routePoly.setColor(Color.RED);
				routePoly.setStyle(Type.CLOSED);
				polys.add(routePoly);
				return;
			} else
			{
				double fromZ = router.getMetalLayer(sv.getZ()).getDistance() - 5;
				double toZ = router.getMetalLayer(sv.getLast().getZ()).getDistance() - 5;
				Poly3D.Point[] pts = new Poly3D.Point[4];
				pts[0] = Poly3D.fromLambda(sv.getX()-pathSize, sv.getY()-pathSize);
				pts[1] = Poly3D.fromLambda(sv.getX()-pathSize, sv.getY()+pathSize);
				pts[2] = Poly3D.fromLambda(sv.getX()+pathSize, sv.getY()+pathSize);
				pts[3] = Poly3D.fromLambda(sv.getX()+pathSize, sv.getY()-pathSize);
				Poly3D routePoly = new Poly3D(pts, fromZ, toZ);
				routePoly.setLayer(tech.findLayer("Metal-1"));
				routePoly.setColor(Color.GREEN);
				routePoly.setStyle(Type.CLOSED);
				polys.add(routePoly);

				Poly3D.Point[] pts1 = new Poly3D.Point[1];
				pts1[0] = Poly3D.fromLambda(sv.getX(), sv.getY());
				routePoly = new Poly3D(pts1, fromZ, fromZ);
				routePoly.setLayer(tech.findLayer("Metal-1"));
				routePoly.setColor(Color.BLACK);
				routePoly.setStyle(Type.TEXTCENT);
				routePoly.setText(sv.getCost()+"");
				polys.add(routePoly);
				return;
			}
		}
	}
}
