/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: Topology.java
 * Input/output tool: superclass for output modules that write connectivity.
 * Written by Steven M. Rubin, Sun Microsystems.
 *
 * Copyright (c) 2003, 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.io.output;

import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.hierarchy.Export;
import com.sun.electric.database.hierarchy.HierarchyEnumerator;
import com.sun.electric.database.hierarchy.Nodable;
import com.sun.electric.database.hierarchy.View;
import com.sun.electric.database.network.Global;
import com.sun.electric.database.network.Netlist;
import com.sun.electric.database.network.Network;
import com.sun.electric.database.network.NetworkTool;
import com.sun.electric.database.prototype.PortCharacteristic;
import com.sun.electric.database.prototype.PortProto;
import com.sun.electric.database.topology.ArcInst;
import com.sun.electric.database.topology.Connection;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.database.topology.PortInst;
import com.sun.electric.database.variable.VarContext;
import com.sun.electric.database.variable.Variable;
import com.sun.electric.technology.PrimitiveNode;
import com.sun.electric.technology.PrimitivePort;
import com.sun.electric.tool.generator.sclibrary.SCLibraryGen;
import com.sun.electric.util.TextUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * This is the Simulation Interface tool.
 */
public abstract class Topology extends Output
{
	private static final boolean DEBUGTOPOLOGY = false;

	/** top-level cell being processed */			protected Cell topCell;

	/** Map of all CellTopologies */				private Map<String,CellNetInfo> cellTopos;
	/** Map of all Cell names */					private Map<Cell,String> cellNameMap;
													private HierarchyEnumerator.CellInfo lastInfo;

	/** Creates a new instance of Topology */
	public Topology() {}

	/**
	 * Write cell to file
	 * @return true on error
	 */
    @Override
	public boolean writeCell(Cell cell, VarContext context)
	{
		writeCell(cell, context, new Visitor(this));
		return false;
	}

	/**
	 * Write cell to file
	 * @return true on error
	 */
	public boolean writeCell(Cell cell, VarContext context, Visitor visitor)
	{
		// remember the top-level cell
		topCell = cell;

		// clear the map of CellNetInfo for each processed cell
		cellTopos = new HashMap<String,CellNetInfo>();

		// make a map of cell names to use (unique across libraries)
		cellNameMap = makeCellNameMap(topCell);

		// write out cells
		start();
		HierarchyEnumerator.enumerateCell(cell, context, visitor, getShortResistors());
		done();
		return false;
	}

	/** Abstract method called before hierarchy traversal */
	protected abstract void start();

	/** Abstract method called after traversal */
	protected abstract void done();

	/** Called at the end of the enter cell phase of hierarchy enumeration */
	protected void enterCell(HierarchyEnumerator.CellInfo info) { }

	/** Abstract method to write CellGeom to disk */
	protected abstract void writeCellTopology(Cell cell, String cellName, CellNetInfo cni, VarContext context, Topology.MyCellInfo info);

	/** Abstract method to convert a network name to one that is safe for this format */
	protected abstract String getSafeNetName(String name, boolean bus);

	/** Abstract method to convert a cell name to one that is safe for this format */
	protected abstract String getSafeCellName(String name);

	/** Abstract method to return the proper name of Power (or null to use existing name) */
	protected abstract String getPowerName(Network net);

	/** Abstract method to return the proper name of Ground (or null to use existing name) */
	protected abstract String getGroundName(Network net);

	/** Abstract method to return the proper name of a Global signal */
	protected abstract String getGlobalName(Global glob);

	/**
	 * Abstract method to decide whether export names take precedence over
	 * arc names when determining the name of the network.
	 */
	protected abstract boolean isNetworksUseExportedNames();

	/** Abstract method to decide whether library names are always prepended to cell names. */
	protected abstract boolean isLibraryNameAlwaysAddedToCellName();

	/** Abstract method to decide whether aggregate names (busses) are used. */
	protected abstract boolean isAggregateNamesSupported();

	/** Abstract method to decide whether aggregate names (busses) can have gaps in their ranges. */
	protected abstract boolean isAggregateNameGapsSupported();

	/** Method to decide whether to choose best export name among exports connected to signal. */
	protected boolean isChooseBestExportName() { return true; }

	/** Abstract method to decide whether aggregate names (busses) are used. */
	protected abstract boolean isSeparateInputAndOutput();

	/** Abstract method to decide whether netlister is case-sensitive (Verilog) or not (Spice). */
	protected abstract boolean isCaseSensitive();

	/** Method to decide whether signal sorting supports ascending/descending order */
	protected boolean doesSupportDescAsc() { return true; }
	
	/**
	 * If the netlister has requirements not to netlist certain cells and their
	 * subcells, override this method.
	 */
	protected boolean skipCellAndSubcells(Cell cell) { return false; }

	/**
	 * Method to tell whether the netlister should write a separate copy of schematic cells for each icon.
	 *
	 * If you want this to happen, you must do these things:
	 * (1) override this method and have it return true.
	 * (2) At the place where the cell name is written, use this code:
	 *     // make sure to use the correct icon cell name if there are more than one
	 *     String subCellName = subCni.getParameterizedName();
	 *     NodeInst ni = no.getNodeInst();
	 *     if (ni != null)
	 *     {
	 *         String alternateSubCellName = getIconCellName((Cell)ni.getProto());
	 *         if (alternateSubCellName != null) subCellName = alternateSubCellName;
	 *     }
	 */
	protected boolean isWriteCopyForEachIcon() { return false; }

	/**
	 * If a cell is skipped, this method can perform any checking to
	 * validate that no error occurs
	 */
	protected void validateSkippedCell(HierarchyEnumerator.CellInfo info) { }

	/**
	 * Method to tell whether the topological analysis should mangle cell names that are parameterized.
	 */
	protected boolean canParameterizeNames() { return false; }

	/** Tell the Hierarchy enumerator how to short resistors */
	protected Netlist.ShortResistors getShortResistors() { return Netlist.ShortResistors.NO; }

