/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: ScanChainXML.java
 *
 * Copyright (c) 2005, Static Free Software. All rights reserved.
 *
 * Electric(tm) is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.sun.electric.tool.io.output;

import com.sun.electric.database.hierarchy.*;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.database.topology.PortInst;
import com.sun.electric.database.topology.ArcInst;
import com.sun.electric.database.topology.Connection;
import com.sun.electric.database.network.Netlist;
import com.sun.electric.database.network.Network;
import com.sun.electric.database.prototype.NodeProto;
import com.sun.electric.database.prototype.PortProto;
import com.sun.electric.database.text.Name;

import java.util.*;
import java.io.*;

/**
 * Class to define XML for scan chains.
 */
public class ScanChainXML {

    private static final boolean DEBUG = false;
    private static final boolean FLAT = false;
    private static final boolean REDUCE = true;
    
    private static Netlist.ShortResistors SHORT_RESISTORS = Netlist.ShortResistors.PARASITIC;

    // --------------------------- Scan Chain Primitives -----------------------------

    /** Defines a Scan Chain Element, which contains one bit of scan storage */
    private static class ScanChainElement {
        public final String name;
        public final String access;
        public final String clears;
//        public final String inport;
        public final String outport;
        public final DataNet dataport;
        public final DataNet dataport2;

        private ScanChainElement(String name, String access, String clears, String inport,
                                 String outport, String dataport, String dataport2) {
            this.name = name;
            this.access = access;
            this.clears = clears;
//            this.inport = inport;
            this.outport = outport;
            if (dataport == null || dataport.equals(""))
                this.dataport = null;
            else
                this.dataport = new DataNet(dataport);
            if (dataport2 == null || dataport2.equals(""))
                this.dataport2 = null;
            else
                this.dataport2 = new DataNet(dataport2);
        }
    }

    private static class DataNet {
        public final String net;
        public final String options;        // options including parenthesis

        /**
         * Creates a data net object that describes a net that is written or read
         * to by a scan chain element.
         * @param netName the name of the data net, including any options prepended
         * in parenthesis. R for readable, W for writeable, I for inverted.
         * Ex: net26 or net26(R) or net26(RWI).
         */
        public DataNet(String netName) {
            int i = netName.indexOf('(');
            if (i != -1) {
                net = netName.substring(0, i);
                options = netName.substring(i, netName.length());
            } else {
                net = netName;
                options = "";
            }
        }

        /**
         * Creates a data net object that describes a net that is written or read
         * to by a scan chain element.
         * @param net the net name
         * @param options options describing the network: R for readable, W for
         * writable, I for inverted. Should be encased in parenthesis, such as
         * (RW).
         */
        public DataNet(String net, String options) {
            this.net = net;
            this.options = options;
        }

        public String toString() { return net+options; }
    }

    /** Defines the Jtag controller from which the scan chains start and end */
    private static class JtagController {
        public final String name;
        public final int lengthIR;
        private List<Port> ports;

        protected static class Port {
            public final int opcode;
            public final String soutPort;
            public final String chainName;
            public Port(int opcode, String soutPort, String chainName) {
                this.opcode = opcode;
                this.soutPort = soutPort;
                this.chainName = chainName;
            }
        }

        private JtagController(String name, int lengthIR) {
            this.name = name;
            this.lengthIR = lengthIR;
            ports = new ArrayList<Port>();
        }

        /**
         * Add an opcode for a chain, and the associated scan out port,
         * for example addPort(1, "leaf1[0]")
         * @param opcode the opcode of the chain
         * @param soutPort the associated scan out port
         */
        private void addPort(int opcode, String soutPort, String chainName) {
            Port p = new Port(opcode, soutPort, chainName);
            ports.add(p);
        }
        /** get iterator over JtagContoller.Port list */
        private Iterator<Port> getPorts() { return ports.iterator(); }
    }

    /**
     * Define a pass through cell that passes scan data through it.  Examples
     * are inverters or buffers.
     */
    private static class PassThroughCell {
        public final String cellName;
//        public final String inport;
        public final String outport;

        public PassThroughCell(String cellName, String inport, String outport) {
            this.cellName = cellName;
//            this.inport = inport;
            this.outport = outport;
        }
    }

    // --------------------------------------------------------------------

    private String outputFile;
    private PrintWriter out;
    private File outFile;
    // objects used to parse cell schematics
    private JtagController jtagController;
    private HashMap<String,ScanChainElement> scanChainElements;
    private HashMap<String,PassThroughCell> passThroughCells;
    private HashMap<Cell,Cell> cellsToFlatten;
    private String chipName;
    private Cell jtagCell;
    // list of parsed objects to write to XML
    private Map<String,Entity> entities;                          // list of !ENTITY definitions
    private List<Chain> chains;                            // list of top level chains
    private SubChain endChain;

    // for tracing one subchain
    private List<String> chainStartExports = null;          // list of strings
    private List<String> chainNames = null;                 // list of strings
    private List<ExPort> chainStartExPorts = null;          // list of ExPorts

    // ------------------------ Constructors ------------------------------

    /**
     * Create a new ScanChainXML object that will parse the schematics and
     * write out an XML description
     */
    public ScanChainXML() {
        this.jtagController = null;
        this.scanChainElements = new HashMap<String,ScanChainElement>();
        this.passThroughCells = new HashMap<String,PassThroughCell>();
        this.cellsToFlatten = new HashMap<Cell,Cell>();
        this.chipName = "?";
        this.jtagCell = null;
        outputFile = null;
        out = new PrintWriter(System.out);
        outFile = null;
        entities = new HashMap<String,Entity>();
        chains = new ArrayList<Chain>();
    }

    // --------------------------- Settings --------------------------------

    /**
     * Specify a scan chain element.  When an instance of this is found, it is will
     * be parsed as one bit in the scan chain.
     * @param name name of the cell to be defined as a scan chain element.
     * @param access the access type: for example, "RW".
     * @param clears the clears type: for example, "L".
     * @param inport the name of input data port, typically "sin".
     * May contain index info, such as "s[1]"
     * @param outport the name of the output data port, typically "sout".
     * May contain index info, such as "ss[1]"
     */
    public void addScanChainElement(String name, String access, String clears, String inport, String outport) {
        ScanChainElement e = new ScanChainElement(name, access, clears, inport, outport, "", "");
        scanChainElements.put(name+"_"+inport, e);
    }

