/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: SeaOfGates.java
 * Routing tool: Sea of Gates control
 * Written by: Steven M. Rubin
 *
 * Copyright (c) 2007, 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.routing;

import com.sun.electric.database.EditingPreferences;
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.Geometric;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.database.variable.EditWindow_;
import com.sun.electric.database.variable.UserInterface;
import com.sun.electric.database.variable.Variable;
import com.sun.electric.technology.ArcProto;
import com.sun.electric.technology.Technology;
import com.sun.electric.technology.technologies.Generic;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.routing.seaOfGates.SeaOfGatesEngine;
import com.sun.electric.tool.routing.seaOfGates.SeaOfGatesEngineFactory.SeaOfGatesEngineType;
import com.sun.electric.tool.routing.seaOfGates.SeaOfGatesHandlers;
import com.sun.electric.util.ElapseTimer;
import com.sun.electric.util.TextUtils.UnitScale;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.Serializable;
import java.net.URL;
import java.net.URLConnection;
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 java.util.TreeMap;
import java.util.TreeSet;

/**
 * Class to control sea-of-gates routing.
 */
public class SeaOfGates
{
    /**
     * Method to run Sea-of-Gates routing on the current cell.
     * Presumes that it is inside of a Job.
     * @param justSubCells true to run the router on all sub-cells of the current cell
     * (but not on the current cell itself).
     */
    public static void seaOfGatesRoute(boolean justSubCells)
    {
    	if (justSubCells)
    	{
            // get cell
            UserInterface ui = Job.getUserInterface();
            Cell cell = ui.needCurrentCell();
            if (cell == null) return;
            Set<Cell> subCells =  new HashSet<Cell>();
            for(Iterator<NodeInst> it = cell.getNodes(); it.hasNext(); )
            {
            	NodeInst ni = it.next();
            	if (ni.isCellInstance()) subCells.add((Cell)ni.getProto());
            }
            for(Cell subCell : subCells)
            {
                List<ArcInst> selected = new ArrayList<ArcInst>();
                for (Iterator<ArcInst> it = subCell.getArcs(); it.hasNext(); )
                {
                	ArcInst ai = it.next();
                    if (ai.getProto() != Generic.tech().unrouted_arc) continue;
                    selected.add(ai);
                }
                if (!selected.isEmpty())
                {
                    // Run seaOfGatesRoute on subcell
                    SeaOfGatesHandlers.startInJob(subCell, selected, SeaOfGatesEngineType.defaultVersion);
                }
            }
    	} else
    	{
    		seaOfGatesRoute(SeaOfGatesEngineType.defaultVersion);
    	}
    }

    /**
     * Method to run Sea-of-Gates routing on the current cell, using a specified routing engine type.
     * Presumes that it is inside of a Job.
     */
    public static void seaOfGatesRoute(SeaOfGatesEngineType version)
    {
        // get cell and network information
        UserInterface ui = Job.getUserInterface();
        Cell cell = ui.needCurrentCell();
        if (cell == null) return;

        // get list of selected nets
        List<ArcInst> selected = getSelected();
        if (selected == null) return;

        // make sure there is something to route
        if (selected.isEmpty())
        {
            ui.showErrorMessage("There are no Unrouted Arcs in this cell", "Routing Error");
            return;
        }

        // Run seaOfGatesRoute on selected unrouted arcs in a separate job
        SeaOfGatesHandlers.startInJob(cell, selected, version);
    }
    
    /**
     * Method to run Sea-of-Gates routing on the current cell, using a specified routing engine.
     * Presumes that it is inside of a Job.
     */
    public static void seaOfGatesRoute(EditingPreferences ep, SeaOfGatesEngine router)
    {
        if (router == null) {
            throw new NullPointerException();
        }

        // get cell and network information
        UserInterface ui = Job.getUserInterface();
        Cell cell = ui.needCurrentCell();
        if (cell == null) return;

        // get list of selected nets
        List<ArcInst> selected = getSelected();

        // make sure there is something to route
        if (selected.isEmpty())
        {
            ui.showErrorMessage("There are no Unrouted Arcs in this cell", "Routing Error");
            return;
        }

        // Run seaOfGatesRoute on selected unrouted arcs
        Job job = Job.getRunningJob();
		router.routeIt(SeaOfGatesHandlers.getDefault(cell, router.getPrefs().resultCellName, job, ep), cell, false, selected);
    }

    private static List<ArcInst> getSelected() {
        EditWindow_ wnd = Job.getUserInterface().getCurrentEditWindow_();
        if (wnd == null) return null;
        Cell cell = wnd.getCell();
        if (cell == null) return null;
        
        // get list of selected nets
        List<ArcInst> selected = new ArrayList<ArcInst>();
        List<Geometric> highlighted = wnd.getHighlightedEObjs(false, true);
        for(Geometric h : highlighted)
        {
            ArcInst ai = (ArcInst)h;
            if (ai.getProto() != Generic.tech().unrouted_arc) continue;
            selected.add(ai);
        }
        if (selected.isEmpty())
        {
            for (Iterator<ArcInst> it = cell.getArcs(); it.hasNext(); )
            {
            	ArcInst ai = it.next();
                if (ai.getProto() != Generic.tech().unrouted_arc) continue;
                selected.add(ai);
            }
        }
        return selected;
    }