	/** Tell the Hierarchy enumerator whether or not to short parasitic resistors */
	protected boolean isShortResistors() { return getShortResistors() != Netlist.ShortResistors.NO; }

	/** Tell the Hierarchy enumerator whether or not to short explicit (poly) resistors */
	protected boolean isShortExplicitResistors() { return getShortResistors() == Netlist.ShortResistors.ALL; }

	/** Tell the Hierarchy enumerator whether or not to visit icons without schematic equivalents. */
	protected boolean visitIcons() { return false; }

	/**
	 * Method to tell set a limit on the number of characters in a name.
	 * @return the limit to name size (0 if no limit).
	 */
	protected int maxNameLength() { return 0; }

	/** Abstract method to convert a cell name to one that is safe for this format */
	protected CellNetInfo getCellNetInfo(String cellName)
	{
		CellNetInfo cni = cellTopos.get(cellName);
		return cni;
	}

	/** Used to switch from schematic enumeration to layout enumeration */
	protected boolean enumerateLayoutView(Cell cell) { return false; }

	//------------------ override for HierarchyEnumerator.Visitor ----------------------

	public class MyCellInfo extends HierarchyEnumerator.CellInfo
	{
		String currentInstanceParametizedName;
	}

	public class Visitor extends HierarchyEnumerator.Visitor
	{
		/** Topology object this Visitor is enumerating for */	private Topology outGeom;

		public Visitor(Topology outGeom)
		{
			this.outGeom = outGeom;
		}

		public boolean enterCell(HierarchyEnumerator.CellInfo info)
		{
			if (skipCellAndSubcells(info.getCell()))
			{
				if (!info.isRootCell())
				{
					// save subcell topology, even though the cell isn't being written
					HierarchyEnumerator.CellInfo parentInfo = info.getParentInfo();
					Nodable no = info.getParentInst();
					String parameterizedName = parameterizedName(no, parentInfo.getContext());
					CellNetInfo cni = getNetworkInformation(info.getCell(), false, parameterizedName,
						isNetworksUseExportedNames(), info);
					cellTopos.put(parameterizedName, cni);
				}
				validateSkippedCell(info);
				return false;
			}
			outGeom.enterCell(info);
			return true;
		}

		public void exitCell(HierarchyEnumerator.CellInfo info)
		{
			// write cell
			Cell cell = info.getCell();
			CellNetInfo cni = null;
			if (info.isRootCell())
			{
				cni = getNetworkInformation(cell, false, getSafeCellName(cell.getName()),
					isNetworksUseExportedNames(), info);
				cellTopos.put(cell.getName(), cni);
			} else
			{
				// derived parameterized name of instance of this cell in parent
				HierarchyEnumerator.CellInfo parentInfo = info.getParentInfo();
				Nodable no = info.getParentInst();
				String parameterizedName = parameterizedName(no, parentInfo.getContext());
				cni = getNetworkInformation(info.getCell(), false, parameterizedName,
					isNetworksUseExportedNames(), info);
				cellTopos.put(parameterizedName, cni);
			}
			String cellName = cni.getParameterizedName();
			outGeom.writeCellTopology(cell, cellName, cni, info.getContext(), (MyCellInfo)info);

			// see if there are alternate icons with different names
			if (isWriteCopyForEachIcon() && cell != topCell)
			{
				for(Cell otherCell : cell.getCellsInGroup())
				{
					if (otherCell.getView() != View.ICON) continue;
					if (otherCell.getName().equals(cell.getName())) continue;
					String otherCellName = getSafeCellName(otherCell.getName());
					if (isLibraryNameAlwaysAddedToCellName())
					{
						if (otherCellName.startsWith("_")) otherCellName = "_" + otherCellName; else
							otherCellName = "__" + otherCellName;
						otherCellName = otherCell.getLibrary().getName() + otherCellName;
					}
					if (otherCellName.equals(cellName)) continue;
					outGeom.writeCellTopology(cell, otherCellName, cni, info.getContext(), (MyCellInfo)info);			
				}
			}

			lastInfo = info;
		}

		public boolean visitNodeInst(Nodable no, HierarchyEnumerator.CellInfo info)
		{
			if (!no.isCellInstance()) return false;

			VarContext context = info.getContext();
			String parameterizedName = parameterizedName(no, context);

			if (cellTopos.containsKey(parameterizedName)) return false;	// already processed this Cell

			Cell cell = (Cell)no.getProto();
			Cell schcell = cell.contentsView();
			if (schcell == null) schcell = cell;
			if (cell.isSchematic() && enumerateLayoutView(schcell)) {
				Cell layCell = cell.otherView(View.LAYOUT);
				if (layCell != null) {
					HierarchyEnumerator.enumerateCell(layCell, context, this);
					// save subcell topology, even though the cell isn't being written
					CellNetInfo cni = getNetworkInformation(layCell, false, layCell.getName(),
						isNetworksUseExportedNames(), lastInfo);
					cellTopos.put(parameterizedName, cni);
					return false;
				}
			}

			// else just a cell
			return true;
		}

		public HierarchyEnumerator.CellInfo newCellInfo()
		{
			return new MyCellInfo();
		}

		public boolean visitIcons() { return outGeom.visitIcons(); }
	}

	//---------------------------- Utility Methods --------------------------------------

	/**
	 * Class to describe a single signal in a Cell.
	 */
	protected static class CellSignal
	{
		/** name to use for this network. */		private String name;
		/** Network for this signal. */				private Network net;
		/** CellAggregate that this is part of. */	private CellAggregateSignal aggregateSignal;
		/** export that this is part of. */			private Export pp;
		/** if export is bussed, index of signal */ private int ppIndex;
		/** true if part of a descending bus */		private boolean descending;
		/** true if a power signal */				private boolean power;
		/** true if a ground signal */				private boolean ground;
		/** true if from a global signal */			private Global globalSignal;