    /**
     * Specify a scan chain element.  When an instance of this is found, it is will
     * be parsed as one bit in the scan chain.
     * @param name name of the cell to be defined as a scan chain element.
     * @param access the access type: for example, "RW".
     * @param clears the clears type: for example, "L".
     * @param inport the name of input data port, typically "sin".
     * May contain index info, such as "s[1]"
     * @param outport the name of the output data port, typically "sout".
     * May contain index info, such as "ss[1]"
     * @param dataport the name of the port the scan data is read from and written to. May include options
     * R, W, or I for (Readable,Writable,Inverted) in parenthesis at the end. Ex: dout(RW)
     * @param dataport2 another port for data like dataport, with the same format.
     */
    public void addScanChainElement(String name, String access, String clears, String inport, String outport, String dataport, String dataport2) {
        ScanChainElement e = new ScanChainElement(name, access, clears, inport, outport, dataport, dataport2);
        scanChainElements.put(name+"_"+inport, e);
    }

    /**
     * Specify a pass through element.  Pass through elements are found in series in
     * the scan chain, but are not scan chain elements themselves.  Examples of this are
     * inverters and buffers that buffer the scan chain data.
     * @param cellName name of the cell to be defined as a pass through element
     * @param inport the name of the input port that passes data through
     * May contain index info, such as "s[1]"
     * @param outport the name of the output port that passes data through
     * May contain index info, such as "ss[1]"
     */
    public void addPassThroughCell(String cellName, String inport, String outport) {
        PassThroughCell p = new PassThroughCell(cellName, inport, outport);
        passThroughCells.put(cellName+"_"+inport, p);
    }

    /**
     * Specify a cell to flatten.  The XML is hierarchical, but sometimes you don't need
     * or want all that hierarchy.  This specifies a cell that will be flattened
     * @param libName the library that contains the cell
     * @param cellName the name of the cell
     */
    public void addCellToFlatten(String libName, String cellName) {
        //System.out.println("Warning: addCellToFlatten() optimization is no longer supported.");
        Library lib = Library.findLibrary(libName);
        if (lib == null) {
            System.out.println("Did not find library "+libName+" for flattening cell "+cellName);
            return;
        }
        Cell cell = lib.findNodeProto(cellName);
        if (cell == null) {
            System.out.println("Did not find cell "+cellName+" to flatten, in library "+libName);
            return;
        }
        cellsToFlatten.put(cell, cell);
    }

    /**
     * Specify the JTAG Controller.  All scan chains are assumed to start, and end, at the
     * JTAG Controller.  This specifies the jtag controller.
     * @param jtagLib the name of the library that holds the jtag controller cell
     * @param jtagCellName the name of the cell that is the jtag controller
     * @param lengthIR the number of instruction register bits in the jtag controller.
     */
    public void setJtagController(String jtagLib, String jtagCellName, int lengthIR) {
        Library lib = Library.findLibrary(jtagLib);
        if (lib == null) {
            System.out.println("Did not find jtag library "+jtagLib);
            return;
        }
        Cell cell = lib.findNodeProto(jtagCellName);
        if (cell == null) {
            System.out.println("Did not find jtag cell "+jtagCellName+" in library "+jtagLib);
            return;
        }
        jtagCell = cell;
        jtagController = new JtagController(jtagCellName, lengthIR);
        //PassThroughCell endCell = new PassThroughCell(jtagCellName, null, null);
        endChain = new SubChain("end:jtagController", -1);
    }

    /**
     * Add a port to the JTAG Controller that serves as a starting point for a scan chain.
     * A JTAG Controller may have several ports that each have a scan chain attached.
     * The JTAG Controller must have already been specified using setJtagController.
     * @param opcode the opcode for this scan chain
     * @param soutPortName the port name that outputs data for the scan chain.
     * May contain index info, such as "leaf1[1]"
     * @param chainName the name given to this scan chain
     */
    public void addJtagPort(int opcode, String soutPortName, String chainName) {
        if (jtagController == null) {
            System.out.println("Can't add port "+soutPortName+" because the jtag controller has not been defined yet");
            return;
        }
        jtagController.addPort(opcode, soutPortName, chainName);
    }

    /**
     * Start tracing a chain from the specified export in the start cell.  This
     * is used to trace a section of the scan chain. This
     * traces only one chain.
     * @param exportName
     * @param chainName
     */
    public void startFromExport(String exportName, String chainName) {
        if (chainStartExports == null) {
            chainStartExports = new ArrayList<String>();
            chainNames = new ArrayList<String>();
            chainStartExPorts = new ArrayList<ExPort>();
        }
        chainStartExports.add(exportName);
        chainNames.add(chainName);
    }

    /**
     * Specify the name of the chip. Only used when writing the chip name to the file.
     * @param name the chip name
     */
    public void setChipName(String name) { chipName = name; }

    /**
     * Set the output file.  This may be an absolute or relative path.  If this
     * option is not specified, the output goes to the Electric console.
     * @param file the name of the file.
     */
    public void setOutput(String file) {
        // try to open outputFile
        outputFile = file;
        try {
            outFile = new File(outputFile);
            out = new PrintWriter(new BufferedWriter(new FileWriter(outFile)));
        } catch (IOException e) {
            System.out.println(e.getMessage() + "\nWriting XML to console");
        }
    }

