/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: NccNetlist.java
 *
 * 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.ncc.netlist;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

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.HierarchyEnumerator.CellInfo;
import com.sun.electric.database.hierarchy.HierarchyEnumerator.NetNameProxy;
import com.sun.electric.database.hierarchy.HierarchyEnumerator.NodableNameProxy;
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.prototype.PortCharacteristic;
import com.sun.electric.database.prototype.PortProto;
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.Technology;
import com.sun.electric.technology.TransistorSize;
import com.sun.electric.technology.PrimitiveNode.Function;
import com.sun.electric.tool.ncc.NccGlobals;
import com.sun.electric.tool.ncc.basic.NccCellAnnotations;
import com.sun.electric.tool.ncc.basic.NccUtils;
import com.sun.electric.tool.ncc.basic.TransitiveRelation;
import com.sun.electric.tool.ncc.basic.NccCellAnnotations.NamePattern;
import com.sun.electric.tool.ncc.netlist.NccNameProxy.PartNameProxy;
import com.sun.electric.tool.ncc.netlist.NccNameProxy.WireNameProxy;
import com.sun.electric.tool.ncc.processing.HierarchyInfo;
import com.sun.electric.tool.ncc.processing.SubcircuitInfo;
import com.sun.electric.tool.user.ncc.ExportConflict;
import com.sun.electric.tool.user.ncc.NccGuiInfo;
import com.sun.electric.tool.user.ncc.UnrecognizedPart;
import com.sun.electric.tool.Job;

/**
 * NCC's representation of a netlist.
 */
public class NccNetlist {
    public final static Netlist.ShortResistors SHORT_RESISTORS = Netlist.ShortResistors.PARASITIC;
	private final NccGlobals globals;
	private final Cell rootCell;
	private final VarContext rootContext;
	private ArrayList<Wire> wires;
	private ArrayList<Part> parts;
	private ArrayList<Port> ports;
	private boolean exportAssertionFailures;
	private boolean exportGlobalConflicts;
	private boolean badTransistorType;
	private boolean userAbort;

	// ---------------------------- public methods ---------------------------
	/** Build a netlist for Cell root. Mos transistors are represented by
	 * Mos. Instances of lower level Cells that have already been checked
	 * by NCC are represented by SubCircuit. Instances of lower level Cells 
	 * that haven't been checked by NCC are flattened.
	 * @param root the top Cell
	 * @param context the VarContext for the top Cell
	 * @param hierInfo information about what's already been checked by NCC
	 * @param blackBox we're not actually going to check so just build an
	 * empty netlist
	 * @param globals variables shared by all parts of NCC
	 */
	public NccNetlist(Cell root, VarContext context, 
					  HierarchyInfo hierInfo, boolean blackBox, 
					  NccGlobals globals) {
		this.globals = globals;
		rootCell = root; 
		rootContext = context;

		try {
			Visitor v = new Visitor(globals, hierInfo, blackBox, context);
			HierarchyEnumerator.enumerateCell(root.getNetlist(SHORT_RESISTORS), context, v, true);
			wires = v.getWireList();
			parts = v.getPartList();
			ports = v.getPortList();
			exportAssertionFailures = v.exportAssertionFailures();
			badTransistorType = v.badTransistorType();
		} catch (RuntimeException e) {
			if (e instanceof ExportGlobalConflict) {
				exportGlobalConflicts = true;
			} else if (e instanceof UserAbort) {
				userAbort = true;
			} else {
				throw e;
			}
		}
		// if there are net list errors then make net list look empty
		if (cantBuildNetlist())
		{
            wires = new ArrayList<Wire>();
			parts = new ArrayList<Part>();
			ports = new ArrayList<Port>();
		}
	}
	/** @return a list of all the Wires */
	public ArrayList<Wire> getWireArray() {return wires;}
	/** @return a list of all the Parts */
	public ArrayList<Part> getPartArray() {return parts;}
	/** @return a list of all the Ports */
	public ArrayList<Port> getPortArray() {return ports;}
	/** @return true if some error prevents us from building a netlist */
	public boolean cantBuildNetlist() {
		return exportAssertionFailures || exportGlobalConflicts ||
		       badTransistorType;
	}
	/** @return true if user has requested an abort of NCC run */
	public boolean userAbort() {return userAbort;}
	/** @return the root Cell */
	public Cell getRootCell() {return rootCell;}
	/** @return the VarContext of the root Cell */
	public VarContext getRootContext() {return rootContext;}
}