		protected String getName() { return name; }
		protected Network getNetwork() { return net; }
		protected Export getExport() { return pp; }
		protected int getExportIndex() { return ppIndex; }
		protected CellAggregateSignal getAggregateSignal() { return aggregateSignal; }
		protected boolean isDescending() { return descending; }
		protected boolean isGlobal() { return globalSignal != null; }
		protected Global getGlobal() { return globalSignal; }
		protected boolean isPower() { return power; }
		protected boolean isGround() { return ground; }
		protected boolean isExported() { return pp != null; }
	}

	/**
	 * Class to describe an aggregate signal (a bus) in a Cell.
	 * The difference between aggregate signals and busses is that aggregate signals
	 * are more sensitive to variations.  For example, a bus may be X[1,2,4,5] but
	 * there may be two aggregate signals describing it because of the noncontinuity
	 * of the indices (thus, the aggregate signals will be X[1:2] and X_1[4:5]).
	 * Some netlisters allow for gaps, in which case they will be described with a
	 * single aggregate signal.  Other factors that cause busses to be broken into
	 * multiple aggregate signals are:
	 * (1) differences in port direction (mixes of input and output, for example)
	 * (2) differences in port type (power, ground, global, etc.)
	 */
	protected static class CellAggregateSignal
	{
		private String name;
		private Export pp;
		private int ppIndex;
		private int low;
		private int high;
		private int[] indices;
		private boolean supply;
		private boolean descending;
		private CellSignal [] signals;
		/** scratch word for the subclass */	private int flags;

		protected String getName() { return name; }
		protected String getNameWithIndices()
		{
			if (low > high) return name;
			if (indices != null)
			{
				StringBuffer sb = new StringBuffer();
				sb.append(name);
				sb.append('[');
				for(int i=0; i<indices.length; i++)
				{
					if (i != 0) sb.append(',');
					sb.append(indices[i]);
				}
				sb.append(']');
				return sb.toString();
			}
			int lowIndex = low, highIndex = high;
			if (descending)
			{
				lowIndex = high;   highIndex = low;
			}
			return name + "[" + lowIndex + ":" + highIndex + "]";
		}
		protected int getNumSignals() { return signals.length; }
		protected CellSignal getSignal(int index) { return signals[index]; }
		protected boolean isDescending() { return descending; }
		protected boolean isSupply() { return supply; }
		protected Export getExport() { return pp; }
		protected int getExportIndex() { return ppIndex; }
		protected int [] getIndices() { return indices; }
		protected int getLowIndex() { return low; }
		protected int getHighIndex() { return high; }
		protected int getFlags() { return flags; }
		protected void setFlags(int flags) { this.flags = flags; }
		protected boolean isGlobal()
		{
			int numGlobal = 0;
			for(int i=0; i<signals.length; i++)
			{
				CellSignal cs = signals[i];
				if (cs.isGlobal()) numGlobal++;
			}
			if (numGlobal > 0 && numGlobal == signals.length) return true;
			return false;
		}
	}

	/**
	 * Class to describe the networks in a Cell.
	 * This contains all of the CellSignal and CellAggregateSignal information.
	 */
	protected static class CellNetInfo
	{
		private Cell cell;
		private String paramName;
		private Map<Network,CellSignal> cellSignals;
		private List<CellSignal> cellSignalsSorted;
		private List<CellAggregateSignal> cellAggregateSignals;
		private Network pwrNet;
		private Network gndNet;
		private Netlist netList;

		protected CellSignal getCellSignal(Network net) { return cellSignals.get(net); }
		protected Iterator<CellSignal> getCellSignals() { return cellSignalsSorted.iterator(); }
		protected Iterator<CellAggregateSignal> getCellAggregateSignals() { return cellAggregateSignals.iterator(); }
		protected String getParameterizedName() { return paramName; }
		protected Network getPowerNet() { return pwrNet; }
		protected Network getGroundNet() { return gndNet; }
		protected Netlist getNetList() { return netList; }
		protected Cell getCell() { return cell; }
	}

	private CellNetInfo getNetworkInformation(Cell cell, boolean quiet, String paramName, boolean useExportedName,
		HierarchyEnumerator.CellInfo info)
	{
		CellNetInfo cni = doGetNetworks(cell, quiet, paramName, useExportedName, info);
		if (DEBUGTOPOLOGY)
		{
			printWriter.println("********Decomposition of " + cell);
			printWriter.println("** Have " + cni.cellSignalsSorted.size() + " signals:");
			for(Iterator<CellSignal> it = cni.getCellSignals(); it.hasNext(); )
			{
				CellSignal cs = it.next();
				printWriter.println("**   Name="+cs.name+" export="+cs.pp+" index="+cs.ppIndex+" descending="+cs.descending+" power="+cs.power+" ground="+cs.ground+" global="+cs.globalSignal);
			}
			if (isAggregateNamesSupported())
			{
				printWriter.println("** Have " + cni.cellAggregateSignals.size() + " aggregate signals:");
				for(CellAggregateSignal cas : cni.cellAggregateSignals)
				{
					printWriter.print("**   Name="+cas.name+", export="+cas.pp+" descending="+cas.descending);
					if (cas.indices != null)
					{
						printWriter.print(", indices=");
						for(int i=0; i<cas.indices.length; i++)
						{
							if (i != 0) printWriter.print(",");
							printWriter.print(cas.indices[i]);
						}
						printWriter.println();
					} else
					{
						printWriter.println(", low="+cas.low+", high="+cas.high);
					}
				}
			}
			printWriter.println("********DONE WITH " + cell);
		}
		return cni;
	}