     /**
     * Class to hold preferences during Sea-of-Gates routing run.
     */
    public static class SeaOfGatesOptions implements Serializable
    {
        public boolean useParallelFromToRoutes;
        public boolean useParallelRoutes;
        public double maxArcWidth;
        public int complexityLimit, rerunComplexityLimit;
        public boolean useGlobalRouter, reRunFailedRoutes, enableSpineRouting;
        public int forcedNumberOfThreads;
        public String resultCellName;
        public ElapseTimer theTimer;

        public SeaOfGatesOptions()
        {
            useParallelFromToRoutes = true;
            useParallelRoutes = false;
            maxArcWidth = 10;
            complexityLimit = 200000;
            rerunComplexityLimit = 400000;
            useGlobalRouter = false;
            reRunFailedRoutes = false;
            enableSpineRouting = false;
            forcedNumberOfThreads = 0;
            resultCellName = null;
        }

        public void getOptionsFromPreferences(boolean factory)
        {
        	if (factory)
        	{
	            useParallelFromToRoutes = Routing.isFactorySeaOfGatesUseParallelFromToRoutes();
	            useParallelRoutes = Routing.isFactorySeaOfGatesUseParallelRoutes();
	            maxArcWidth = Routing.getFactorySeaOfGatesMaxWidth();
	            complexityLimit = Routing.getFactorySeaOfGatesComplexityLimit();
	            rerunComplexityLimit = Routing.getFactorySeaOfGatesRerunComplexityLimit();
	            useGlobalRouter = Routing.isFactorySeaOfGatesUseGlobalRouting();
	            reRunFailedRoutes = Routing.isFactorySeaOfGatesRerunFailedRoutes();
	            enableSpineRouting = Routing.isFactorySeaOfGatesEnableSpineRouting();
	            forcedNumberOfThreads = Routing.getFactorySeaOfGatesForcedProcessorCount();
        	}
        	else
        	{
	            useParallelFromToRoutes = Routing.isSeaOfGatesUseParallelFromToRoutes();
	            useParallelRoutes = Routing.isSeaOfGatesUseParallelRoutes();
	            maxArcWidth = Routing.getSeaOfGatesMaxWidth();
	            complexityLimit = Routing.getSeaOfGatesComplexityLimit();
	            rerunComplexityLimit = Routing.getSeaOfGatesRerunComplexityLimit();
	            useGlobalRouter = Routing.isSeaOfGatesUseGlobalRouting();
	            reRunFailedRoutes = Routing.isSeaOfGatesRerunFailedRoutes();
	            enableSpineRouting = Routing.isSeaOfGatesEnableSpineRouting();
	            forcedNumberOfThreads = Routing.getSeaOfGatesForcedProcessorCount();
        	}
        }
    }

    public static class SeaOfGatesArcProperties implements Serializable
    {
    	private Double overrideWidth;
    	private Double overrideSpacing;

    	public SeaOfGatesArcProperties()
    	{
    		overrideWidth = null;
    		overrideSpacing = null;
    	}

    	public void setWidthOverride(Double w) { overrideWidth = w; }

    	public Double getWidthOverride() { return overrideWidth; }

    	public void setSpacingOverride(Double s) { overrideSpacing = s; }

    	public Double getSpacingOverride() { return overrideSpacing; }
    }

    /**
     * Class to define Sea-of-Gates routing parameters that apply only to a specific Cell.
     */
    public static class SeaOfGatesCellParameters implements Serializable
	{
    	private Cell cell;
    	private boolean steinerDone, forceHorVer, favorHorVer, horEven, canRotateContacts;
    	private Map<ArcProto,String> gridSpacing;
    	private Set<ArcProto> preventedArcs, favoredArcs;
    	private Map<ArcProto,SeaOfGatesArcProperties> overrides;
    	private Map<String,List<ArcProto>> netsAndArcsToRoute;
    	private Map<String,Map<ArcProto,SeaOfGatesArcProperties>> netAndArcOverrides;
    	private String ignorePrimitives; // regular expression to ignore certain primitives - null by default
    	private String acceptOnlyPrimitives; // regular expression to accept only certain primitives - null by default

		/** key of Variable holding SOG parameters. */	private static final Variable.Key ROUTING_SOG_PARAMETERS_KEY = Variable.newKey("ATTR_ROUTING_SOG_PARAMETERS");

		public void clear()
		{
			this.steinerDone = false;
			this.forceHorVer = false;
			this.favorHorVer = true;
			this.horEven = false;
			this.canRotateContacts = true;
			this.gridSpacing = new HashMap<ArcProto,String>();
			this.preventedArcs = new HashSet<ArcProto>();
			this.favoredArcs = new HashSet<ArcProto>();
			this.overrides = new HashMap<ArcProto,SeaOfGatesArcProperties>();
			this.netsAndArcsToRoute = new TreeMap<String,List<ArcProto>>();
			this.netAndArcOverrides = new HashMap<String,Map<ArcProto,SeaOfGatesArcProperties>>();
			this.ignorePrimitives = null;
			this.acceptOnlyPrimitives = null;
		}