    /**
     * Start tracing all the scan chains from the any instances of specified
     * jtag controller
     */
    public void start(String libName, String cellName) {
        Library lib = Library.findLibrary(libName);
        if (lib == null) {
            System.out.println("Did not find library "+libName+" for starting chain analysis in cell "+cellName);
            return;
        }
        Cell cell = lib.findNodeProto(cellName);
        if (cell == null) {
            System.out.println("Did not find cell "+cellName+" for starting chain analysis, in library "+libName);
            return;
        }

        Stack<Nodable> startNode = null;
        if (chainStartExports != null) {
            for (int i=0; i<chainStartExports.size(); i++) {
                ExPort startExport = getExPort(cell, chainStartExports.get(i));
                if (startExport == null) {
                    System.out.println("Cannot find export "+chainStartExports.get(i)+" in cell "+cell.describe(false));
                    continue;
                }
                chainStartExPorts.add(startExport);
            }
            if (jtagController == null)
                jtagController = new JtagController("", 8);
        } else {
            if (jtagCell == null) {
                return;
            }
            startNode = findStartNode(cell, new Stack<Nodable>());
            if (startNode == null) {
                System.out.println("Did not find any usages of the jtag controller: "+jtagCell.getName());
                return;
            }
            Nodable no = startNode.lastElement();
            System.out.println("*** Generating chains starting from jtag controller "+no.getParent().describe(false)+" : "+no.getName());
        }
        start(startNode);
        if (outFile != null) {
            System.out.println("Wrote XML file to "+outFile.getAbsolutePath());
        } else {
            System.out.println("Wrote XML file to console");
        }
    }

    // find the start node.  it will be the last nodable in the var context
    private Stack<Nodable> findStartNode(Cell cell, Stack<Nodable> context) {
        Netlist netlist = cell.getNetlist(SHORT_RESISTORS);
        for (Iterator<Nodable> it = netlist.getNodables(); it.hasNext(); ) {
            Nodable no = it.next();
            if (no.getProto().getName().equals(jtagCell.getName())) {
                context.push(no);
                return context;
            }
            if (no.isCellInstance()) {
                // descend
                Cell subCell = (Cell)no.getProto();
                subCell = subCell.contentsView();
                if (subCell == null) subCell = (Cell)no.getProto();

                context.push(no);
                Stack<Nodable> next = findStartNode(subCell, context);
                if (next == null)
                    context.pop();      // get rid of no
                else
                    return next;
            }
        }
        return null;
    }

    /**
     * Start tracing the scan chains. The nodeinst must be the jtag controller
     * @param startNode The context pointing to the jtag controller.
     */
    private void start(Stack<Nodable> startNode) {

        if (startNode != null) {
            // generate a chain for each port
            for (Iterator<JtagController.Port> it = jtagController.getPorts(); it.hasNext(); ) {
                JtagController.Port jtagPort = it.next();
                if (jtagPort.soutPort == null) continue;
                if (DEBUG) System.out.println("Starting chain "+jtagPort.opcode+" from port "+jtagPort.soutPort);

                String chainName = jtagPort.chainName;
                if (chainName == null)
                    chainName = "chain_"+jtagPort.soutPort;
                Chain chain = new Chain(chainName, jtagPort.opcode, -1);
                Stack<Nodable> startNodeCopy = new Stack<Nodable>();
                startNodeCopy.addAll(startNode);
                startChain(chain, startNodeCopy, jtagPort.soutPort);
                // report number of elements found
                int found = chain.numScanElements();
                System.out.println("Info: completed successfully: chain "+chainName+" had "+found+" scan chain elements");
                // make sure chain ended properly
                SubChain last = chain.getLastSubChain();
                if (last != endChain) {
                    System.out.println("Error! Chain "+chainName+" did not end at the jtag controller. Possible error in parsing or schematics.");
                    if (last != null) System.out.println("     Last sub chain is "+last.name+", length="+last.length);
                }
                chains.add(chain);
            }
        } else if (chainStartExPorts != null) {
            for (int i=0; i<chainStartExPorts.size(); i++) {
                ExPort startExport = chainStartExPorts.get(i);
                String name = chainNames.get(i);
                Chain chain = new Chain(name, 0, -1);
                System.out.println("Tracing sub-chain "+name+" from export "+startExport);
                appendChain(chain, getOtherPorts(startExport));
                int found = chain.numScanElements();
                System.out.println("Info: completed successfully: chain "+name+" had "+found+" scan chain elements");
                chains.add(chain);
            }
        } else {
            System.out.println("No starting point, aborting.");
            return;
        }
        // post process
        postProcessEntitiesRemovePassThroughs();
        if (REDUCE)
            postProcessEntitiesPhase1();

        // write header
        out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
        out.println("\n<!--");
        out.println("    Document      : "+outputFile);
        out.println("    Author        : automatically generated by Electric");
        out.println("    Description   : none");
        out.println("-->\n");
        out.println();
        out.println("<!DOCTYPE ChainG SYSTEM \"file:./ChainG.dtd\" [");
        // print all entities
        if (!FLAT) {
            for (Entity ent : entities.values()) {
                ent.writeDefinition(out, new StringBuffer(), cellsToFlatten);
            }
            out.println("]>");
            out.println();
        }

        // print all chains
        StringBuffer indent = new StringBuffer();
        out.println("<ChainG>");
        indent.append("\t");
        out.println(indent+"<system>");
        indent.append("\t");
        out.println(indent+"<chip name=\""+chipName+"\" lengthIR=\""+jtagController.lengthIR+"\">");
        indent.append("\t");
        for (Chain chain : chains) {
            if (chain.numScanElements() == 0) continue;
            chain.write(out, indent, null, cellsToFlatten);
            if (DEBUG) System.out.println("Double Check: chain "+chain.name+" had "+chain.numScanElements()+" scan chain elements");
        }
        indent.setLength(indent.length() - 1);
        out.println(indent+"</chip>");
        indent.setLength(indent.length() - 1);
        out.println(indent+"</system>");
        indent.setLength(indent.length() - 1);
        out.println("</ChainG>");
        out.flush();
    }

    // ------------------------- Elements ----------------------------------------

    private static class SubChainInst {
        private Port inport;
        private Port outport;
        private Nodable no;
        private SubChain content;

        private SubChainInst(Port inport, Port outport, Nodable no, SubChain content) {
            this.inport = inport;
            this.outport = outport;
            this.no = no;
            this.content = content;
        }
//        protected void setInport(Port port) { this.inport = port; }
//        protected Port getInport() { return inport; }
        protected void setOutport(Port port) { this.outport = port; }
        protected Port getOutport() { return outport; }