	private CellNetInfo doGetNetworks(Cell cell, boolean quiet, String paramName, boolean useExportedName,
		HierarchyEnumerator.CellInfo info)
	{
		// create the object with cell net information
		CellNetInfo cni = new CellNetInfo();
		cni.cell = cell;
		cni.paramName = paramName;

		// get network information about this cell
		cni.netList = info.getNetlist();
		Global.Set globals = cni.netList.getGlobals();
		int globalSize = globals.size();

		// create a map of all nets in the cell
		cni.cellSignals = new HashMap<Network,CellSignal>();
		for(Iterator<Network> it = cni.netList.getNetworks(); it.hasNext(); )
		{
			Network net = it.next();

			// special case: ignore nets on transistor bias ports that are unconnected and unexported
			if (!net.isExported() && !net.getArcs().hasNext())
			{
				Iterator<PortInst> pIt = net.getPorts();
				if (pIt.hasNext())
				{
					PortInst onlyPI = pIt.next();
					PortProto pp = onlyPI.getPortProto();
					if (pp instanceof PrimitivePort && !pIt.hasNext())
					{
						// found just one primitive port on this network
						if (((PrimitivePort)pp).isWellPort()) continue;
					}
				}
			}

			CellSignal cs = new CellSignal();
			cs.pp = null;
			cs.descending = !NetworkTool.isBusAscending();
			cs.power = false;
			cs.ground = false;
			cs.net = net;

			// see if it is global
			cs.globalSignal = null;
			for(int j=0; j<globalSize; j++)
			{
				Global global = globals.get(j);
				if (cni.netList.getNetwork(global) == net) { cs.globalSignal = global;   break; }
			}

			// name the signal
			if (cs.globalSignal != null)
			{
				cs.name = getGlobalName(cs.globalSignal);
			} else
			{
				cs.name = net.getName();
			}

			// save it in the map of signals
 			cni.cellSignals.put(net, cs);
		}

		// look at all busses and mark directionality, first unnamed busses, then named ones
		for(int j=0; j<2; j++)
		{
			for(Iterator<ArcInst> aIt = cell.getArcs(); aIt.hasNext(); )
			{
				ArcInst ai = aIt.next();
				if (ai.getNameKey().isTempname())
				{
					// temporary names are handled first, ignore them in pass 2
					if (j != 0) continue;
				} else
				{
					// real names are handled second, ignore them in pass 1
					if (j == 0) continue;
				}
				int width = cni.netList.getBusWidth(ai);
				if (width < 2) continue;
				Network [] nets = new Network[width];
				String [] netNames = new String[width];
				for(int i=0; i<width; i++)
				{
					nets[i] = cni.netList.getNetwork(ai, i);
					netNames[i] = null;
					if (j != 0)
					{
						String preferredName = ai.getNameKey().subname(i).toString();
						for(Iterator<String> nIt = nets[i].getNames(); nIt.hasNext(); )
						{
							String netName = nIt.next();
							if (netName.equals(preferredName)) { netNames[i] = netName;   break; }
						}
					}
					if (netNames[i] == null) netNames[i] = nets[i].getNames().next();
				}
				setBusDirectionality(nets, netNames, cni);
			}
		}

		// mark exported networks and set their bus directionality
		for(Iterator<Export> it = cell.getExports(); it.hasNext(); )
		{
			Export pp = it.next();

			// mark every network on the bus (or just 1 network if not a bus)
			int portWidth = cni.netList.getBusWidth(pp);
			for(int i=0; i<portWidth; i++)
			{
				Network net = cni.netList.getNetwork(pp, i);
				CellSignal cs = cni.cellSignals.get(net);
				if (cs == null) continue;

				// if there is already an export on this signal, make sure that it is wider
				if (cs.pp != null)
				{
					if (isChooseBestExportName())
					{
						int oldPortWidth = cni.netList.getBusWidth(cs.pp);
						if (isAggregateNamesSupported())
						{
							// with aggregate names, the widest bus is the best, so that long runs can be emitted
							if (oldPortWidth >= portWidth) continue;
						} else
						{
							// without aggregate names, individual signal names are more important
							if (oldPortWidth == 1) continue;
							if (portWidth != 1 && oldPortWidth >= portWidth) continue;
						}
					} else
					{
						if (TextUtils.STRING_NUMBER_ORDER.compare(cs.pp.getName(), pp.getName()) <= 0) continue;
					}
				}

				// save this export's information
				cs.pp = pp;
				cs.ppIndex = i;
				if (useExportedName)
				{
					String rootName = pp.getName();
					if (portWidth == 1)
					{
						cs.name = rootName;
					}
					int openPos = rootName.indexOf('[');
					if (openPos > 0)
					{
						rootName = rootName.substring(0, openPos);
						for(Iterator<String> nIt = net.getExportedNames(); nIt.hasNext(); )
						{
							String exportNetName = nIt.next();
							String exportRootName = exportNetName;
							openPos = exportRootName.indexOf('[');
							if (openPos > 0)
							{
								exportRootName = exportRootName.substring(0, openPos);
								if (rootName.equals(exportRootName))
								{
									cs.name = rootName + exportNetName.substring(openPos);
								}
							}
						}
					}
				}
			}

			if (portWidth <= 1) continue;
			Network [] nets = new Network[portWidth];
			String [] netNames = new String[portWidth];
			for(int i=0; i<portWidth; i++)
			{
				nets[i] = cni.netList.getNetwork(pp, i);
				String preferredName = pp.getNameKey().subname(i).toString();
				netNames[i] = null;
				for(Iterator<String> nIt = nets[i].getNames(); nIt.hasNext(); )
				{
					String netName = nIt.next();
					if (netName.equals(preferredName)) { netNames[i] = netName;   break; }
				}
				if (netNames[i] == null) netNames[i] = nets[i].getNames().next();
			}
			setBusDirectionality(nets, netNames, cni);
		}

		// find power and ground
		cni.pwrNet = cni.gndNet = null;
		boolean multiPwr = false, multiGnd = false;
		for(Iterator<PortProto> eIt = cell.getPorts(); eIt.hasNext(); )
		{
			Export pp = (Export)eIt.next();
			int portWidth = cni.netList.getBusWidth(pp);
			if (portWidth > 1) continue;
			Network subNet = cni.netList.getNetwork(pp, 0);
			if (pp.isPower())
			{
				if (cni.pwrNet != null && cni.pwrNet != subNet && !multiPwr)
				{
					if (!quiet)
						System.out.println("Warning: multiple power networks in " + cell);
					multiPwr = true;
				}
				cni.pwrNet = subNet;
			}
			if (pp.isGround())
			{
				if (cni.gndNet != null && cni.gndNet != subNet && !multiGnd)
				{
					if (!quiet)
						System.out.println("Warning: multiple ground networks in " + cell);
					multiGnd = true;
				}
				cni.gndNet = subNet;
			}
		}
		for(Iterator<Network> it = cni.netList.getNetworks(); it.hasNext(); )
		{
			Network net = it.next();
			CellSignal cs = cni.cellSignals.get(net);
			if (cs == null) continue;
			if (cs.globalSignal != null)
			{
				if (cs.globalSignal == Global.power)
				{
					if (cni.pwrNet != null && cni.pwrNet != net && !multiPwr)
					{
						if (!quiet)
							System.out.println("Warning: multiple power networks in " + cell);
						multiPwr = true;
					}
					cni.pwrNet = net;
				}
				if (cs.globalSignal == Global.ground)
				{
					if (cni.gndNet != null && cni.gndNet != net && !multiGnd)
					{
						if (!quiet)
							System.out.println("Warning: multiple ground networks in " + cell);
						multiGnd = true;
					}
					cni.gndNet = net;
				}
			}
		}
		for(Iterator<NodeInst> it = cell.getNodes(); it.hasNext(); )
		{
			NodeInst ni = it.next();
			PrimitiveNode.Function fun = ni.getFunction();
			if (fun == PrimitiveNode.Function.CONPOWER || fun == PrimitiveNode.Function.CONGROUND)
			{
				for(Iterator<Connection> cIt = ni.getConnections(); cIt.hasNext(); )
				{
					Connection con = cIt.next();
					ArcInst ai = con.getArc();
					Network subNet = cni.netList.getNetwork(ai, 0);
					if (fun == PrimitiveNode.Function.CONPOWER)
					{
						if (cni.pwrNet != null && cni.pwrNet != subNet && !multiPwr)
						{
							if (!quiet)
								System.out.println("Warning: multiple power networks in " + cell);
							multiPwr = true;
						}
						cni.pwrNet = subNet;
					} else
					{
						if (cni.gndNet != null && cni.gndNet != subNet && !multiGnd)
						{
							if (!quiet)
								System.out.println("Warning: multiple ground networks in " + cell);
							multiGnd = true;
						}
						cni.gndNet = subNet;
					}
				}
			}
		}
		if (cni.pwrNet != null)
		{
			CellSignal cs = cni.cellSignals.get(cni.pwrNet);
			String powerName = getPowerName(cni.pwrNet);
			if (powerName != null) cs.name = powerName;
			cs.power = true;
		}
		if (cni.gndNet != null)
		{
			CellSignal cs = cni.cellSignals.get(cni.gndNet);
			String groundName = getGroundName(cni.gndNet);
			if (groundName != null) cs.name = groundName;
			cs.ground = true;
		}

		// make an array of the CellSignals
		cni.cellSignalsSorted = new ArrayList<CellSignal>();
		for(CellSignal cs : cni.cellSignals.values())
			cni.cellSignalsSorted.add(cs);

		// sort the networks by characteristic and name
		Collections.sort(cni.cellSignalsSorted, new SortNetsByName(isSeparateInputAndOutput(), 
				isAggregateNamesSupported(), isCaseSensitive(), doesSupportDescAsc()));

		// create aggregate signals if needed
		if (isAggregateNamesSupported())
		{
			// organize by name and index order
			cni.cellAggregateSignals = new ArrayList<CellAggregateSignal>();
			int total = cni.cellSignalsSorted.size();
			for(int i=0; i<total; i++)
			{
				CellSignal cs = cni.cellSignalsSorted.get(i);

				CellAggregateSignal cas = new CellAggregateSignal();
				cas.name = unIndexedName(cs.name);
				cas.pp = cs.pp;
				cas.ppIndex = cs.ppIndex;
				cas.supply = cs.power | cs.ground;
				cas.descending = cs.descending;
				cas.flags = 0;
				cs.aggregateSignal = cas;
				if (cs.name.equals(cas.name))
				{
					// single wire: set range to show that
					cas.low = 1;
					cas.high = 0;
					cas.signals = new CellSignal[1];
					cas.signals[0] = cs;
				} else
				{
					boolean hasGaps = false;
					cas.high = cas.low = TextUtils.atoi(cs.name.substring(cas.name.length()+1));
					int start = i;
					for(int j=i+1; j<total; j++)
					{
						CellSignal csEnd = cni.cellSignalsSorted.get(j);
						if (csEnd.descending != cs.descending) break;
//						if (cell.isSchematic() && csEnd.pp != cs.pp) break;
						if (csEnd.globalSignal != cs.globalSignal) break;
						String endName = csEnd.name;
						String ept = unIndexedName(endName);
						if (ept.equals(endName)) break;
						if (!ept.equals(cas.name)) break;
						int index = TextUtils.atoi(endName.substring(ept.length()+1));

						// make sure export indices go in order
						if (index != cas.high+1)
						{
							if (!isAggregateNameGapsSupported()) break;
							hasGaps = true;
						}
						if (index > cas.high) cas.high = index;
						i = j;
						csEnd.aggregateSignal = cas;
					}
					cas.signals = new CellSignal[i-start+1];
					if (hasGaps) cas.indices = new int[i-start+1];
					for(int j=start; j<=i; j++)
					{
						CellSignal csEnd = cni.cellSignalsSorted.get(j);
						if (hasGaps)
						{
							String endName = csEnd.name;
							String ept = unIndexedName(endName);
							cas.indices[j-start] = TextUtils.atoi(endName.substring(ept.length()+1));
						}
						cas.signals[j-start] = csEnd;
					}
				}
				cni.cellAggregateSignals.add(cas);
			}

			// give all signals a safe name
			for(CellAggregateSignal cas : cni.cellAggregateSignals)
				cas.name = getSafeNetName(cas.name, true);

			// make sure all names are unique
			int numNameList = cni.cellAggregateSignals.size();
			for(int i=1; i<numNameList; i++)
			{
				// single signal: give it that name
				CellAggregateSignal cas = cni.cellAggregateSignals.get(i);

				// see if the name clashes
				for(int k=0; k<1000; k++)
				{
					String ninName = cas.name;
					if (k > 0) ninName = ninName + "_" + k;
					boolean found = false;
					for(int j=0; j<i; j++)
					{
						CellAggregateSignal oCas = cni.cellAggregateSignals.get(j);
						if (ninName.equals(oCas.name)) { found = true;   break; }
					}
					if (!found)
					{
						if (k > 0) cas.name = ninName;
						break;
					}
				}
			}

			// put names back onto the signals
			for(CellAggregateSignal cas : cni.cellAggregateSignals)
			{
				if (cas.low > cas.high)
				{
					CellSignal cs = cas.signals[0];
					cs.name = cas.name;
				} else
				{
					if (cas.indices != null)
					{
						for(int k=0; k<cas.indices.length; k++)
						{
							CellSignal cs = cas.signals[k];
							cs.name = getSafeNetName(cas.name + "[" + cas.indices[k] + "]", false);
						}
					} else
					{
						for(int k=cas.low; k<=cas.high; k++)
						{
							CellSignal cs = cas.signals[k-cas.low];
							cs.name = getSafeNetName(cas.name + "[" + k + "]", false);
						}
					}
				}
			}

			Collections.sort(cni.cellAggregateSignals, 
					new SortAggregateNetsByName(isSeparateInputAndOutput(), isCaseSensitive()));
		} else
		{
			// just get safe net names for all cell signals
			for (CellSignal cs : cni.cellSignalsSorted)
			{
				cs.name = getSafeNetName(cs.name, false);
			}
		}

		return cni;
	}