		/**
		 * Constructor for the SOG parameters specific to a given Cell.
		 * @param cell the Cell in question.
		 */
		public SeaOfGatesCellParameters(Cell cell)
		{
			this.cell = cell;
			clear();
			
			Variable var = cell.getVar(ROUTING_SOG_PARAMETERS_KEY);
			if (var != null)
			{
				String[] lines = (String[])var.getObject();
				for(int i=0; i<lines.length; i++)
				{
					String[] parts = lines[i].split(" ");
					if (parts.length <= 0) continue;
					if (parts[0].startsWith(";")) continue;
					if (parts[0].equalsIgnoreCase("SteinerTreesDone"))
					{
						steinerDone = true;
						continue;
					}
					if (parts[0].equalsIgnoreCase("NoContactRotation"))
					{
						canRotateContacts = false;
						continue;
					}
					if (parts[0].equalsIgnoreCase("AcceptContacts"))
					{
						int spacePos = lines[i].indexOf(' ');
						if (spacePos > 0) acceptOnlyPrimitives = lines[i].substring(spacePos+1);
						continue;
					}
					if (parts[0].equalsIgnoreCase("IgnoreContacts"))
					{
						int spacePos = lines[i].indexOf(' ');
						if (spacePos > 0) ignorePrimitives = lines[i].substring(spacePos+1);
						continue;
					}
					if (parts[0].equalsIgnoreCase("ForceHorVer"))
					{
						forceHorVer = true;
						continue;
					}
					if (parts[0].equalsIgnoreCase("IgnoreHorVer"))
					{
						favorHorVer = false;
						continue;
					}
					if (parts[0].equalsIgnoreCase("HorizontalEven"))
					{
						horEven = true;
						continue;
					}
					if (parts[0].equalsIgnoreCase("ArcGrid") && parts.length >= 3)
					{
						ArcProto ap = parseArcName(parts[1]);
						if (ap != null) gridSpacing.put(ap, parts[2]);
						continue;
					}
					if (parts[0].equalsIgnoreCase("ArcAvoid") && parts.length >= 2)
					{
						ArcProto ap = parseArcName(parts[1]);
						if (ap != null) preventedArcs.add(ap);
						continue;
					}
					if (parts[0].equalsIgnoreCase("ArcFavor") && parts.length >= 2)
					{
						ArcProto ap = parseArcName(parts[1]);
						if (ap != null) favoredArcs.add(ap);
						continue;
					}
					if (parts[0].equalsIgnoreCase("ArcWidthOverride") && parts.length >= 3)
					{
						ArcProto ap = parseArcName(parts[1]);
						if (ap != null)
						{
							SeaOfGatesArcProperties sogap = overrides.get(ap);
							if (sogap == null) overrides.put(ap, sogap = new SeaOfGatesArcProperties());
							sogap.setWidthOverride(TextUtils.atof(parts[2]));
						}
						continue;
					}
					if (parts[0].equalsIgnoreCase("ArcSpacingOverride") && parts.length >= 3)
					{
						ArcProto ap = parseArcName(parts[1]);
						if (ap != null)
						{
							SeaOfGatesArcProperties sogap = overrides.get(ap);
							if (sogap == null) overrides.put(ap, sogap = new SeaOfGatesArcProperties());
							sogap.setSpacingOverride(TextUtils.atof(parts[2]));
						}
						continue;
					}
					if (parts[0].equalsIgnoreCase("Network") && parts.length >= 2)
					{
						String netName = parts[1];
						List<ArcProto> arcs = netsAndArcsToRoute.get(netName);
						if (arcs == null) netsAndArcsToRoute.put(netName, arcs = new ArrayList<ArcProto>());
						for(int p=2; p<parts.length; p++)
						{
							ArcProto ap = parseArcName(parts[p]);
							arcs.add(ap);
						}
						continue;
					}
					if (parts[0].equalsIgnoreCase("NetworkOverride") && parts.length >= 2)
					{
						String netName = parts[1];
						Map<ArcProto,SeaOfGatesArcProperties> arcs = netAndArcOverrides.get(netName);
						if (arcs == null) netAndArcOverrides.put(netName, arcs = new HashMap<ArcProto,SeaOfGatesArcProperties>());
						SeaOfGatesArcProperties curOverrides = null;
						for(int p=2; p<parts.length; p++)
						{
							if (parts[p].startsWith("W="))
							{
								if (curOverrides != null)
									curOverrides.setWidthOverride(TextUtils.atof(parts[p].substring(2)));
								continue;
							}
							if (parts[p].startsWith("S="))
							{
								if (curOverrides != null)
									curOverrides.setSpacingOverride(TextUtils.atof(parts[p].substring(2)));
								continue;
							}
							ArcProto ap = parseArcName(parts[p]);
							curOverrides = arcs.get(ap);
							if (curOverrides == null) arcs.put(ap, curOverrides = new SeaOfGatesArcProperties());
							continue;
						}
						continue;
					}
				}
			}
		}