class ExportGlobalConflict extends RuntimeException {
	static final long serialVersionUID = 0;
}
class UserAbort extends RuntimeException {
	static final long serialVersionUID = 0;
}

/** map from netID to NCC Wire */
class Wires {
	private final ArrayList<Wire> wires = new ArrayList<Wire>();
	private final String pathPrefix; 
	private void growIfNeeded(int ndx) {
		while(ndx>wires.size()-1) wires.add(null);
	}
	/** Construct a Wire table. The mergedNetIDs argument specifies
	 * sets of net IDs that should point to the same Wire.
	 * @param mergedNetIDs contains sets of net IDs that must be merged
	 * into the same Wire. */
	public Wires(TransitiveRelation<Integer> mergedNetIDs, CellInfo info, 
	             String pathPrefix) {
		this.pathPrefix = pathPrefix;
		for (Iterator<Set<Integer>> it=mergedNetIDs.getSetsOfRelatives(); it.hasNext();) {
			Set<Integer> relatives = it.next();
			Iterator<Integer> ni = relatives.iterator();
			if (!ni.hasNext()) continue;
			int netId = ni.next().intValue();
			Wire w = get(netId, info);
			while (ni.hasNext()) {
				netId = ni.next().intValue();
				growIfNeeded(netId);
				wires.set(netId, w);
			}
		}
	}
	public Wire get(int netID, CellInfo info) {
		growIfNeeded(netID);
		Wire wire = wires.get(netID);
		if (wire==null) {
			NetNameProxy np = info.getUniqueNetNameProxy(netID, "/");
			WireNameProxy wireNm = new WireNameProxy(np, pathPrefix);
			wire = new Wire(wireNm);
			wires.set(netID, wire);
		}
		return wire;
	}
	// return non-null entries of wires array
	// Eliminate duplicated Wires.
	public ArrayList<Wire> getWireArray() {
		Set<Wire> wireSet = new HashSet<Wire>();
		wireSet.addAll(wires);
		wireSet.remove(null);
		ArrayList<Wire> nonNullWires = new ArrayList<Wire>();
		nonNullWires.addAll(wireSet);
		return nonNullWires;
	}
}

class NccCellInfo extends CellInfo {
	private NccGlobals globals;
	// I'm caching the annotations because otherwise every Cell
	// instance is going to regenerate annotations for it's parent.
	private boolean gotAnnotations = false;
	private NccCellAnnotations annotations;
	
	// ----------------------------- public methods ---------------------------
	public NccCellInfo(NccGlobals globals) {this.globals=globals;}