	private void setBusDirectionality(Network [] nets, String [] netNames, CellNetInfo cni)
	{
		boolean upDir = false, downDir = false;
		int last = 0;
		int width = nets.length;
		for(int i=0; i<width; i++)
		{
			Network subNet = nets[i];
			if (subNet == null) continue;
			String netName = netNames[i];
			int index = -1;
			int charPos = 0;
			for(;;)
			{
				charPos = netName.indexOf('[', charPos);
				if (charPos < 0) break;
				charPos++;
				if (!TextUtils.isDigit(netName.charAt(charPos))) continue;
				index = TextUtils.atoi(netName.substring(charPos));
				break;
			}
			if (index < 0) break;
			if (i != 0)
			{
				if (index < last) downDir = true; else
					if (index > last) upDir = true;
			}
			last = index;
		}
		if (upDir && downDir) return;
		if (!upDir && !downDir) return;

		// valid direction found: set it on all entries of the bus
		for(int i=0; i<width; i++)
		{
			Network subNet = nets[i];
			CellSignal cs = cni.cellSignals.get(subNet);
			cs.descending = downDir;
		}
	}

	/**
	 * Method to return the character position in network name "name" that is the start of indexing.
	 * If there is no indexing ("clock"), this will point to the end of the string.
	 * If there is simple indexing ("dog[12]"), this will point to the "[".
	 * If the index is non-numeric ("dog[cat]"), this will point to the end of the string.
	 * If there are multiple indices, ("dog[12][45]") this will point to the last "[" (unless it is non-numeric).
	 */
	protected static String unIndexedName(String name)
	{
		int len = name.length();
		if (len == 0) return name;
		if (name.charAt(len-1) != ']') return name;
		int i = len - 2;
		for( ; i > 0; i--)
		{
			char theChr = name.charAt(i);
			if (theChr == '[') break;
			if (theChr == ':' || theChr == ',') continue;
			if (!TextUtils.isDigit(theChr)) break;
		}
		if (name.charAt(i) != '[') return name;
		return name.substring(0, i);
	}