	    /**
	     * Method to import the control file for routing.
	     * @param fileName the name of the control file.
	     * @param curTech the current technology to use for this.
	     */
	    public void importData(String fileName, Technology curTech)
	    {
	    	URL url = TextUtils.makeURLToFile(fileName);
	    	try
	    	{
	    		URLConnection urlCon = url.openConnection();
	    		InputStreamReader is = new InputStreamReader(urlCon.getInputStream());
	    		LineNumberReader lineReader = new LineNumberReader(is);
	    		clear();
	    		List<ArcProto> layersToOverride = new ArrayList<ArcProto>();
	    		Map<ArcProto,Double> widthsToOverride = new HashMap<ArcProto,Double>();
	    		Map<ArcProto,Double> spacingsToOverride = new HashMap<ArcProto,Double>();
	    		boolean figuredOutAlternation = false;
	    		forceHorVer = true;
	    		canRotateContacts = false;
	    		for(;;)
	    		{
	    			String buf = lineReader.readLine();
	    			if (buf == null) break;
	    			if (buf.length() == 0 || buf.startsWith(";")) continue;

					String[] parts = buf.split(" ");
					if (parts.length <= 0) continue;

					if (parts[0].equalsIgnoreCase("Project")) continue;
					if (parts[0].equalsIgnoreCase("Library")) continue;
					if (parts[0].equalsIgnoreCase("Cell"))
					{
						if (!getCell().getName().equals(parts[1]))
						{
							System.out.println("WARNING: Cell name " + parts[1] + " on line " + lineReader.getLineNumber() +
								" doesn't match current cell (" + getCell().describe(false) + ")");
						}
						continue;
					}
					if (parts[0].equalsIgnoreCase("View")) continue;
					if (parts[0].equalsIgnoreCase("PowerNets")) continue;
					if (parts[0].equalsIgnoreCase("HorizontalEven"))
					{
						// default is even metals vertical with 
						horEven = true;
						continue;
					}
					if (parts[0].equalsIgnoreCase("ContactInclusion"))
					{
						if (parts.length > 1 && !parts[1].isEmpty())
							acceptOnlyPrimitives = parts[1];
						else
							System.out.println("WARNING: no regular expression for ContactInclusion on line " + lineReader.getLineNumber());
						continue;
					}
					// HorizontalMetals M<x> <anything> where x is a number
					if (parts[0].equalsIgnoreCase("HorizontalMetals"))
					{
						assert(parts.length > 1);
						// just getting first element
						ArcProto ap = getArcProto(parts[1], lineReader.getLineNumber(), curTech);
						if (ap == null) continue;
						int level = ap.getFunction().getLevel();
						this.horEven = level%2 == 0;
					}
					// VerticalMetals M<x> <anything> where x is a number
					if (parts[0].equalsIgnoreCase("VerticalMetals"))
					{
						assert(parts.length > 1);
						// just getting first element
						ArcProto ap = getArcProto(parts[1], lineReader.getLineNumber(), curTech);
						if (ap == null) continue;
						int level = ap.getFunction().getLevel();
						this.horEven = level%2 != 0;
					}
					if (parts[0].equalsIgnoreCase("DefaultRouteMetals"))
					{
						Set<ArcProto> allowed = new HashSet<ArcProto>();
						for(int i=1; i<parts.length; i++)
						{
							ArcProto ap = getArcProto(parts[i], lineReader.getLineNumber(), curTech);
							if (ap == null) continue;
							allowed.add(ap);
						}
						for(Iterator<ArcProto> it = curTech.getArcs(); it.hasNext(); )
						{
							ArcProto ap = it.next();
							setPrevented(ap, !allowed.contains(ap));
						}
						continue;
					}
					if (parts[0].equalsIgnoreCase("DefaultMetalWS") && parts.length >= 4)
					{
						ArcProto ap = getArcProto(parts[1], lineReader.getLineNumber(), curTech);
						double width = TextUtils.atof(parts[2]);
						width = TextUtils.convertFromDistance(width, curTech, UnitScale.MICRO);
						double spacing = TextUtils.atof(parts[3]);
						spacing = TextUtils.convertFromDistance(spacing, curTech, UnitScale.MICRO);
						setDefaultWidthOverride(ap, width);
						setDefaultSpacingOverride(ap, spacing);
						continue;
					}
					if (parts[0].equalsIgnoreCase("Track"))
					{
						ArcProto ap = getArcProto(parts[1], lineReader.getLineNumber(), curTech);
						int arcLevel = ap.getFunction().getLevel();
						boolean wantHorEven;
						if (parts[2].equalsIgnoreCase("t"))
						{
							// this metal is horizontal
							wantHorEven = (arcLevel%2) == 0;
						} else
						{
							// this metal is vertical
							wantHorEven = (arcLevel%2) != 0;
						}
						if (figuredOutAlternation)
						{
							if (wantHorEven != horEven)
								System.out.println("Warning: Horizontal layers are " + (horEven?"":" not") + " even but metal " +
									parts[1] + " has horizontal set to " + parts[2]);
						} else
						{
							horEven = wantHorEven;
							figuredOutAlternation = true;
						}
						double coord = TextUtils.atof(parts[3]);
						coord = TextUtils.convertFromDistance(coord, curTech, UnitScale.MICRO);
						double spacing = TextUtils.atof(parts[4]);
						spacing = TextUtils.convertFromDistance(spacing, curTech, UnitScale.MICRO);
						int numTracks = TextUtils.atoi(parts[5]);

						// get former grid data
						Set<Double> gridValues = new TreeSet<Double>();
						String formerGrid = gridSpacing.get(ap);
						if (formerGrid != null)
						{
							String[] gridParts = formerGrid.split(",");
							for(int i=0; i<gridParts.length; i++)
							{
								String part = gridParts[i].trim();
								if (part.length() == 0) continue;
								double val = TextUtils.atof(part);
								gridValues.add(new Double(val));
							}
						}
						
						// add in new grid data
						for(int i=0; i<numTracks; i++)
						{
							gridValues.add(new Double(coord));
							coord += spacing;
						}
						
						// build new list
						String newGrid = "";
						for(Double v : gridValues)
						{
							if (newGrid.length() > 0) newGrid += ",";
							newGrid += v.doubleValue();
						}
						gridSpacing.put(ap, newGrid);
						continue;
					}
					if (parts[0].equalsIgnoreCase("NET"))
					{
						parts[1] = parts[1].replace("<", "[").replace(">", "]");
						addNetToRoute(parts[1]);
						for(ArcProto ap : layersToOverride)
							addArcToNet(parts[1], ap);
						for(ArcProto ap : widthsToOverride.keySet())
						{
							Double width = widthsToOverride.get(ap);
							setWidthOverrideForArcOnNet(parts[1], ap, width);
						}
						for(ArcProto ap : spacingsToOverride.keySet())
						{
							Double spacing = spacingsToOverride.get(ap);
							setSpacingOverrideForArcOnNet(parts[1], ap, spacing);
						}
						layersToOverride.clear();
						widthsToOverride.clear();
						spacingsToOverride.clear();
						continue;
					}
					if (parts[0].equalsIgnoreCase("layers"))
					{
						for(int i=1; i<parts.length; i++)
						{
							ArcProto ap = getArcProto(parts[i], lineReader.getLineNumber(), curTech);
							if (ap == null) continue;
							layersToOverride.add(ap);
						}
						continue;
					}
					if (parts[0].equalsIgnoreCase("width"))
					{
						for(int i=1; i<parts.length; i += 2)
						{
							ArcProto ap = getArcProto(parts[i], lineReader.getLineNumber(), curTech);
							if (ap == null) continue;
							double v = TextUtils.atof(parts[i+1]);
							v = TextUtils.convertFromDistance(v, curTech, UnitScale.MICRO);
							widthsToOverride.put(ap, new Double(v));
						}
						continue;
					}
					if (parts[0].equalsIgnoreCase("spacing"))
					{
						for(int i=1; i<parts.length; i += 2)
						{
							ArcProto ap = getArcProto(parts[i], lineReader.getLineNumber(), curTech);
							if (ap == null) continue;
							double v = TextUtils.atof(parts[i+1]);
							v = TextUtils.convertFromDistance(v, curTech, UnitScale.MICRO);
							spacingsToOverride.put(ap, new Double(v));
						}
						continue;
					}
	    		}
	    		lineReader.close();
	    	} catch (IOException e)
	    	{
	    		System.out.println("Error reading " + fileName);
	    		return;
	    	}
	    }