	public NccCellAnnotations getAnnotations() {
		if (!gotAnnotations) 
			annotations = NccCellAnnotations.getAnnotations(getCell());
		gotAnnotations = true;
		return annotations;
	}
	// Subtle:  Suppose a schematic network has an export on it named "gnd"
	// and suppose it is also attached to a global signal "gnd". Then we may
	// create two "Ports" for the network, one for the global and one for the
	// export. The problem is both these ports have the same name: "gnd". This
	// violates the invariant that no two Ports may have the same name. I 
	// believe it's better to preserve the invariant by discarding the global 
	// "gnd" if there is already an Export named "gnd".
	public Iterator<ExportGlobal> getExportsAndGlobals() {
		HashMap<String,ExportGlobal> nameToExport = new HashMap<String,ExportGlobal>();
		// first collect all exports
		for (Iterator<PortProto> it=getCell().getPorts(); it.hasNext();) {
			Export e = (Export) it.next();
            if (ignoreExport(e.getName(), getAnnotations())) continue;
            int[] expNetIDs = getExportNetIDs(e);
			for (int i=0; i<expNetIDs.length; i++) {
				String nm = e.getNameKey().subname(i).toString();
				if (ignoreExport(nm, getAnnotations())) continue;
				ExportGlobal eg = 
					new ExportGlobal(nm, expNetIDs[i],
						             e.getCharacteristic(), 
									 getNetlist().getNetwork(e,i), e);
				nameToExport.put(nm, eg);
			}
		}
		List<ExportGlobal> expGlob = new ArrayList<ExportGlobal>();
		expGlob.addAll(nameToExport.values());

		// next collect all the globals
		Global.Set globNets = getNetlist().getGlobals();
		for (int i=0; i<globNets.size(); i++) {
			Global g = globNets.get(i);
			String nm = g.getName();
			if (ignoreExport(g.getName(), getAnnotations())) continue;
			Network net = getNetlist().getNetwork(g);
			int netID = getNetID(net);
			PortCharacteristic type = globNets.getCharacteristic(g); 
			ExportGlobal eg = nameToExport.get(nm);
			if (eg!=null) {
				// Name collision between an export and a global signal. 
				// Discard the global.
				if (eg.netID!=netID || eg.type!=type) {
					if (eg.netID!=netID) {
						globals.prln(
							"  Error! Cell: "+getCell().libDescribe()+
							" has both an Export and a global signal "+
							"named: "+nm+" but their networks differ");
						
						// GUI
                        ExportConflict.NetworkConflict conf = 
                            new ExportConflict.NetworkConflict(getCell(), getContext(),
                                    nm, eg.network, net);
                        globals.getNccGuiInfo().addNetworkExportConflict(conf);                        
					}
					if (eg.type!=type) {
						globals.prln(
							"  Error! Cell: "+getCell().libDescribe()+
							" has both an Export and a global signal "+
							"named: "+nm+" but their Characteristics differ");
						// GUI
                        ExportConflict.CharactConflict conf = 
                            new ExportConflict.CharactConflict(getCell(), getContext(),
                                    nm, type.getFullName(), eg.type.getFullName(), eg.getExport());
                        globals.getNccGuiInfo().addCharactExportConflict(conf);
					}
					
					throw new ExportGlobalConflict();
				}
			} else {
				eg = new ExportGlobal(nm, netID, type, net);
				expGlob.add(eg);
			}
		}
		return expGlob.iterator();
	}

    private boolean ignoreExport(String name, NccCellAnnotations ann) {
        if (ann==null) return false;
        for (Iterator<List<NamePattern>> patit=ann.getExportsToIgnore(); patit.hasNext();) {
            List<NamePattern> patterns = patit.next();
            for (NamePattern pat : patterns) {
                if (pat.matches(name)) {
                    return true;
                }
            }
        }
        return false;
    }
}

/** Information from either an Export or a Global signal */
class ExportGlobal {
	private final Export export;
	public final String name;
	public final int netID;
	public final PortCharacteristic type;
	public final Network network;
	/** For Export 
	 * @param export is needed because this ExportGlobal 
	 * might be a piece of bussed Export; for example if this ExportGlobal
	 * is foo[1] but is only a part of the Export foo[1:3]. */
	public ExportGlobal(String nm, int id, PortCharacteristic ty, Network net,
			            Export export) {
		this.export=export;  name=nm;  netID=id;  type=ty;  network=net;
	}
	/** For Global */
	public ExportGlobal(String nm, int id, PortCharacteristic ty, Network net) {
		export=null; name=nm;  netID=id;  type=ty; network=net;
	}
	public boolean isExport() {return export!=null;}
	public Export getExport() {
		Job.error(export==null, "this is a Global, not an Export!");
		return export;
	}
}

class Visitor extends HierarchyEnumerator.Visitor {
	// --------------------------- private data -------------------------------
	private static final boolean debug = false;
	private static final Technology SCHEMATIC = Technology.findTechnology("schematic");
	private static final Function4PortTo3Port fourToThree = new Function4PortTo3Port();

	private final NccGlobals globals;
	/** If I'm building a netlist from a node that isn't at the top of the 
	 * design hierarchy then Part and Wire names all share a common prefix.
	 * This is VERY annoying to the user. Save the path prefix here so I can
	 * remove it whenever I build Parts or Wires. */
	private final String pathPrefix;
	private boolean exportAssertionFailures = false;
	private boolean badPartType = false;
	private int depth = 0;