	private static class SortNetsByName implements Comparator<CellSignal>
	{
		private boolean separateInputAndOutput, aggregateNamesSupported, caseSensitive, descendingAscendingSupported;

		SortNetsByName(boolean separateInputAndOutput, boolean aggregateNamesSupported, boolean caseSensitive,
				boolean descendingAscendingSupported)
		{
			this.separateInputAndOutput = separateInputAndOutput;
			this.aggregateNamesSupported = aggregateNamesSupported;
			this.caseSensitive = caseSensitive;
			this.descendingAscendingSupported = descendingAscendingSupported;
		}

		public int compare(CellSignal cs1, CellSignal cs2)
		{
			if ((aggregateNamesSupported || separateInputAndOutput) && (cs1.pp == null) != (cs2.pp == null))
			{
				// one is exported and the other isn't...sort accordingly
				return cs1.pp == null ? 1 : -1;
			}
			if (cs1.pp != null && cs2.pp != null)
			{
				if (separateInputAndOutput)
				{
					// both are exported: sort by characteristics (if different)
					PortCharacteristic ch1 = cs1.pp.getCharacteristic();
					PortCharacteristic ch2 = cs2.pp.getCharacteristic();
					if (ch1 != ch2) return ch1.getOrder() - ch2.getOrder();
				}
			}
			if (aggregateNamesSupported && cs1.descending != cs2.descending)
			{
				// one is descending and the other isn't...sort accordingly
				return cs1.descending ? 1 : -1;
			}
			String n1 = cs1.name;
			String n2 = cs2.name;
			if (!caseSensitive)
			{
				n1 = n1.toLowerCase();
				n2 = n2.toLowerCase();
			}
			
			if (aggregateNamesSupported)
				return TextUtils.STRING_NUMBER_ORDER.compare(n1, n2);

			// special case if both go in the same order and have the same base name: use specified ascending/descending order
			if (descendingAscendingSupported && (cs1.descending == cs2.descending))
			{
				int open1 = n1.indexOf('[');
				int open2 = n2.indexOf('[');
				if (open1 > 0 && open2 > 0)
				{
					// two indexed names, see if they are the same
					String part1 = n1.substring(0, open1);
					String part2 = n2.substring(0, open2);
					if (part1.equals(part2))
					{
						int val = TextUtils.STRING_NUMBER_ORDER.compare(n1, n2);
						if (cs1.descending) val = -val;
						return val;
					}
				}
			}

			// Lexicography order
			// Sort by simple string comparison, otherwise it is impossible
			// to stitch this together with netlists from other tools
			return n1.compareTo(n2);
		}
	}