        public String toString() {
            return "SubChainInst "+no.getName()+"; in: "+inport+", out: "+outport;
        }
        protected SubChain getSubChain() { return content; }
        protected String getName() { return no.getName(); }
    }

    /**
     * A chain is a description of an entire scan chain
     */
    private static class Chain {
        protected String name;
        private int opcode;                           // the opcode to control this chain
        protected int length;                         // not output if 0 or less
        protected String access;                      // not output if null
        protected String clears;                      // not output if null
        protected DataNet dataNet;                    // network data is written to, not output if null
        protected DataNet dataNet2;                   // network dataBar is written to, not output if null
        private List<SubChainInst> subchains;         // list of ScanChainInstances

        private Chain(String name, int opcode, int length, String access, String clears,
                      DataNet dataNet, DataNet dataNet2) {
            this.name = name;
            this.opcode = opcode;
            this.length = length;
            this.access = access;
            this.clears = clears;
            this.dataNet = dataNet;
            this.dataNet2 = dataNet2;
            subchains = new ArrayList<SubChainInst>();
        }
        private Chain(String name, int opcode, int length) {
            this(name, opcode, length, null, null, null, null);
        }

        protected void addSubChainInst(SubChainInst subChain) {
            subchains.add(subChain);
        }
        protected int getSubChainSize() { return subchains.size(); }
        protected SubChainInst getSubChainInst(int i) { return subchains.get(i); }
        protected SubChain getSubChain(int i) {
            SubChainInst inst = subchains.get(i);
            if (inst == null) return null;
            return inst.getSubChain();
        }
        protected Iterator<SubChainInst> getSubChainInsts() { return subchains.iterator(); }
//        protected Iterator<SubChain> getSubChains() {
//            List<SubChain> subs = new ArrayList<SubChain>();
//            for (SubChainInst inst : subchains) {
//                subs.add(inst.getSubChain());
//            }
//            return subs.iterator();
//        }
        protected int getLength() { return length; }
//        protected String getAccess() { return access; }
//        protected String getClears() { return clears; }

        protected void write(PrintWriter out, StringBuffer indent, String instName, Map<Cell,Cell> cellsToFlatten) {
            if (numScanElements() == 0) return;   // nothing to print

            String n = (instName == null) ? name : instName;
            out.print(indent+"<"+getTag()+" name=\""+n+"\"");
            if (opcode > -1) out.print(" opcode=\""+Integer.toBinaryString(opcode)+"\"");
            if (length > 0) out.print(" length=\""+length+"\"");
            if (access != null) out.print(" access=\""+access+"\"");
            if (clears != null) out.print(" clears=\""+clears+"\"");
            if (dataNet != null) out.print(" dataNet=\""+dataNet+"\"");
            if (dataNet2 != null) out.print(" dataNet2=\""+dataNet2+"\"");

            // short hand if no elements
            if (subchains.size() == 0) {
                out.println(" />");
                return;
            }

            out.println(">");
            indent.append("\t");
            for (Iterator<SubChainInst> it = getSubChainInsts(); it.hasNext(); ) {
                SubChainInst inst = it.next();
                SubChain subChain = inst.getSubChain();
                subChain.write(out, indent, inst.getName(), cellsToFlatten);
            }
            indent.setLength(indent.length() - 1);
            out.println(indent+"</"+getTag()+">");
        }

        protected String getTag() { return "chain"; }

        protected int numScanElements() {
            int num = 0;
            if (length > 0) num += length;
            for (SubChainInst inst : subchains) {
                SubChain sub = inst.getSubChain();
                num += sub.numScanElements();
            }
            //System.out.println("Num scan chain elements in "+name+": "+num+" (length="+length+")");
            return num;
        }

        /**
         * Get the last subchain in this chain
         * @return
         */
        protected SubChain getLastSubChain() {
            if (subchains.size() == 0) return null;
            SubChainInst last = subchains.get(subchains.size() - 1);
            return last.getSubChain().getLastSubChain();
        }

        protected void copyTo(Chain dest) {
            dest.name = this.name;
            dest.length = this.length;
            dest.access = this.access;
            dest.clears = this.clears;
            dest.subchains.clear();
            dest.subchains.addAll(this.subchains);
        }

        protected void removePassThroughs() {
            List<SubChainInst> toRemove = new ArrayList<SubChainInst>();
            for (Iterator<SubChainInst> it2 = getSubChainInsts(); it2.hasNext(); ) {
                SubChainInst inst = it2.next();
                SubChain sub = inst.getSubChain();
                if (sub.isPassThrough()) {
                    //System.out.println("Removed pass through cell "+sub.name);
                    toRemove.add(inst);
                }
            }
            for (SubChainInst sci : toRemove) {
                subchains.remove(sci);
            }
        }
//        protected void replaceSubChainInsts(List<SubChainInst> newSubChainInsts) {
//            subchains.clear();
//            subchains.addAll(newSubChainInsts);
//        }
        protected void remove(SubChainInst inst) {
            subchains.remove(inst);
        }
        protected List<SubChainInst> getSubChainsInsts() {
            ArrayList<SubChainInst> copy = new ArrayList<SubChainInst>();
            for (Iterator<SubChainInst> it = getSubChainInsts(); it.hasNext(); ) copy.add(it.next());
            return copy;
        }
        protected void addAllSubChainInsts(int i, List<SubChainInst> list) {
            subchains.addAll(i, list);
        }
    }

    /**
     * A subchain is exactly like a chain, except that it must be part of
     * a chain, and cannot stand alone.
     */
    private static class SubChain extends Chain {
        private boolean passThrough;

        private SubChain(String name, int length, String access, String clears, DataNet dataNet, DataNet dataNet2) {
            super(name, -1, length, access, clears, dataNet, dataNet2);
            passThrough = false;
        }
        private SubChain(String name, int length) {
            super(name, -1, length);
        }
        protected String getTag() { return "subchain"; }
        private void setPassThrough(boolean b) { passThrough = b; }
        private boolean isPassThrough() { return passThrough; }

        /**
         * Get the last subchain in this chain
         * @return
         */
        protected SubChain getLastSubChain() {
            if (getSubChainSize() == 0) {
                return this;
            }
            return super.getLastSubChain();
        }