	/** map from netID to Wire */	 
	private Wires wires;
	/** all Parts in the net list */ 
	private final ArrayList<Part> parts = new ArrayList<Part>();
	/** all ports in the net list */ 
	private final ArrayList<Port> ports = new ArrayList<Port>();
	/** treat these Cells as primitives */
	private final HierarchyInfo hierarchicalCompareInfo;
	/** generate only hierarchical comparison information */
	private final boolean blackBox;
	
	// --------------------------- private methods ----------------------------
	private void error(boolean pred, String msg) {globals.error(pred, msg);}
	private String spaces() {
		StringBuffer sp = new StringBuffer();
		for (int i=0; i<depth; i++)	 sp.append(" ");
		return sp.toString();
	}
	private void addMatchingNetIDs(List<Integer> netIDs, NamePattern pattern, 
	                               NccCellInfo rootInfo) {
		for (Iterator<ExportGlobal> it=rootInfo.getExportsAndGlobals(); it.hasNext();) {
			ExportGlobal eg = it.next();
			if (pattern.matches(eg.name)) netIDs.add(new Integer(eg.netID));			 								
		}
	}
	private void doExportsConnAnnot(TransitiveRelation<Integer> mergedNetIDs,
	                                List<NamePattern> connected, NccCellInfo rootInfo) {
		List<Integer> netIDs = new ArrayList<Integer>();
		for (NamePattern np : connected) {
			addMatchingNetIDs(netIDs, np, rootInfo);
		}
		for (int i=1; i<netIDs.size(); i++) {
			mergedNetIDs.theseAreRelated(netIDs.get(0), netIDs.get(i));
		}
	}
	private void doExportsConnAnnots(TransitiveRelation<Integer> mergedNetIDs,
	                                 NccCellInfo rootInfo) {
		NccCellAnnotations ann = rootInfo.getAnnotations();
		if (ann==null) return;
		for (Iterator<List<NamePattern>> it=ann.getExportsConnected(); it.hasNext();) {
			doExportsConnAnnot(mergedNetIDs, it.next(), rootInfo);					                                     
		}
	}
	private void initWires(NccCellInfo rootInfo) {
		TransitiveRelation<Integer> mergedNetIDs = new TransitiveRelation<Integer>();
		doExportsConnAnnots(mergedNetIDs, rootInfo);
		wires = new Wires(mergedNetIDs, rootInfo, pathPrefix);
	}
	
	private void createPortsFromExports(NccCellInfo rootInfo) {
		boolean oneNamePerPort = globals.getOptions().oneNamePerPort;
		HashSet<Port> portSet = new HashSet<Port>();
		for (Iterator<ExportGlobal> it=rootInfo.getExportsAndGlobals(); it.hasNext();) {
			ExportGlobal eg = it.next();
			Wire wire = wires.get(eg.netID, rootInfo);
			portSet.add(wire.addExport(eg.name, eg.type, oneNamePerPort));
		}
		
		for (Port p : portSet) 
			ports.add(p);
	}

	/** Get the Wire that's attached to port pi. The Nodable must be an
	 * instance of a PrimitiveNode. 
	 * @return the attached Wire. */
	private Wire getWireForPortInst(PortInst pi, CellInfo info) {
		NodeInst ni = pi.getNodeInst();
		error(ni.isCellInstance(), "not PrimitiveNode");
		PortProto pp = pi.getPortProto();
		int[] netIDs = info.getPortNetIDs(ni, pp);
		error(netIDs.length!=1, "Primitive Port connected to bus?");
		return wires.get(netIDs[0], info);
	}
	
	private boolean isSchematicPrimitive(NodeInst ni) {
		return ni.getProto().getTechnology()==SCHEMATIC;
	}
	