	private static class SortAggregateNetsByName implements Comparator<CellAggregateSignal>
	{
		private boolean separateInputAndOutput, caseSensitive;
		SortAggregateNetsByName(boolean separateInputAndOutput, boolean caseSensitive)
		{
			this.separateInputAndOutput = separateInputAndOutput;
			this.caseSensitive = caseSensitive;
		}

		public int compare(CellAggregateSignal cs1, CellAggregateSignal cs2)
		{
			if ((separateInputAndOutput) && (cs1.pp == null) != (cs2.pp == null))
			{
				// one is exported and the other isn't...sort accordingly
				return cs1.pp == null ? 1 : -1;
			}
			if (cs1.pp != null && cs2.pp != null)
			{
				if (separateInputAndOutput)
				{
					// both are exported: sort by characteristics (if different)
					PortCharacteristic ch1 = cs1.pp.getCharacteristic();
					PortCharacteristic ch2 = cs2.pp.getCharacteristic();
					if (ch1 != ch2) return ch1.getOrder() - ch2.getOrder();
				}
			}
			// Sort by simple string comparison, otherwise it is impossible
			// to stitch this together with netlists from other tools
			return (caseSensitive) ? cs1.name.compareTo(cs2.name) : cs1.name.toLowerCase().compareTo(cs2.name.toLowerCase());
		}
	}

	/**
	 * Method to create a parameterized name for node instance "ni".
	 * If the node is not parameterized, returns zero.
	 * If it returns a name, that name must be deallocated when done.
	 */
	protected String parameterizedName(Nodable no, VarContext context)
	{
		Cell cell = (Cell)no.getProto();
		String uniqueCellName = getUniqueCellName(cell);
		if (canParameterizeNames() && no.isCellInstance())
		{
			// if there are parameters, append them to this name
			Map<Variable.Key,Variable> paramValues = new HashMap<Variable.Key,Variable>();
			for(Iterator<Variable> it = no.getDefinedParameters(); it.hasNext(); )
			{
				Variable var = it.next();
				paramValues.put(var.getKey(), var);
			}
			for(Variable.Key key : paramValues.keySet())
			{
				Variable var = no.getParameter(key);
				String eval = var.describe(context, no.getNodeInst());
				if (eval == null) continue;
				uniqueCellName += "-" + eval.toString();
			}
		}

		// if it is over the length limit, truncate it
		int limit = maxNameLength();
		if (limit > 0 && uniqueCellName.length() > limit)
		{
			int ckSum = 0;
			for(int i=0; i<uniqueCellName.length(); i++)
				ckSum += uniqueCellName.charAt(i);
			ckSum = (ckSum % 9999);
			uniqueCellName = uniqueCellName.substring(0, limit-10) + "-TRUNC"+ckSum;
		}

		// make it safe
		return getSafeCellName(uniqueCellName);
	}

	/**
	 * Method to return the name of cell "c", given that it may be ambiguously used in multiple
	 * libraries.  Also, since this is an icon cell name, do not switch to its schematic.
	 */
	protected String getIconCellName(Cell cell)
	{
		String name = cellNameMap.get(cell);
		return name;
	}

	/**
	 * Method to return the name of cell "c", given that it may be ambiguously used in multiple
	 * libraries.
	 */
	protected String getUniqueCellName(Cell cell)
	{
		Cell contents = cell.contentsView();
		if (contents != null) cell = contents;
		String name = cellNameMap.get(cell);
		if (name == null) name = getDefaultName(cell);
		return name;
	}

	private String getDefaultName(Cell cell)
	{
		if (isLibraryNameAlwaysAddedToCellName() && !SCLibraryGen.isStandardCell(cell))
			return cell.getLibrary().getName() + "__" + cell.getName();
		return cell.getName();
	}

	private class NameMapGenerator extends HierarchyEnumerator.Visitor {

		private Map<Cell,String> cellNameMap;
		private Map<String,List<Cell>> cellNameMapReverse;
		private Topology topology;
		private Cell topCell;

		public NameMapGenerator(Topology topology) {
			cellNameMap = new HashMap<Cell,String>();
			cellNameMapReverse = new HashMap<String,List<Cell>>();
			this.topology = topology;
			topCell = null;
		}

		public boolean enterCell(HierarchyEnumerator.CellInfo info) {
			Cell cell = info.getCell();
			if (topCell == null) topCell = cell;

			if (cellNameMap.containsKey(cell)) return false;
			// add name for this cell
			String name = getDefaultName(cell);
			cellNameMap.put(cell, name);
			getConflictList(name).add(cell);

			// see if there are alternate icons with different names
			if (cell != topCell && topology.isWriteCopyForEachIcon() && cell.isSchematic())
			{
				for(Cell otherCell : cell.getCellsInGroup())
				{
					if (otherCell.getView() != View.ICON) continue;
					if (otherCell.getName().equals(cell.getName())) continue;
					String otherCellName = getDefaultName(otherCell);

					// add name for this cell
					if (!cellNameMap.containsKey(otherCell))
					{
						cellNameMap.put(otherCell, otherCellName);
						getConflictList(otherCellName).add(otherCell);
					}
				}
			}
			return true;
		}

		private List<Cell> getConflictList(String cellname) {
			String properCellName = topology.isCaseSensitive() ? cellname : cellname.toLowerCase();
			List<Cell> conflictList = cellNameMapReverse.get(properCellName);
			if (conflictList == null) {
				conflictList = new ArrayList<Cell>();
				cellNameMapReverse.put(properCellName, conflictList);
			}
			return conflictList;
		}