	    private ArcProto getArcProto(String name, int lineNumber, Technology tech)
	    {
			if (!name.startsWith("M"))
			{
				System.out.println("ERROR: Unrecognized layer name on line " + lineNumber + ": " + name);
				return null;
			}
			int metNum = TextUtils.atoi(name.substring(1));
			if (metNum <= 0 || metNum > tech.getNumArcs())
			{
				System.out.println("ERROR: Unrecognized metal number on line " + lineNumber + ": " + name);
				return null;
			}
			ArcProto found = null;
			for(Iterator<ArcProto> it = tech.getArcs(); it.hasNext(); )
			{
				ArcProto ap = it.next();
				if (ap.getFunction().getLevel() == metNum) { found = ap;   break; }
			}
			if (found == null)
			{
				System.out.println("ERROR: Unrecognized metal layer on line " + lineNumber + ": " + name);
				return null;
			}
			return found;
	    }

		private ArcProto parseArcName(String name)
		{
			int colonPos = name.indexOf(':');
			if (colonPos < 0) return null;
			String techName = name.substring(0, colonPos);
			String layerName = name.substring(colonPos+1);
			Technology tech = Technology.findTechnology(techName);
			ArcProto ap = tech.findArcProto(layerName);
			return ap;
		}

		/**
		 * Method to save changes to this SeaOfGatesCellParameters object.
		 * @param ep the EditingPreferences needed for database changes.
		 */
		public void saveParameters(EditingPreferences ep)
		{
			List<String> strings = new ArrayList<String>();

			// header
			strings.add("; Parameters for Cell " + cell.describe(false));

			// steiner trees
			if (steinerDone) strings.add("SteinerTreesDone");

			// contacts
			if (!canRotateContacts) strings.add("NoContactRotation");
			if (ignorePrimitives != null && ignorePrimitives.length() > 0) strings.add("IgnoreContacts " + ignorePrimitives);
			if (acceptOnlyPrimitives != null && acceptOnlyPrimitives.length() > 0) strings.add("AcceptContacts " + acceptOnlyPrimitives);

			// miscellaneous
			if (!favorHorVer) strings.add("IgnoreHorVer");
			if (forceHorVer) strings.add("ForceHorVer");
			if (horEven) strings.add("HorizontalEven");

			// ArcProto information
			for(ArcProto ap : gridSpacing.keySet())
			{
				String grid = gridSpacing.get(ap);
				strings.add("ArcGrid " + ap.getTechnology().getTechName() + ":" + ap.getName() + " " + grid);
			}
			for(ArcProto ap : preventedArcs)
			{
				strings.add("ArcAvoid " + ap.getTechnology().getTechName() + ":" + ap.getName());
			}
			for(ArcProto ap : favoredArcs)
			{
				strings.add("ArcFavor " + ap.getTechnology().getTechName() + ":" + ap.getName());
			}
			for(ArcProto ap : overrides.keySet())
			{
				SeaOfGatesArcProperties sogap = overrides.get(ap);
				if (sogap.getWidthOverride() != null)
					strings.add("ArcWidthOverride " + ap.getTechnology().getTechName() + ":" + ap.getName() +
						" " +sogap.getWidthOverride().doubleValue());
				if (sogap.getSpacingOverride() != null)
					strings.add("ArcSpacingOverride " + ap.getTechnology().getTechName() + ":" + ap.getName() +
						" " +sogap.getSpacingOverride().doubleValue());
			}

			// network information
			for(String netName : netsAndArcsToRoute.keySet())
			{
				String line = "Network " + netName;
				List<ArcProto> arcs = netsAndArcsToRoute.get(netName);
				if (arcs != null)
				{
					for(ArcProto ap : arcs)
						line += " " + ap.getTechnology().getTechName() + ":" + ap.getName();
				}
				strings.add(line);
			}
			for(String netName : netAndArcOverrides.keySet())
			{
				String line = "NetworkOverride " + netName;
				Map<ArcProto,SeaOfGatesArcProperties> arcs = netAndArcOverrides.get(netName);
				if (arcs != null)
				{
					for(ArcProto ap : arcs.keySet())
					{
						SeaOfGatesArcProperties overrides = arcs.get(ap);
						if (overrides != null)
						{
							line += " " + ap.getTechnology().getTechName() + ":" + ap.getName();
							if (overrides.getWidthOverride() != null) line += " W=" + overrides.getWidthOverride().doubleValue();
							if (overrides.getSpacingOverride() != null) line += " S=" + overrides.getSpacingOverride().doubleValue();
						}
					}
				}
				strings.add(line);
			}

			String[] paramArray = new String[strings.size()];
			for(int i=0; i<strings.size(); i++) paramArray[i] = strings.get(i);
			cell.newVar(ROUTING_SOG_PARAMETERS_KEY, paramArray, ep);
		}