	private Function getMosType(NodeInst ni, NccCellInfo info) {
		Function func = ni.getFunction();

		if (isSchematicPrimitive(ni)) {
			// This is a workaround to allow compatibility with the regression
			// suite. The old regressions use "transistorType" declarations. 
			
			// Designer convention:
			// If the Cell containing a schematic transistor primitive has an  
			// NCC declaration "transistorType" then get the type information 
			// from that annotation. Otherwise use the type of the transistor
			// primitive.
			Cell parent = ni.getParent();
			
			NccCellAnnotations ann = NccCellAnnotations.getAnnotations(parent);
			
			String typeNm = ann==null ? null : ann.getTransistorType();
			if (typeNm!=null) {
				func = PrimitiveNameToFunction.nameToFunction(typeNm);
				if (func==null) {
					badPartType = true;
					prln("  Unrecognized transistor type: "+typeNm);
					
					// GUI
					globals.getNccGuiInfo().addUnrecognizedPart(
		                    new UnrecognizedPart(ni.getParent(), info.getContext(), typeNm, ni));
				}
			}
		} /* else {
			// This is a workaround until we update the Technologies to utilize 
			// NodeProto.Function.
			if (func==Function.TRANMOS || func==Function.TRAPMOS) {
				String protoNm = ni.getProto().getName();
				Function funcOverride = PrimitiveNameToFunction.nameToFunction(protoNm);
				if (funcOverride!=null)  func=funcOverride;
			}
		}*/
		
		func = fourToThree.translate(func);
		
		return func;
	}