        public Object clone() {
            SubChain sub = new SubChain(name, length, access, clears, dataNet, dataNet2);
            this.copyTo(sub);
            sub.passThrough = passThrough;
            return sub;
        }
    }

    /**
     * An entity is a SubChain that associate with a cell, and contains
     * the scan chain elements for that cell.  This is unlike a subchain,
     * which may simply be a single scan element, or a group of scan elements.
     * Entities also serve as caching mechanisms, and define the chain for a cell.
     */
    private static class Entity extends SubChain {
        private static final String deftag = "!ENTITY";
        private Cell cell;
        private ExPort inExport;
        private ExPort outExport;

        private Entity(Cell cell) {
            super(cell.getLibrary().getName()+"_"+cell.getName(), -1);
            this.cell = cell;
        }

        private void setInExPort(ExPort port) { this.inExport = port; }
//        private ExPort getInExPort() { return inExport; }
        private void setOutExPort(ExPort port) { this.outExport = port; }
        private ExPort getOutExPort() { return outExport; }

        protected void writeDefinition(PrintWriter out, StringBuffer indent, Map<Cell,Cell> cellsToFlatten) {
            if (FLAT) return;
            // don't write a definition
            if (numScanElements() < 3 || getSubChainSize() == 0) return;

            out.println(indent+"<"+deftag+" "+getKey()+" '");
            indent.append("\t");
            for (Iterator<SubChainInst> it = getSubChainInsts(); it.hasNext(); ) {
                SubChainInst inst = it.next();
                SubChain subChain = inst.getSubChain();
                subChain.write(out, indent, inst.getName(), cellsToFlatten);
            }
            indent.setLength(indent.length() - 1);
            out.println(indent+"'>");
        }
        protected void write(PrintWriter out, StringBuffer indent, String instName, Map<Cell,Cell> cellsToFlatten) {
            if (FLAT) {
                super.write(out, indent, instName, cellsToFlatten);
                return;
            }

            if (numScanElements() < 3 || getSubChainSize() == 0) {
                super.write(out, indent, instName, cellsToFlatten);
            } else {
                //super.write(out, indent);
                out.println(indent+"<subchain name=\""+instName+"\"> &"+getKey()+"; </subchain>");
            }
        }

        private Object getKey() {
            String key = name+"_"+inExport.name.toString();
            key = key.replaceAll("[\\[\\]]", "_");
            return key;
        }

        public Object clone() {
            Entity ent = new Entity(cell);
            this.copyTo(ent);
            ent.inExport = this.inExport;
            ent.outExport = this.outExport;
            return ent;
        }
    }

    /**
     * A Port holds information about a single port, that may be part of a bussed portinst
     */
    private static class Port {
        private PortProto pp;
        private int index;
        private Name name;
        private Nodable no;

        private Port(Name name, Nodable no, PortProto pp, int index) {
            this.name = name;
            this.no = no;
            this.pp = pp;
            this.index = index;
        }
        public String toString() { if (name == null) return null; return no.getName() +":"+name.toString(); }
//        public void print() {
//            System.out.println("  Name: "+name);
//            System.out.println("  No: "+no);
//            System.out.println("  int: "+index);
//            System.out.println("  pp: "+pp);
//        }
    }

    private static class ExPort {
        private Export ex;
        private int index;
        private Name name;

        private ExPort(Name name, Export ex, int index) {
            this.name = name;
            this.ex = ex;
            this.index = index;
        }
        public String toString() { if (name == null) return null; return name.toString(); }
//        public void print() {
//            System.out.println("  Name: "+name);
//            System.out.println("  Ex: "+ex);
//            System.out.println("  int: "+index);
//        }
    }

    // -------------------------------------------------------------------------

    private ScanChainElement getScanChainElement(String name, String sin) {
        ScanChainElement e = scanChainElements.get(name+"_"+sin);
        return e;
    }

    private PassThroughCell getPassThroughCell(String name, String sin) {
        PassThroughCell p = passThroughCells.get(name+"_"+sin);
        return p;
    }

    // -------------------------------------------------------------------------

    // Entry point. Starts building a scan chain
    // takes care of adding extra hierarchy if start of chain is not at top level.
    // other methods are top-down only
    // Hierarchy denoted by Stack, start (jtag controller) node is at the top of the stack (pop'd off first)
    private SubChainInst startChain(Chain chain, Stack<Nodable> startNode, String startPortName) {
        // get top nodable
        if (startNode == null) return null;
        if (startNode.size() == 0) return null;
        Nodable no = startNode.remove(0);
        if (startNode.isEmpty()) {
            // no is the jtagController, start following the chain
            Port startPort = getPort(no, startPortName);
            if (startPort == null) {
                System.out.println("Can't find specified start port "+startPortName+" on jtag controller "+jtagController.name);
                return null;
            }
            if (DEBUG) System.out.println("Found startNode. Starting chain "+chain.name+" from port "+startPortName);
            SubChainInst subChainInst = appendChain(chain, getOtherPorts(startPort));
            if (subChainInst == null) {
                // no chains at this level, make dummy with output so next level up can follow export up
                return new SubChainInst(null, startPort, null, null);
            }
            return subChainInst;
        }
        // need to descend to get to start of chain
        SubChain subChain = new SubChain(no.getName(), 0);
        SubChainInst curInst = new SubChainInst(null, null, no, subChain);
        chain.addSubChainInst(curInst);
        if (DEBUG) System.out.println("Recursing from "+no.getParent().describe(true)+" into "+no.getName()+", looking for startNode");
        // recurse, call will add inst to chain
        SubChainInst inst = startChain(subChain, startNode, startPortName);
        if( inst == null) return null;

        // continue chain, get export at lower level
        Port subOutPort = inst.getOutport();
        if (subOutPort != null) {
            ExPort outEx = getExportedPort(subOutPort);
            // find associated port on nodable at this level for export
            if (outEx != null) {
                Port outport = getPort(no, outEx.name.toString());
                curInst.setOutport(outport);
                // continue chain at this level
                if (DEBUG) System.out.println("Continuing chain in "+no.getParent().describe(true)+" from port "+outport.name);
                inst = appendChain(chain, getOtherPorts(outport));
                if (inst != null)
                    curInst = inst;
            }
        }
        return curInst;
    }