		/**
		 * Method to return the cell that this object describes.
		 * @return the cell that this object describes.
		 */
		public Cell getCell() { return cell; }

		/**
		 * Method to tell whether routing the Cell should 
		 * @param sd
		 */
		public void setSteinerDone(boolean sd) { steinerDone = sd; }

		public boolean isSteinerDone() { return steinerDone; }

		/**
		 * Method to set whether routing the Cell should favor alternating horizontal/vertical metal layers.
		 * Favoring uses weights to encourage the activity, but does not force it.
		 * @param f true if routing the Cell should favor alternating horizontal/vertical metal layers
		 */
		public void setFavorHorVer(boolean f) { favorHorVer = f; }

		/**
		 * Method to tell whether routing the Cell should favor alternating horizontal/vertical metal layers.
		 * Favoring uses weights to encourage the activity, but does not force it.
		 * @return true if routing the Cell should favor alternating horizontal/vertical metal layers
		 */
		public boolean isFavorHorVer() { return favorHorVer; }

		/**
		 * Method to set whether routing the Cell should force alternating horizontal/vertical metal layers.
		 * Forcing ensures that no metal layers will be allowed to run in the wrong direction.
		 * @param f true if routing the Cell should force alternating horizontal/vertical metal layers
		 */
		public void setForceHorVer(boolean f) { forceHorVer = f; }

		/**
		 * Method to tell whether routing the Cell should force alternating horizontal/vertical metal layers.
		 * Forcing ensures that no metal layers will be allowed to run in the wrong direction.
		 * @return true if routing the Cell should force alternating horizontal/vertical metal layers
		 */
		public boolean isForceHorVer() { return forceHorVer; }

		/**
		 * Method to set which way alternating horizontal/vertical metal layers run.
		 * @param he true to make even metal layers run horizontally, odd metal layers run vertically.
		 * False means even metal layers run vertically, odd metal layers run horizontally.
		 */
		public void setHorizontalEven(boolean he) { horEven = he; }