	private void buildMos(NodeInst ni, NccCellInfo info) {
		NodableNameProxy np = info.getUniqueNodableNameProxy(ni, "/");
		PartNameProxy name = new PartNameProxy(np, pathPrefix); 
		double width=0, length=0, mfactor=1;
		if (globals.getOptions().checkSizes) {
			TransistorSize dim = ni.getTransistorSize(info.getContext());
			width = dim.getDoubleWidth() * dim.getMFactor();
			length = dim.getDoubleLength();
		}
		Wire s = getWireForPortInst(ni.getTransistorSourcePort(), info);
		Wire g = getWireForPortInst(ni.getTransistorGatePort(), info);
		Wire d = getWireForPortInst(ni.getTransistorDrainPort(), info);
		PortInst biasPi = ni.getTransistorBiasPort();
		Wire w = globals.getOptions().checkBody && biasPi!=null ? ( 
			getWireForPortInst(biasPi, info)
		) : (
			null
		);
		Function type = getMosType(ni, info);
		// if unrecognized transistor type then ignore MOS
		if (type==null) return;
		
		Part t = w!=null ? (
			new Mos(type, name, width, length, s, g, d, w)
		) : (
			new Mos(type, name, width, length, s, g, d)
		);
		parts.add(t);
	}
	private void buildBipolar(NodeInst ni, NccCellInfo info) {
		NodableNameProxy np = info.getUniqueNodableNameProxy(ni, "/");
		PartNameProxy name = new PartNameProxy(np, pathPrefix); 
		double area=0;
		if (globals.getOptions().checkSizes) {
			TransistorSize dim = ni.getTransistorSize(info.getContext());
			area = dim!=null ? dim.getDoubleArea() : 0;
		}
		Wire e = getWireForPortInst(ni.getTransistorEmitterPort(), info);
		Wire b = getWireForPortInst(ni.getTransistorBasePort(), info);
		Wire c = getWireForPortInst(ni.getTransistorCollectorPort(), info);
		Function f = ni.getFunction();
		Part t = new Bipolar(f, name, area, e, b, c);
		parts.add(t);								 
	}
	private void buildTransistor(NodeInst ni, NccCellInfo info) {
		Function f = ni.getFunction();
		if (f.isFET()) {
			buildMos(ni, info);
		} else if (f.isBipolar()) {
			buildBipolar(ni, info);
		} else {
			globals.error(true, "Unrecognized primitive transistor: "+f.getShortName());
		}
	}
	private Function getResistorType(NodeInst ni, NccCellInfo info) {
		Function f = ni.getFunction();
		Cell parent = ni.getParent();
		if (isSchematicPrimitive(ni)) {
			// This is a work-around to allow compatibility with older
			// regressions.
			// Designer convention:
			// Cell containing a schematic transistor primitive may have an  
			// NCC declaration "resistorType" with Primitive Node name.
			NccCellAnnotations ann = NccCellAnnotations.getAnnotations(parent);
			String typeNm = ann==null ? null : ann.getResistorType();
			if (typeNm!=null) {
				Function funcOverride = PrimitiveNameToFunction.nameToFunction(typeNm);
				if (funcOverride!=null) {
					f = funcOverride;
				} else {
					badPartType = true;
					prln("  Unrecognized resistor type: "+typeNm);
					
					// GUI
					globals.getNccGuiInfo().addUnrecognizedPart(
		                    new UnrecognizedPart(ni.getParent(), info.getContext(), typeNm, ni));
				}
			}
		} else {
			// This is a work-around until we update Technologies to specify
			// the correct resistor Function.
			if (f==Function.PRESIST || f==Function.WRESIST) {
				String typeNm = ni.getProto().getName();
				if (typeNm!=null) {
					Function funcOverride = PrimitiveNameToFunction.nameToFunction(typeNm);
					if (funcOverride!=null) f = funcOverride;
				}
			}
		}
		return f;
	}
	private Wire[] getResistorWires(NodeInst ni, NccCellInfo info) {
		Wire a = getWireForPortInst(ni.getPortInst(0), info);
		Wire b = getWireForPortInst(ni.getPortInst(1), info);
		return new Wire[] {a, b};
	}
	private double getDoubleVariableValue(String varName, NodeInst ni, VarContext context) {
		Variable var = ni.getParameterOrVariable(varName);
		if (var==null) return 0;
		Object obj = context==null ? var.getObject() : context.evalVar(var, ni);
		return VarContext.objectToDouble(obj, 0);
	}
	private double[] getResistorSize(NodeInst ni, VarContext context) {
		double w=0, l=0;
		if (isSchematicPrimitive(ni)) {
			w = getDoubleVariableValue("ATTR_width", ni, context);
			l = getDoubleVariableValue("ATTR_length", ni, context);
		} else {
			w = ni.getLambdaBaseYSize();
			l = ni.getLambdaBaseXSize();
//			SizeOffset so = ni.getSizeOffset();
//			w = ni.getYSize() - so.getLowYOffset() - so.getHighYOffset();
//			l = ni.getXSize() - so.getLowXOffset() - so.getHighXOffset();
		}
		return new double[] {w, l};
	}
	private void buildResistor(NodeInst ni, NccCellInfo info) {
		NodableNameProxy np = info.getUniqueNodableNameProxy(ni, "/");
		PartNameProxy name = new PartNameProxy(np, pathPrefix); 
		double width=0, length=0;
		if (globals.getOptions().checkSizes) {
			double[] dim = getResistorSize(ni, info.getContext());
			width = dim[0];
			length = dim[1];
		}
		Wire[] wires = getResistorWires(ni, info);
		Function type = getResistorType(ni, info);
		// if unrecognized resistor type then ignore 
		if (type!=null) {
			Part t = new Resistor(type, name, width, length, wires[0], wires[1]);
			parts.add(t);
		}
	}
	private void doPrimitiveNode(NodeInst ni, NccCellInfo info) {
		Function f = ni.getFunction();
		
		
		if (f.isTransistor()) {
			buildTransistor(ni, info);
		} else if (f.isResistor() && f!=Function.RESIST) {
			// We use normal resistors to model parasitic wire resistance.
			// NCC considers them to be "short circuits" and discards them. 
			buildResistor(ni, info);
		}
	}
	