    private SubChainInst getSubChain(Port inport) {
        Nodable no = inport.no;
        if (DEBUG) System.out.println("getSubChain for NodeInst: "+no.getName()+", sin: "+inport);
        NodeProto np = no.getProto();
        SubChainInst inst;

        // check if this is a pass through element
        PassThroughCell p = getPassThroughCell(np.getName(), inport.name.toString());
        if (p != null) {
            // find output port
            Port outport = getPort(no, p.outport);
            // make dummy subchain with no length
            SubChain sub = new SubChain(p.cellName, -1);
            sub.setPassThrough(true);
            inst = new SubChainInst(inport, outport, no, sub);
            if (DEBUG) System.out.println("  ...matched pass through cell "+p.cellName);
            return inst;
        }

        // check if this is a scan chain element
        if (no.isCellInstance()) {
            ScanChainElement e = getScanChainElement(np.getName(), inport.name.toString());
            if (e != null) {
                SubChain sub;
                Port outport;
                if (no.getNodeInst().getNameKey().isBus()) {
                    // conglomerate into one subchain
                    int size = no.getNodeInst().getNameKey().busWidth();
                    sub = new SubChain(no.getNodeInst().getName(), size, e.access, e.clears,
                            e.dataport, e.dataport2);
                    //sub = new SubChain(no.getNodeInst().getName(), size, e.access, e.clears,
                    //        getScanDataNet(no, e.dataport), getScanDataNet(no, e.dataport2));
                    Nodable lastNo = Netlist.getNodableFor(no.getNodeInst(), size-1);
                    outport = getPort(lastNo, e.outport);
                    no = no.getNodeInst();
                } else {
                    sub = new SubChain(no.getName(), 1, e.access, e.clears,
                        e.dataport, e.dataport2);
                    //sub = new SubChain(no.getName(), 1, e.access, e.clears,
                    //    getScanDataNet(no, e.dataport), getScanDataNet(no, e.dataport2));
                    // find output port
                    outport = getPort(no, e.outport);
                }
                inst = new SubChainInst(inport, outport, no, sub);
                if (DEBUG) System.out.println("  ...matched scan chain element "+e.name);
                return inst;
            }

            // check if this is the jtag controller, which signals the end of the chain
            if (jtagCell != null && np.getName().equals(jtagCell.getName())) {
                if (DEBUG) System.out.println("  ...matched end of chain, port "+inport);
                inst = new SubChainInst(inport, null, no, endChain);
                return inst;
            }

            // otherwise, descend into cell and get subchain (entity) for it
            Cell sch = ((Cell)np).contentsView();
            if (sch == null) sch = (Cell)np;
            if (DEBUG) System.out.println("  ...descending into "+sch);
            inst = getSubChain(sch, inport);
            return inst;
        }

        //System.out.println("Error! Scan chain terminated on node "+ni.getName()+" ("+ni.getParent().getName()+")");
        return null;
    }

//    private DataNet getScanDataNet(Nodable no, DataNet definition) {
//        if (definition == null) return null;
//        String name = getNetName(no, definition.net);
//        return new DataNet(name, definition.options);
//    }

    // get the network name connect to port 'portName' on 'no'.  Returns null if none found.
//    private String getNetName(Nodable no, String portName) {
//        String netName = null;
//        PortInst pi = no.getNodeInst().findPortInst(portName);
//        if (pi == null) return null;
//        if (pi.hasConnections()) {
////        if (pi.getConnections().hasNext()) {
//            ArcInst ai = pi.getConnections().next().getArc();
//            // see if there is a bus name
//            Name busName = no.getParent().getNetlist(SHORT_RESISTORS).getBusName(ai);
//            if (busName == null) {
//                netName = no.getParent().getNetlist(SHORT_RESISTORS).getNetwork(ai, 0).getName();
//            } else {
//                netName = busName.toString();
//            }
//        }
//        return netName;
//    }

    /**
     * Create the subchain for the cell
     * @param cell the cell
     * @param inport the scan data in port on the nodeinst for this cell
     * @return a new Entity that contains the scan chain for this cell
     */
    private SubChainInst getSubChain(Cell cell, Port inport) {
        // get the export corresponding to the nodeInst input port
        Nodable no = inport.no;
        Export inputEx = cell.findExport(inport.pp.getNameKey());
        ExPort schInPort = new ExPort(inport.name, inputEx, inport.index);

        // look up entity
        String key = cell.describe(false) + schInPort.name.toString();
        Entity ent = entities.get(key);
        Port outport = null;

        // if not found, create it now
        if (ent == null) {
            // verify that sinport is part of cell
            if (inputEx == null) {
                System.out.println("Error! In "+cell+", scan data input Export "+inport.name+" not found.");
                return null;
            }
            ent = new Entity(cell);
            ent.setInExPort(schInPort);
            entities.put(key, ent);

            List<Port> nextPorts = getOtherPorts(schInPort);
            SubChainInst lastInst = appendChain(ent, nextPorts);
            if (lastInst != null) {
                // last inst is instance within cell, find export in cell on same network
                Port lastOutport = lastInst.getOutport();
                if (lastOutport != null) {
                    ExPort outEx = getExportedPort(lastOutport);
                    if (outEx == null) {
                        System.out.println("Error! In "+cell+", last element \""+lastOutport.no.getName()+"\", output port \""+lastOutport+"\""+
                                " does not connect to another scan element, is not exported from cell, and does not terminate at the JTAG Controller");
                    } else {
                        ent.setOutExPort(outEx);
                        // get output port on nodeinst corresponding to export
                        outport = getPort(no, outEx.name.toString());
                    }
                }
            }
            if (DEBUG) System.out.println("Completed Entity "+ent.name+".  inport: "+schInPort+", outport: "+ent.getOutExPort());
        } else {
            // definition found, just grab output port instance info
            ExPort outEx = ent.getOutExPort();
            if (outEx != null) {
                outport = getPort(no, outEx.name.toString());
            }
        }
        // wrap with instance info
        return new SubChainInst(inport, outport, no, ent);
    }