		/**
		 * Method to tell which way alternating horizontal/vertical metal layers run.
		 * @return true to make even metal layers run horizontally, odd metal layers run vertically.
		 * False means even metal layers run vertically, odd metal layers run horizontally.
		 */
		public boolean isHorizontalEven() { return horEven; }

		/**
		 * Method to set whether contact nodes can be rotated when placing them.
		 * @param he true to allow contact nodes to be rotated when placing them.
		 * False means they can only appear in their unrotated orientation.
		 */
		public void setContactsRotate(boolean he) { canRotateContacts = he; }

		/**
		 * Method to tell whether contact nodes can be rotated when placing them.
		 * @return true to allow contact nodes to be rotated during routing.
		 * False means they can only appear in their unrotated orientation.
		 */
		public boolean isContactsRotate() { return canRotateContacts; }

		/**
		 * Method to set whether a given layer is prevented in routing the Cell.
		 * @param ap the ArcProto of the layer in question.
		 * @param prevent true to prevent the layer from being used.
		 */
		public void setPrevented(ArcProto ap, boolean prevent)
		{
			if (prevent) preventedArcs.add(ap); else
				preventedArcs.remove(ap);
		}

		/**
		 * Method to tell whether a given layer is prevented in routing the Cell.
		 * @param ap the ArcProto of the layer in question.
		 * @return true if the layer is prevented from being used.
		 */
		public boolean isPrevented(ArcProto ap) { return preventedArcs.contains(ap); }

		/**
		 * Method to set whether a given layer is favored in routing the Cell.
		 * Favored layers are weighted stronger so they are used more.
		 * @param ap the ArcProto of the layer in question.
		 * @param f true to favor the layer.
		 */
		public void setFavored(ArcProto ap, boolean f)
		{
			if (f) favoredArcs.add(ap); else
				favoredArcs.remove(ap);
		}

		/**
		 * Method to tell whether a given layer is favored in routing the Cell.
		 * Favored layers are weighted stronger so they are used more.
		 * @param ap the ArcProto of the layer in question.
		 * @return true if the layer is favored.
		 */
		public boolean isFavored(ArcProto ap) { return favoredArcs.contains(ap); }

		/**
		 * Method to tell whether a given layer should use a different width when routine in the Cell.
		 * @param ap the ArcProto of the layer in question.
		 * @return the arc width to use (null to use the arc's default).
		 */
		public Double getDefaultWidthOverride(ArcProto ap)
		{
			SeaOfGatesArcProperties sogap = overrides.get(ap);
			if (sogap == null) return null;
			return sogap.overrideWidth;
		}

		/**
		 * Method to set whether a given layer should use a different width when routine in the Cell.
		 * @param ap the ArcProto of the layer in question.
		 * @param w the new width to use (null to use the default).
		 */
		public void setDefaultWidthOverride(ArcProto ap, Double w)
		{
			SeaOfGatesArcProperties sogap = overrides.get(ap);
			if (sogap == null) overrides.put(ap, sogap = new SeaOfGatesArcProperties());
			sogap.setWidthOverride(w);
		}

		/**
		 * Method to tell whether a given layer should use a different spacing when routine in the Cell.
		 * @param ap the ArcProto of the layer in question.
		 * @return the arc spacing to use (null to use the arc's default).
		 */
		public Double getDefaultSpacingOverride(ArcProto ap)
		{
			SeaOfGatesArcProperties sogap = overrides.get(ap);
			if (sogap == null) return null;
			return sogap.overrideSpacing;
		}

		/**
		 * Method to set whether a given layer should use a different spacing when routine in the Cell.
		 * @param ap the ArcProto of the layer in question.
		 * @param s the new spacing to use (null to use the default).
		 */
		public void setDefaultSpacingOverride(ArcProto ap, Double s)
		{
			SeaOfGatesArcProperties sogap = overrides.get(ap);
			if (sogap == null) overrides.put(ap, sogap = new SeaOfGatesArcProperties());
			sogap.setSpacingOverride(s);
		}

		/**
		 * Method to return a Set of network names to be routed in this cell.
		 * @return a Set of network names (null to route everything).
		 */
		public Set<String> getNetsToRoute()
		{
			return netsAndArcsToRoute.keySet();
		}

		/**
		 * Method to add a network name to the list of nets to route.
		 * @param netName the network name to add to the list of nets to route.
		 */
		public void addNetToRoute(String netName)
		{
			List<ArcProto> arcs = netsAndArcsToRoute.get(netName);
			if (arcs == null) netsAndArcsToRoute.put(netName, arcs = new ArrayList<ArcProto>());
		}

		/**
		 * Method to remove a network name from the list of nets to route.
		 * @param netName the network name to remove from the list of nets to route.
		 */
		public void removeNetToRoute(String netName)
		{
			List<ArcProto> arcs = netsAndArcsToRoute.get(netName);
			if (arcs != null) netsAndArcsToRoute.remove(netName);
		}

		/**
		 * Method to return a List of Arcs that can be used on a network name in this cell.
		 * @param net the name of the network.
		 * @return a List of ArcProtos to be used (null to use the default set).
		 */
		public List<ArcProto> getArcsOnNet(String net)
		{
			List<ArcProto> arcsOnNet = netsAndArcsToRoute.get(net);
			if (arcsOnNet == null) return null;
			return arcsOnNet;
		}