		public void exitCell(HierarchyEnumerator.CellInfo info) {
			Cell cell = info.getCell();
			if (cell == topCell) {
				// resolve conflicts
				resolveConflicts();
//				if (!alwaysUseLibName)
//					resolveConflicts(1);
//				resolveConflicts(2);
//				resolveConflicts(3);
			}
		}

		public boolean visitNodeInst(Nodable no, HierarchyEnumerator.CellInfo info) {
			if (!no.isCellInstance()) return false;

			VarContext context = info.getContext();

			Cell cell = (Cell)no.getProto();
			Cell schcell = cell.contentsView();
			if (schcell == null) schcell = cell;
			if (cell.isSchematic() && topology.enumerateLayoutView(schcell)) {
				Cell layCell = cell.otherView(View.LAYOUT);
				if (layCell != null) {
					String name = getDefaultName(schcell);
					cellNameMap.put(schcell, name);
					getConflictList(name).add(schcell);
					HierarchyEnumerator.enumerateCell(layCell, context, this);
					return false;
				}
			} else {
				// special case for cells with only icons
				schcell = cell.contentsView();
				if (cell.isIcon() && schcell == null && !cellNameMap.containsKey(cell)) {
					String name = getDefaultName(cell);
					cellNameMap.put(cell, name);
					getConflictList(name).add(cell);
					return false;
				}
			}

			return true;
		}

		private void resolveConflicts()
		{
			List<List<Cell>> conflictLists = new ArrayList<List<Cell>>();
			for (List<Cell> conflictList : cellNameMapReverse.values())
			{
				if (conflictList.size() <= 1) continue; // no conflict
				conflictLists.add(conflictList);
			}
			for (List<Cell> conflictList : conflictLists)
			{
				// see if cells in the same library have a case conflict
				if (!topology.isCaseSensitive())
				{
					for(int i=0; i<conflictList.size()-1; i++)
					{
						Cell cellI = conflictList.get(i);
						String cellIName = cellNameMap.get(cellI);
						for(int j=i+1; j<conflictList.size(); j++)
						{
							Cell cellJ = conflictList.get(j);
							if (cellI.getLibrary() != cellJ.getLibrary()) continue;
							String cellJName = cellNameMap.get(cellJ);
							if (cellIName.equalsIgnoreCase(cellJName) && !cellIName.equals(cellJName))
							{
								// two cells with same name, just case differences
								for(int append=1; append<1000; append++)
								{
									String newTestName = cellJName.toLowerCase() + "_" + append;
									if (cellNameMapReverse.get(newTestName) != null) continue;
									cellNameMap.put(cellJ, newTestName);
									getConflictList(newTestName).add(cellJ);
									conflictList.remove(j);
									j--;
									break;
								}
							}
						}
					}
					if (conflictList.size() <= 1) continue;
				}

				// if not using library names, resolve conflicts by using them
				if (!isLibraryNameAlwaysAddedToCellName())
				{
					for(int i=0; i<conflictList.size(); i++)
					{
						Cell cell = conflictList.get(i);
						String cellName = cellNameMap.get(cell);
						String newTestName = cell.getLibrary().getName() + "__" + cellName;
						if (cellNameMapReverse.get(newTestName) != null) continue;
						cellNameMap.put(cell, newTestName);
						getConflictList(newTestName).add(cell);
						conflictList.remove(i);
						i--;
					}
					if (conflictList.size() <= 1) continue;
				}

				// still conflicts: try adding view abbreviations
				for(int i=0; i<conflictList.size(); i++)
				{
					Cell cell = conflictList.get(i);
					String cellName = cellNameMap.get(cell);
					String newTestName = cell.getLibrary().getName() + "__" + cellName + "__" + cell.getView().getAbbreviation();
					if (cellNameMapReverse.get(newTestName) != null) continue;
					cellNameMap.put(cell, newTestName);
					getConflictList(newTestName).add(cell);
					conflictList.remove(i);
					i--;
				}
				if (conflictList.size() <= 1) continue;

				// must be an error
				Cell cell = conflictList.get(0);
				System.out.print("Error: Unable to make unique cell name for "+cell.describe(false)+
					", it conflicts with:");
				for (int i=1; i<conflictList.size(); i++)
					System.out.print(" "+conflictList.get(i).describe(false));
				System.out.println();
			}
		}
//		private void resolveConflicts(int whichPass) {
//			List<List<Cell>> conflictLists = new ArrayList<List<Cell>>();
//			for (List<Cell> conflictList : cellNameMapReverse.values()) {
//				if (conflictList.size() <= 1) continue; // no conflict
//				conflictLists.add(conflictList);
//			}
//			for (List<Cell> conflictList : conflictLists) {
//				// on pass 1, only cell names. If any conflicts, prepend libname
//				if (whichPass == 1) {
//					// replace name with lib + name
//					for (Cell cell : conflictList) {
//						String name = cell.getLibrary().getName() + "__" + cell.getName();
//						cellNameMap.put(cell, name);
//						getConflictList(name).add(cell);
//					}
//					conflictList.clear();
//				}
//				if (whichPass == 2) {
//					// lib + name conflicts, append view
//					for (Cell cell : conflictList) {
//						String name = cell.getLibrary().getName() + "__" + cell.getName() + "__" + cell.getView().getAbbreviation();
//						cellNameMap.put(cell, name);
//						getConflictList(name).add(cell);
//					}
//					conflictList.clear();
//				}
//				if (whichPass == 3) {
//					// must be an error
//					Cell cell = conflictList.get(0);
//					System.out.print("Error: Unable to make unique cell name for "+cell.describe(false)+
//						", it conflicts with: ");
//					for (int i=1; i<conflictList.size(); i++) {
//						System.out.print(conflictList.get(i).describe(false)+" ");
//					}
//					System.out.println();
//				}
//			}
//		}
	}

	/**
	 * determine whether any cells have name clashes in other libraries
	 */
	private Map<Cell,String> makeCellNameMap(Cell cell)
	{
		NameMapGenerator gen = new NameMapGenerator(this);
		HierarchyEnumerator.enumerateCell(cell, VarContext.globalContext, gen);
		return gen.cellNameMap;
	}

}