    /**
     * Append any scan chain elements to the chain.  Returns the number
     * of elements appended
     * @param chain
     * @param ports
     * @return the last instance added to the chain. May be null if nothing added
     */
    private SubChainInst appendChain(Chain chain, List<Port> ports) {
        ArrayList<Chain> possibleChains = new ArrayList<Chain>();
        ArrayList<SubChainInst> chainLastInstances = new ArrayList<SubChainInst>();

        // each port may or may not lead to a chain of scan chain elements
        for (Port p : ports) {
            SubChainInst inst = getSubChain(p);
            if (inst == null) continue;         // possible dead end

            Chain tempChain = new Chain("temp", -1, -1);
            SubChain sub = inst.content;
            if (sub == endChain) {
                tempChain.addSubChainInst(inst);
                // store if found good chain
                if (tempChain.getSubChainSize() > 0) {
                    possibleChains.add(tempChain);
                    chainLastInstances.add(inst);
                }
                continue;          // end of chain
            }
            if (sub != null) {
                if (sub.getLength() > 0 || sub.getSubChainSize() > 0 || sub.isPassThrough()) {
                    // add to chain if has any content, or if it's a pass through cell
                    tempChain.addSubChainInst(inst);
                }
            }
            // Note: Here we don't care if sub is null. Only the instance info is
            // important from this point on to continue following the chain
            // continue along chain
            Port outport = inst.getOutport();
            if (outport == null) continue;
            SubChainInst last = inst;
            if (DEBUG) System.out.println("Setting last to "+last);
            List<Port> nextPorts = getOtherPorts(outport);
            SubChainInst appendLast = appendChain(tempChain, nextPorts);
            if (appendLast != null) {
                if (DEBUG) System.out.println("Replacing last with "+appendLast);
                last = appendLast;
            }
            // store if found good chain
            if (tempChain.getSubChainSize() > 0) {
                possibleChains.add(tempChain);
                chainLastInstances.add(last);
            }
        }
        // check if any chains found, or if too many found
        if (possibleChains.size() == 0) return null;
        if (possibleChains.size() > 1) {
            System.out.print("Error! Found more than one chain branching from port set: ");
            Port p = null;
            for (Iterator<Port> it = ports.iterator(); it.hasNext(); ) {
                p = it.next();
                System.out.print(p.no.getName()+":"+p.name+", ");
            }
            System.out.println("in cell "+p.no.getParent().describe(false));
        }
        // append only chain, return last instance in chain
        Chain temp = possibleChains.get(0);
        for (Iterator<SubChainInst> it = temp.getSubChainInsts(); it.hasNext(); ) {
            SubChainInst inst = it.next();
            chain.addSubChainInst(inst);
        }
        return chainLastInstances.get(0);
    }

    private void postProcessEntitiesRemovePassThroughs() {
        for (Entity ent : entities.values()) {
            ent.removePassThroughs();
        }
    }


    private void postProcessEntitiesPhase1() {

        int reduced = 1;
        while (reduced > 0) {
            reduced = 0;
            /* can't do this anymore with dataNet and dataNet2 attributes
            for (Entity ent : entities.values()) {
                // if lots of subchains with the same clears and access, consolidate them
                if (ent.getSubChainSize() > 1) {
                    SubChainInst consolidated = null;
                    List newList = new ArrayList();
                    // merge subchains with the same access and clears
                    for (Iterator it2 = ent.getSubChainInsts(); it2.hasNext(); ) {
                        SubChainInst inst = it2.next();
                        SubChain sub = inst.getSubChain();
                        if (sub.access == null || sub.clears == null ||
                            sub.length <= 0 || sub.getSubChainSize() > 0) {
                            // can't consolidate
                            consolidated = null;
                            newList.add(inst);
                        } else {
                            // consolidatable
                            if (consolidated != null &&
                                    consolidated.getSubChain().access.equals(sub.access) &&
                                    consolidated.getSubChain().clears.equals(sub.clears) && isMergable(inst)) {
                                // consolidate if they match
                                consolidated.getSubChain().length += sub.length;
                                consolidated.setOutport(inst.getOutport());
                                reduced++;
                            } else {
                                consolidated = new SubChainInst(inst.getInport(), inst.getOutport(), inst.no, (SubChain)inst.getSubChain().clone());
                                newList.add(consolidated);
                            }
                        }
                    }
                    ent.replaceSubChainInsts(newList);
                }
            }
            */
            for (Entity ent : entities.values()) {
                // if only one sub chain that contains no other sub chains, fold into this
                if (ent.length <= 0 && ent.getSubChainSize() == 1) {
                    SubChainInst inst = ent.getSubChainInsts().next();
                    SubChain sub = inst.getSubChain();
                    if (sub.length > 0 && sub.getSubChainSize() == 0 && isFlattenable(inst)) {
                        // fold into parent
                        ent.length = sub.length;
                        ent.access = sub.access;
                        ent.clears = sub.clears;
                        // on first iteration, do not add sub name, because net is not inside sub
                        if (sub.dataNet != null)
                            ent.dataNet = new DataNet("x" + inst.getName() + "." + sub.dataNet.net, sub.dataNet.options);
                        else
                            ent.dataNet = null;
                        if (sub.dataNet2 != null)
                            ent.dataNet2 = new DataNet("x" + inst.getName() + "." + sub.dataNet2.net, sub.dataNet2.options);
                        else
                            ent.dataNet2 = null;
                        ent.remove(inst);
                        reduced++;
                    }
                }
                // flatten any specified sub chain cells
                if (ent.length <= 0) {
                    // see if any sub cells should be flattened
                    for (int i=0; i<ent.getSubChainSize(); i++) {
                        SubChain sub = ent.getSubChain(i);
                        SubChainInst inst = ent.getSubChainInst(i);
                        if (sub instanceof Entity) {
                            Entity subEnt = (Entity)sub;
                            if ((cellsToFlatten.get(subEnt.cell) != null) &&
                                    isFlattenable(inst) && sub.length < 0 && sub.getSubChainSize() > 0) {
                                // merge subchain's subchains into this entity
                                ent.remove(ent.getSubChainInst(i));
                                ent.addAllSubChainInsts(i, sub.getSubChainsInsts());
                                reduced++;
                            }
                        }
                    }
                }
            }
        }
    }

//    private boolean isMergable(SubChainInst inst) {
//        if (!inst.getName().matches(".*?@.*")) return false;
//        return true;
//    }
    private boolean isFlattenable(SubChainInst inst) {
        if (!inst.getName().matches(".*?@.*")) return false;
        return true;
    }