		/**
		 * Method to add a new arc to a net being routed.
		 * Adding an arc to a net forces all arcs not added to be excluded from the route.
		 * @param net the network name.
		 * @param ap the ArcProto that can be used to route the named net.
		 */
		public void addArcToNet(String net, ArcProto ap)
		{
			List<ArcProto> arcsOnNet = netsAndArcsToRoute.get(net);
			if (arcsOnNet == null) netsAndArcsToRoute.put(net, arcsOnNet = new ArrayList<ArcProto>());
			arcsOnNet.add(ap);
		}

		/**
		 * Method to remove an arc from a net being routed.
		 * If all arcs are removed from the net, the default set is used.
		 * @param net the network name.
		 * @param ap the ArcProto that can no longer be used to route the named net.
		 */
		public void removeArcFromNet(String net, ArcProto ap)
		{
			List<ArcProto> arcsOnNet = netsAndArcsToRoute.get(net);
			if (arcsOnNet == null) return;
			arcsOnNet.remove(ap);
		}

		/**
		 * Method to return an object with width and spacing overrides for a given arc on a given network in this cell.
		 * @param net the name of the network.
		 * @param ap the ArcProto being routed on the network.
		 * @return a SeaOfGatesArcProperties object with width and spacing overrides.
		 * A null return, or an object with nulls in the width or spacing, indicates that default values should be used.
		 */
		public SeaOfGatesArcProperties getOverridesForArcsOnNet(String net, ArcProto ap)
		{
			Map<ArcProto,SeaOfGatesArcProperties> arcsOnNet = netAndArcOverrides.get(net);
			if (arcsOnNet == null) return null;
			SeaOfGatesArcProperties sogap = arcsOnNet.get(ap);
			return sogap;
		}

		/**
		 * Method to set the width for a given arc on a given network.
		 * @param net the name of the network.
		 * @param ap the ArcProto being routed on the network.
		 * @param width the arc width to use (null to remove the override).
		 */
		public void setWidthOverrideForArcOnNet(String net, ArcProto ap, Double width)
		{
			Map<ArcProto,SeaOfGatesArcProperties> arcsOnNet = netAndArcOverrides.get(net);
			if (arcsOnNet == null) netAndArcOverrides.put(net, arcsOnNet = new HashMap<ArcProto,SeaOfGatesArcProperties>());
			SeaOfGatesArcProperties sogap = arcsOnNet.get(ap);
			if (sogap == null) arcsOnNet.put(ap, sogap = new SeaOfGatesArcProperties());
			sogap.setWidthOverride(width);
		}

		/**
		 * Method to set the spacing for a given arc on a given network.
		 * @param net the name of the network.
		 * @param ap the ArcProto being routed on the network.
		 * @param spacing the arc spacing to use (null to remove the override).
		 */
		public void setSpacingOverrideForArcOnNet(String net, ArcProto ap, Double spacing)
		{
			Map<ArcProto,SeaOfGatesArcProperties> arcsOnNet = netAndArcOverrides.get(net);
			if (arcsOnNet == null) netAndArcOverrides.put(net, arcsOnNet = new HashMap<ArcProto,SeaOfGatesArcProperties>());
			SeaOfGatesArcProperties sogap = arcsOnNet.get(ap);
			if (sogap == null) arcsOnNet.put(ap, sogap = new SeaOfGatesArcProperties());
			sogap.setSpacingOverride(spacing);
		}

		//private Map<String,List<ArcProto>> netsAndArcsToRoute;
		//private Map<String,Map<ArcProto,SeaOfGatesArcProperties>> netAndArcOverrides;
		/**
		 * Method to get the grid information for a given layer in the Cell.
		 * @return a String with the grid information.
		 */
		public String getGrid(ArcProto ap)
		{
			String v = gridSpacing.get(ap);
			return v;
		}

		/**
		 * Method to set the grid information for a given layer in the Cell.
		 * @param grid a String with the grid information.
		 */
		public void setGrid(ArcProto ap, String grid)
		{
			if (grid == null) gridSpacing.remove(ap); else
				gridSpacing.put(ap, grid);
		}
		
		/**
		 * Method to return possible regular expression to discard some primitives from routing.
		 * @return String with the regular expression
		 */
		public String getAcceptOnlyPrimitives() { return acceptOnlyPrimitives; }
		
		/**
		 * Method to set the regular expression to accept only certain primitives from routing.
		 * @param s String representing the regular expression
		 */
		public void setAcceptOnlyPrimitive(String s) {acceptOnlyPrimitives = s; }
		
		/**
		 * Method to return possible regular expression to accept only certain primitives from routing.
		 * @return String with the regular expression
		 */
		public String getIgnorePrimitives() { return ignorePrimitives; }
		
		/**
		 * Method to set the regular expression to discard some primitives from routing.
		 * @param s String representing the regular expression
		 */
		public void setIgnorePrimitive(String s) {ignorePrimitives = s; }
	}
}