	private void addToPins(Wire[] pins, int pinNdx, Wire w) {
		if (pins[pinNdx]==null) {
			pins[pinNdx] = w;
		} else {
			globals.error(pins[pinNdx]!=w, 
						  "exports that should be connected aren't");
		}
	}
	private void pr(String s) {System.out.print(s);}
	private void prln(String s) {System.out.println(s);}
	private void printExports(HashSet<ExportGlobal> exportNames) {
		pr("{ ");
		for (ExportGlobal eg : exportNames)
			pr(eg.name+" ");
		pr("}");
	}
	private void printExportAssertionFailure(HashMap<Wire,HashSet<ExportGlobal>> wireToExportGlobals,
	                                         NccCellInfo info) {
		String instPath = 
			NccNameProxy.removePrefix(pathPrefix, 
				                      info.getContext().getInstPath("/"));
		String cellName = NccUtils.fullName(info.getCell());
		prln("  Assertion: exportsConnectedByParent in cell: "+
			 cellName+" fails. Instance path is: "+instPath);
		prln("    The exports are connected to "+
		     wireToExportGlobals.size()+" different networks");
		for (Wire w : wireToExportGlobals.keySet()) {
			pr("    On network: "+w.getName()+" are exports: ");
			printExports(wireToExportGlobals.get(w));
			prln("");
		}
		// The GUI should put the following into one box
        VarContext context = info.getContext();
        Cell cell = info.getCell();

        NccGuiInfo cm = globals.getNccGuiInfo();
        Object[][] items = new Object[wireToExportGlobals.keySet().size()][];
        String[][] names = new String[wireToExportGlobals.keySet().size()][];
        int j = 0;
		for (Iterator<Wire> it=wireToExportGlobals.keySet().iterator(); it.hasNext(); j++) {
			HashSet<ExportGlobal> exportGlobals = wireToExportGlobals.get(it.next());
           
            items[j] = new Object[exportGlobals.size()];
            names[j] = new String[exportGlobals.size()];
            int i = 0;
			// The GUI should put the following on one line
			for (Iterator<ExportGlobal> it2=exportGlobals.iterator(); it2.hasNext(); i++) {
				ExportGlobal eg = it2.next();
                names[j][i] = eg.name;
				if (eg.isExport())
					items[j][i] = eg.getExport();
				else
                    items[j][i] = eg.network;
			}
		}
        cm.addExportAssertionFailure(cell,context,items, names);
	}
	private void matchExports(HashMap<Wire,HashSet<ExportGlobal>> wireToExportGlobals, NamePattern pattern,
							  NccCellInfo info) {
		for (Iterator<ExportGlobal> it=info.getExportsAndGlobals(); it.hasNext();) {
			ExportGlobal eg = it.next();
			if (!pattern.matches(eg.name)) continue;
			Wire wire = wires.get(eg.netID, info);
			HashSet<ExportGlobal> exportGlobals = wireToExportGlobals.get(wire);
			if (exportGlobals==null) {
				exportGlobals = new HashSet<ExportGlobal>();
				wireToExportGlobals.put(wire, exportGlobals);
			}
			exportGlobals.add(eg);
		}
	}
	private boolean exportAssertionFailure(List<NamePattern> patterns, NccCellInfo info) {
		// map from Wire to Set of Export Names
		HashMap<Wire,HashSet<ExportGlobal>> wireToExportGlobals = new HashMap<Wire,HashSet<ExportGlobal>>();
		for (NamePattern np : patterns) {
			matchExports(wireToExportGlobals, np, info);
		}
		if (wireToExportGlobals.size()<=1) return false;
		printExportAssertionFailure(wireToExportGlobals, info);
		return true;
	}
	private boolean exportAssertionFailures(NccCellInfo info) {
		NccCellAnnotations ann = info.getAnnotations();
		if (ann==null) return false;
		boolean gotError = false;
		for (Iterator<List<NamePattern>> it=ann.getExportsConnected(); it.hasNext();) 
			gotError |= exportAssertionFailure(it.next(), info);
		return gotError;	                                      	
	}
	
	private void doSubcircuit(SubcircuitInfo subcktInfo, NccCellInfo info) {
		Wire[] pins = new Wire[subcktInfo.numPorts()];

		for (Iterator<ExportGlobal> it=info.getExportsAndGlobals(); it.hasNext();) {
			ExportGlobal eg = it.next();
			Wire wire = wires.get(eg.netID, info);
			int pinNdx = subcktInfo.getPortIndex(eg.name);
			if (pinNdx!=-1)  addToPins(pins, pinNdx, wire);
		}
		for (int i=0; i<pins.length; i++) 
			globals.error(pins[i]==null, "disconnected subcircuit pins!");

		CellInfo parentInfo = info.getParentInfo();
		Nodable parentInst = info.getParentInst();
		NodableNameProxy np = parentInfo.getUniqueNodableNameProxy(parentInst, "/");
		PartNameProxy name = new PartNameProxy(np, pathPrefix); 

		parts.add(new Subcircuit(name, subcktInfo, pins));
	}
	/** Check to see if the parent of the current Cell instance says to
	 * flatten the current Cell instance */
	private boolean parentSaysFlattenMe(NccCellInfo info) {
		if (info.isRootCell()) return false;
		NccCellInfo parentInfo = (NccCellInfo) info.getParentInfo();
		//if (!parentInfo.isRootCell()) return false;
		NccCellAnnotations parentAnn = parentInfo.getAnnotations();
		if (parentAnn==null) return false;
		Nodable no = info.getParentInst();
		String instName = no.getName();
		return parentAnn.flattenInstance(instName);
	}
	