    // -------------------------------------- Port Manipulation ---------------------------------

    private ExPort getExportedPort(Port port) {
        if (port == null) return null;

        Cell cell = port.no.getParent();

        // list of all portinsts on net
        Netlist netlist = cell.getNetlist(SHORT_RESISTORS);
        Network net = netlist.getNetwork(port.no, port.pp, port.index);

        for (Iterator<PortProto> it = cell.getPorts(); it.hasNext(); ) {
            Export ex = (Export)it.next();
            Name name = ex.getNameKey();
            for (int i=0; i<name.busWidth(); i++) {
                if (netlist.getNetwork(ex, i) == net)
                    return new ExPort(name.subname(i), ex, i);
            }
        }
        return null;
    }

    /**
     * Get a list of other Ports on the same network as the ExPort.
     * The returned list does not include the Port corresponding to the ExPort
     * @param inport the ExPort
     * @return a list of Port objects
     */
    private ArrayList<Port> getOtherPorts(ExPort inport) {
        // convert ExPort to Port
        PortInst pi = inport.ex.getOriginalPort();
        NodeInst ni = pi.getNodeInst();
        Netlist netlist = ni.getParent().getNetlist(SHORT_RESISTORS);
        Network net = netlist.getNetwork(inport.ex, inport.index);
        Port port = null;

        PortProto pp = pi.getPortProto();
        if (pp instanceof Export) {
        	Export e = (Export)pp;
            Cell equivCell = e.getParent().getEquivalent();
            PortProto equiv = e.findEquivalent(equivCell);
            if (equiv != null)
                pp = equiv;
        }
        for (Iterator<Nodable> it = netlist.getNodables(); it.hasNext(); ) {
            Nodable no = it.next();
            if (no.getNodeInst() == ni) {
                if (net == netlist.getNetwork(no, pp, inport.index)) {
                    port = new Port(inport.name, no, pp, inport.index);
                    break;
                }
            }
        }
        if (port == null) {
            port = new Port(inport.name, ni, pp, inport.index);
            ArrayList<Port> list = new ArrayList<Port>();
            list.add(port);
            return list;
            //System.out.println("Error: no other ports connected to export "+inport.ex.getName()+" (make sure the export is on an off-page node).");
            //return null;
        }
        return getOtherPorts(port, false);
    }

    /**
     * Get a list of other Ports on the same network as inport.  List
     * does not include inport.
     * @param inport
     * @return a list of Port objects
     */
    private ArrayList<Port> getOtherPorts(Port inport) {
        return getOtherPorts(inport, true);
    }
    /**
     * Get a list of other Ports on the same network as inport.  List
     * does not include inport.
     * @param inport
     * @param ignoreInport true to not include inport in the list
     * @return a list of Port objects
     */
    private ArrayList<Port> getOtherPorts(Port inport, boolean ignoreInport) {
        if (inport == null) return null;
        ArrayList<Port> ports = new ArrayList<Port>();

        Cell cell = inport.no.getParent();
        Netlist netlist = cell.getNetlist(SHORT_RESISTORS);
        Network net = netlist.getNetwork(inport.no, inport.pp, inport.index);

        for (Iterator<Nodable> it = netlist.getNodables(); it.hasNext(); ) {
            Nodable no = it.next();
            for (Iterator<PortProto> it2 = no.getProto().getPorts(); it2.hasNext(); ) {
                PortProto pp = it2.next();

                Name name = pp.getNameKey();
                for (int i=0; i<name.busWidth(); i++) {
                    if (ignoreInport) {
                        if ((no == inport.no) && (pp == inport.pp) && (i == inport.index))
                            continue;
                    }
                    if (netlist.getNetwork(no, pp, i) == net) {
                        // found matching port
                        Name subname = name;
                        if (name.busWidth() > 1) subname = name.subname(i);
                        Port p = new Port(subname, no, pp, i);
                        ports.add(p);
                    }
                }
            }
        }

        if (ports.size() == 0) {
            if (!ignoreInport) {
                ports.add(inport);
                return ports;
            }
            System.out.println("Warning: no other ports connected to port "+inport.name+" on node "+inport.no.getName()+" in cell "+inport.no.getParent().describe(false));
        }
        return ports;
    }

    /**
     * Get a port from a port name on a Nodable
     * @param no the nodable
     * @param portName the port name including bus index, such as foo[1][2]
     * @return a Port, or null if none found
     */
    private Port getPort(Nodable no, String portName) {
        for (Iterator<PortProto> it = no.getProto().getPorts(); it.hasNext(); ) {
            PortProto pp = it.next();
            Name name = pp.getNameKey();
            for (int i=0; i<name.busWidth(); i++) {
                Name subname = name.subname(i);
                if (subname.toString().equals(portName)) {
                    return new Port(subname, no, pp, i);
                }
            }
        }
        System.out.println("Could not find "+portName+" on "+no.getName());
        return null;
    }

    /**
     * Get an export in a cell from a exportName. The export
     * name may include a bus index, such as foo[1][2]
     * @param cell the cell
     * @param exportName the export name
     * @return an ExPort
     */
    private ExPort getExPort(Cell cell, String exportName) {
        for (Iterator<PortProto> it = cell.getPorts(); it.hasNext(); ) {
            Export ex = (Export)it.next();
            Name name = ex.getNameKey();
            for (int i=0; i<name.busWidth(); i++) {
                Name subname = name.subname(i);
                if (subname.toString().equals(exportName)) {
                    return new ExPort(subname, ex, i);
                }
            }
        }
        return null;
    }

}