	// --------------------------- public methods -----------------------------
	@Override
	public CellInfo newCellInfo() {
		return new NccCellInfo(globals);
	}
	
	@Override
	public boolean enterCell(CellInfo ci) {
		if (globals.userWantsToAbort()) throw new UserAbort();
		
		NccCellInfo info = (NccCellInfo) ci;
		if (debug) {
			globals.status2(spaces()+"Enter cell: " + info.getCell().getName());
			depth++;
		}
		if (info.isRootCell()) {
			initWires(info);
			createPortsFromExports(info);
			// "black box" means only use Export information. Ignore contents.
			if (blackBox) return false;
		} else {
			// We need to test exportsConnectedByParent assertions here
			// because if assertions fail then doSubcircuit will attempt
			// to connect multiple wires to the same port.
			boolean exportAssertFail = exportAssertionFailures(info);
			exportAssertionFailures |= exportAssertFail;
			// Subtle! Suppose mux21{sch}, mux21{lay}, and mux21_r{lay} are in 
			// the same compareList. Then we will first compare 
			// mux21{sch} with mux21{lay}, and then with mux21_r{lay}. For the 
			// second comparison hierarchicalCompareInfo will tell us to treat 
			// mux21{sch} as a primitive. Ignore it. We NEVER want to treat
			// the root Cell as a primitive.
			Cell cell = info.getCell();
			if (!parentSaysFlattenMe(info) &&
//			    hierarchicalCompareInfo!=null && 
				hierarchicalCompareInfo.treatAsPrimitive(cell) &&
				!exportAssertFail) {
				SubcircuitInfo subcktInfo = 
					hierarchicalCompareInfo.getSubcircuitInfo(cell);
				doSubcircuit(subcktInfo, info);
				return false;			
			} 
		}
		return true;
	}
	@Override
	public void exitCell(CellInfo info) {
		if (debug) {
			depth--;
			globals.status2(spaces()+"Exit cell: " + info.getCell().getName());
		}
	}
	@Override
	public boolean visitNodeInst(Nodable no, CellInfo ci) {
		NccCellInfo info = (NccCellInfo) ci;
		if (!no.isCellInstance()) {
			doPrimitiveNode((NodeInst)no, info);
			return false; 
		} else {
			error(!no.isCellInstance(), "expecting Cell");
//			boolean paralleled = info.isDiscardable(no);
//			return !paralleled;
			return true;
		}
	}
	// ---------------------- intended public interface -----------------------
	/** @return a list of Wires */
	public ArrayList<Wire> getWireList() {return wires.getWireArray();}
	/** @return a list of Parts */
	public ArrayList<Part> getPartList() {return parts;}
	/** @return a list of Ports */
	public ArrayList<Port> getPortList() {return ports;}
	/** Ensure that all subcircuits we instantiate have valid exportsConnectedByParent assertions.
	 * If not then this netlist isn't valid. */
	public boolean exportAssertionFailures() {return exportAssertionFailures;}
	/** @return true if we found a transistor type we don't recognize */
	public boolean badTransistorType() {return badPartType;}
	
	public Visitor(NccGlobals globals, 
	               HierarchyInfo hierarchicalCompareInfo,
	               boolean blackBox,
	               VarContext context) {
		this.globals = globals;
		this.hierarchicalCompareInfo = hierarchicalCompareInfo;
		this.blackBox = blackBox;
		this.pathPrefix = context.getInstPath("/");
	}
}

