/* -*- tab-width: 4 -*-
*
* Electric(tm) VLSI Design System
*
* File: ComparisonsTree.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.user.ncc;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JTree;
import javax.swing.SwingConstants;
import javax.swing.border.Border;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreePath;

import com.sun.electric.tool.ncc.netlist.NetObject;
import com.sun.electric.tool.ncc.processing.LocalPartitionResult;
import com.sun.electric.tool.ncc.result.EquivRecReport;
import com.sun.electric.tool.ncc.result.NetObjReport;
import com.sun.electric.tool.ncc.trees.Circuit;


/**
 * This class implements the mismatch comparin tree displayed in the right pane 
 * of the NCC GUI window. 
 */
class ComparisonsTree extends JTree implements ActionListener, TreeSelectionListener {
    /* --- size restrictions --- */
    public static final int MAX_COMP_NODES = 100;
    public static final int MAX_ZEROS = 100;
    public static final int MAX_CLASSES = 200;
    public static final int MAX_LIST_ELEMENTS = 200;
    public static final int MAX_NAME_LEN = 100;
    
    /* --- GUI variables --- */
    private ComparisonsPane parentPane;
    private DefaultMutableTreeNode root;
    private TreeNode rootTreeNode;
    private WireClassNode wireClassNodes[][];
    protected JPopupMenu popup;
    protected String clipboard;
    private static Border border = BorderFactory.createEmptyBorder();
    private boolean updateInProgress = true;

    /** list of current comparison mismatches */ 
    private NccGuiInfo[] mismatches;
    
    protected ComparisonsTree(ComparisonsPane pane, DefaultMutableTreeNode root) {
        super(root);
        this.root = root;
        rootTreeNode = (TreeNode)root.getUserObject();
        parentPane = pane;
        setMinimumSize(new Dimension(0,0));
        setShowsRootHandles(true);
        addMouseListener(new TreeMouseAdapter());
        addTreeSelectionListener(this);
        createPopup();
        setCellRenderer(new MyRenderer());
    }
    
    /**
     * Create a right-click popup for tree nodes
     */
    private void createPopup() {
        popup = new JPopupMenu();
        JMenuItem menuItem = new JMenuItem("Copy Node Title To Clipboard");
        menuItem.addActionListener(this);
        popup.add(menuItem);
    }
    
    /**
     * Update the tree with the provided list of comparison mismatches
     * @param misms  list of comparison mismatches
     */
    protected void update(NccGuiInfo[] misms) {
        updateInProgress = true;
        mismatches = misms;
        wireClassNodes = new WireClassNode[misms.length][];   
        root.removeAllChildren();
        DefaultMutableTreeNode compNode;
        EquivRecReport[] mismEqRecs;
        // for each comparison in the list
        for (int compNdx = 0; compNdx < mismatches.length 
                           && compNdx < MAX_COMP_NODES; compNdx++) {
            NccGuiInfo cm = mismatches[compNdx];
            // compute node name from cell names
            String titles[] = cm.getNames();
            String title0 = titles[0].substring(0,titles[0].length()-5);
            String title1 = titles[1].substring(0,titles[1].length()-5);
            String title;
            if (title1.equals(title0))
                title = title0 + "{sch,lay}";
            else
                title = titles[0] + " & " + titles[1];
            
            // create top-level tree node
            TreeNode compTreeNode = new TreeNode(rootTreeNode, 
                                    title + " [" + cm.getTotalMismatchCount() + "]", 
                                    compNdx, -1, TreeNode.COMP_TITLE);
            compTreeNode.setShortName(title);
            compNode = new DefaultMutableTreeNode(compTreeNode);
            root.add(compNode);
            
            // add exports entry if necessary
            int exportMismCount = cm.getValidExportMismatchCount();
            String exportsTitle = null;
            if (exportMismCount > 0) {
                if (exportMismCount > ExportTable.MAXROWS) {
                    exportsTitle = "Exports [first " + ExportTable.MAXROWS 
                              + " of " + exportMismCount + "]";                
                    exportMismCount = ExportTable.MAXROWS;
                } else if (exportMismCount > 0) {
                    exportsTitle = "Exports [" + exportMismCount + "]";                
                }                
                compNode.add(new DefaultMutableTreeNode(
                             new TreeNode(compTreeNode, exportsTitle, 
                                          compNdx, -1, TreeNode.EXPORTS)));
            }
            
            boolean isHashChecked = cm.isHashFailuresPrinted();

            // collect part/wire equiv records
            int size = cm.getWireRecReports().size() + cm.getPartRecReports().size();
            mismEqRecs = new EquivRecReport[size];
            int i=0;
            for (EquivRecReport r : cm.getPartRecReports()) mismEqRecs[i++] = r;
            for (EquivRecReport r : cm.getWireRecReports()) mismEqRecs[i++] = r;

            parentPane.setMismatchEquivRecs(compNdx, mismEqRecs);
            if (mismEqRecs != null && mismEqRecs.length > 0) {
                // add parts entry
                addPartClasses(compTreeNode, compNdx, compNode, mismEqRecs, isHashChecked);
                // add wires entry
                addWireClasses(compTreeNode, compNdx, compNode, mismEqRecs, isHashChecked);     
            }
            
            
            // add sizes entry, if necessary
            int sizeMismCount = cm.getSizeMismatches().size();
            String sizeTitle = null;
            if (sizeMismCount > SizeMismatchPane.MAXROWS) {
                sizeTitle = "Sizes [first " + SizeMismatchPane.MAXROWS 
                          + " of " + sizeMismCount + "]";                
                sizeMismCount = SizeMismatchPane.MAXROWS;
            } else if (sizeMismCount > 0){
                sizeTitle = "Sizes [" + sizeMismCount + "]";                
            }
            if (sizeMismCount > 0)
                compNode.add(new DefaultMutableTreeNode(
                             new TreeNode(compTreeNode, sizeTitle, 
                                          compNdx, -1, TreeNode.SIZES)));
            
            // add "export assertion failures" entry, if necessary
            int exportAssrtCount = cm.getExportAssertionFailures().size();
            String exportAssrtTitle = null;
            if (exportAssrtCount > ExportTable.MAXROWS) {
                exportAssrtTitle = "Export Assertions [first " + ExportTable.MAXROWS 
                          + " of " + exportAssrtCount + "]";                
                exportAssrtCount = ExportTable.MAXROWS;
            } else if (exportAssrtCount > 0) {
                exportAssrtTitle = "Export Assertions [" + exportAssrtCount + "]";                
            }
            if (exportAssrtCount > 0)
                compNode.add(new DefaultMutableTreeNode(
                             new TreeNode(compTreeNode, exportAssrtTitle, 
                                          compNdx, -1, TreeNode.EXPORT_ASSERTS)));
            
            // add "export network conflicts", if necessary
            int exportNetConflictCount = cm.getNetworkExportConflicts().size();
            String exportNetConfTitle = null;
            if (exportNetConflictCount > ExportTable.MAXROWS) {
                exportNetConfTitle = "Export/Global Network Conflicts [first " + ExportTable.MAXROWS 
                          + " of " + exportNetConflictCount + "]";                
                exportNetConflictCount = ExportTable.MAXROWS;
            } else if (exportNetConflictCount > 0) {
                exportNetConfTitle = "Export/Global Network Conflicts [" + exportNetConflictCount + "]";                
            }
            if (exportNetConflictCount > 0)
                compNode.add(new DefaultMutableTreeNode(
                             new TreeNode(compTreeNode, exportNetConfTitle, 
                                          compNdx, -1, TreeNode.EXPORT_NET_CONF)));
            
            // add "export characteristics conflicts", if necessary
            int exportChrConflictCount = cm.getCharactExportConflicts().size();
            String exportChrConfTitle = null;
            if (exportChrConflictCount > ExportTable.MAXROWS) {
                exportChrConfTitle = "Export/Global Characteristics Conflicts [first " 
                    + ExportTable.MAXROWS + " of " + exportChrConflictCount + "]";                
                exportChrConflictCount = ExportTable.MAXROWS;
            } else if (exportChrConflictCount > 0) {
                exportChrConfTitle = "Export/Global Characteristics Conflicts [" 
                    + exportChrConflictCount + "]";                
            }
            if (exportChrConflictCount > 0)
                compNode.add(new DefaultMutableTreeNode(
                             new TreeNode(compTreeNode, exportChrConfTitle, 
                                          compNdx, -1, TreeNode.EXPORT_CHR_CONF)));
            
            // add "unrecognized Parts", if necessary
            int unrecPartsCount = cm.getUnrecognizedParts().size();
            String unrecPartsTitle = null;
            if (unrecPartsCount > ExportTable.MAXROWS) {
                unrecPartsTitle = "Unrecognized Parts [first " 
                    + ExportTable.MAXROWS + " of " + unrecPartsCount + "]";                
                unrecPartsCount = ExportTable.MAXROWS;
            } else if (unrecPartsCount > 0) {
                unrecPartsTitle = "Unrecognized Parts [" + unrecPartsCount + "]";                
            }
            if (unrecPartsCount > 0)
                compNode.add(new DefaultMutableTreeNode(
                             new TreeNode(compTreeNode, unrecPartsTitle, 
                                          compNdx, -1, TreeNode.UNRECOG_PART)));            
        }
        setRootVisible(true);
        updateUI();      
        expandRow(0);  // expand root
        expandRow(1);  // expand first comparison 
        setRootVisible(false);  // hide root
        addSelectionRow(0); // select first comparison
        requestFocusInWindow();
        updateInProgress = false;
    }    
    
    /**
     * Add Part equiv. classes nodes
     * @param compTreeNode  top-level comparison node
     * @param compNdx  comparison index
     * @param inode  tree node to add to
     * @param mismEqRecs  equiv. classes
     * @param lpRes  results of Local Partitioning 
     * @param isHashChecked  true is hashcodes were checked
     */
    private void addPartClasses(TreeNode compTreeNode, int compNdx, 
                                DefaultMutableTreeNode inode, 
                                EquivRecReport[] mismEqRecs,
                                boolean isHashChecked) {
        DefaultMutableTreeNode parts, eclass;
        // add parts entry title
        TreeNode partsNode = new TreeNode(compTreeNode, "Parts ", 
                                          compNdx, -1, TreeNode.TITLE);
        parts = new DefaultMutableTreeNode(partsNode);
        inode.add(parts);

        // add part equivalence classes
        int type = TreeNode.PART;
        int count=0;
        boolean truncated = false;
        for (int i=0; i<mismEqRecs.length; i++) {
            if (!mismEqRecs[i].hasParts()) continue;
                
            count++;
            // limit output size
            if (count > MAX_CLASSES) { truncated = true; continue;}
            
            List<String> reasons = mismEqRecs[i].getReasons();
            StringBuffer nodeName = new StringBuffer("#"+ count + " [");
            int size = mismEqRecs[i].maxSize();

            if (size > MAX_LIST_ELEMENTS)
                nodeName.append("first " + MAX_LIST_ELEMENTS + " of ");
            nodeName.append(size + "]");
            
            Iterator<String> it = null;
            String reas = null;            
            if (! isHashChecked) {  // don't process reasons for hash code classes
                it = reasons.iterator();
                if (it.hasNext()) {
                    reas = it.next();
                    // fetch part type if it's available
                    int ind = reas.indexOf("type is ");
                    if (ind >= 0) {
                        nodeName.append(": " + reas.substring(ind + 8));
                        reas = null;
                    }
                } 
                if (it.hasNext() || reas != null) {
                    if (reas == null) {
                        reas = it.next();
                        nodeName.append(", ");
                    } else
                        nodeName.append(": ");
                    // fetch number of wires attached if it's available                    
                    if (reas.endsWith("different Wires attached")) {
                        int a = reas.indexOf("has ") + 4;
                        int b = reas.indexOf(" different");
                        nodeName.append(reas.substring(a,b) + " Wires attached");
                        reas = null;
                    }
                }
            }
            
            TreeNode partTreeNode = new TreeNode(compTreeNode, nodeName.toString(), 
                                                 compNdx, i, type);
            partTreeNode.setShortName("Part Class #"+ count);
            eclass = new DefaultMutableTreeNode(partTreeNode);
            parts.add(eclass);
            
            if (! isHashChecked) {  // don't process reasons for hash code classes
                if (reasons.size() == 0) {
                    eclass.add(new DefaultMutableTreeNode(
                               new TreeNode(partTreeNode, "all Parts are indistinguishable", 
                                            compNdx, i, TreeNode.PARTLEAF)));
                } else {
                    if (reas != null)
                        eclass.add(new DefaultMutableTreeNode(
                                new TreeNode(partTreeNode, reas, compNdx, i, TreeNode.PARTLEAF)));
                    while (it.hasNext())
                        eclass.add(new DefaultMutableTreeNode(
                                   new TreeNode(partTreeNode, it.next(), 
                                                compNdx, i, TreeNode.PARTLEAF)));
                }
            }
        }
        if (count == 0)
            inode.remove(parts);  // remove "Parts" node if no Part classes exist
        else {
            StringBuffer buf = new StringBuffer("Parts ");
            if (isHashChecked) buf.append("(hash code) ");
            buf.append("[");
            if (truncated) buf.append("first " + MAX_CLASSES + " of ");
            buf.append((count) + "]");
            partsNode.setFullName(buf.toString());
        }
    }
    
    /**
     * Add Wire equiv. classes nodes
     * @param compTreeNode  top-level comparison node
     * @param compNdx  comparison index
     * @param inode  tree node to add to
     * @param mismEqRecs  equiv. classes
     * @param lpRes  results of Local Partitioning 
     * @param isHashChecked  true is hashcodes were checked
     */    
    private void addWireClasses(TreeNode compTreeNode, int compNdx, 
                                DefaultMutableTreeNode inode, 
                                EquivRecReport[] mismEqRecs,
                                boolean isHashChecked) {
        DefaultMutableTreeNode wires, eclass, node;
        // add wires entry title
        TreeNode wiresNode = new TreeNode(compTreeNode, "Wires ", 
                                          compNdx, -1, TreeNode.TITLE);
        wires = new DefaultMutableTreeNode(wiresNode);
        inode.add(wires);
        
        // add wire equivalence classes
        TreeNode wireTreeNode;
        int type = TreeNode.WIRE;
        int count = 0;
        boolean truncated = false;

        int i=0;
        for (; i<mismEqRecs.length; i++) {
            if (mismEqRecs[i].hasParts()) continue;
            
            count++;
            // limit output size
            if (count > MAX_CLASSES) { truncated = true; continue;}            
            
            wireTreeNode = new TreeNode(compTreeNode, "Wire Class #" + count, 
                                        compNdx, i, type);
            wireTreeNode.setShortName("Wire Class #" + count);
            wireTreeNode.setWireClassNum(count-1);
            
            eclass = new DefaultMutableTreeNode(wireTreeNode);
            wires.add(eclass);
            if (!isHashChecked) {
                String reasons[] = 
                    mismEqRecs[i].getReasons().toArray(new String[0]);
                int j = 0;
                if (reasons.length == 0) {
                    eclass.add(new DefaultMutableTreeNode(
                            new TreeNode(wireTreeNode, "all Wires are indistinguishable", 
                                         compNdx, i, TreeNode.WIRELEAF)));
                } else if (reasons.length > 1 
                        && reasons[0].startsWith("0") && reasons[1].startsWith("0")) {
                    node = new DefaultMutableTreeNode(
                           new TreeNode(wireTreeNode, "0's", compNdx, i, TreeNode.WIRELEAF));
                    eclass.add(node);
                    for (; j<reasons.length && reasons[j].startsWith("0"); j++) {
                        if (j >= MAX_ZEROS) continue; // limit number of zeros displayed
                        int start = reasons[j].indexOf(" of ");
                        String reason;
                        if (start >= 0)
                            reason = reasons[j].substring(start + 4);
                        else
                            reason = reasons[j];
                        node.add(new DefaultMutableTreeNode(
                                 new TreeNode(wireTreeNode, reason, compNdx, i, TreeNode.WIRELEAF)));
                    }
                }
                for (; j<reasons.length; j++)
                    eclass.add(new DefaultMutableTreeNode(
                               new TreeNode(wireTreeNode, reasons[j], compNdx, i, TreeNode.WIRELEAF)));
            }
        }
        if (count == 0)
            inode.remove(wires);
        else {
            StringBuffer buf = new StringBuffer("Wires ");
            if (isHashChecked) buf.append("(hash code) ");
            buf.append("[");
            if (truncated) buf.append("first " + MAX_CLASSES + " of ");
            buf.append((count) + "]");
            wiresNode.setFullName(buf.toString());
            wireClassNodes[compNdx] = new WireClassNode[count];
        }
    } 
    
    /* (non-Javadoc)
     * ActionListener interface (for popup menus)
     */
    public void actionPerformed(ActionEvent e) {
        Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();
        // copy text prepared during popup to clipboard
        StringSelection ss = new StringSelection(clipboard);
        cb.setContents(ss,ss);                    
    }

    /* (non-Javadoc)
     * TreeSelectionListener interface
     */
    public void valueChanged(TreeSelectionEvent e) {
        TreePath[] paths = e.getPaths();
        boolean doAdded = false;
        // one pass for removed, one - for added nodes
        for (int i=0; i<2; i++) {  
            // loop over all nodes whose state has changed
            for (int j=0; j<paths.length; j++) {  
                if (e.isAddedPath(j) != doAdded) continue;
                if (paths[j] == null) {
                    parentPane.treeSelectionChanged(null, doAdded);
                    continue;
                }
                DefaultMutableTreeNode node = (DefaultMutableTreeNode)paths[j].getLastPathComponent();
                // notify Comparison Pane about the change                
                parentPane.treeSelectionChanged((TreeNode)node.getUserObject(), doAdded);
            }
            doAdded = true;
        }
        // update Comparison Pane
        parentPane.updateRightPane();
    }

    private class MyRenderer extends DefaultTreeCellRenderer {
        /* (non-Javadoc)
         * TreeCellRenderer interface
         */
        public Component getTreeCellRendererComponent(JTree tree, Object value,
                boolean selected, boolean expanded, boolean leaf,
                int row, boolean hasFocus) {
            DefaultMutableTreeNode node = (DefaultMutableTreeNode)value;
            TreeNode data = (TreeNode)node.getUserObject();
            int compNdx = data.compNdx;
            int wclass = data.getWireClassNum();
            if (data.type == TreeNode.WIRE && !updateInProgress
                    && compNdx < wireClassNodes.length && wireClassNodes[compNdx] != null
                    && wclass < wireClassNodes[compNdx].length) {
                // wclass is a special index of Wire classes in each comparison
                // Needed because Wire and part classes are mixed together
                if (wireClassNodes[compNdx][wclass] == null)
                    createWireClassNodes(data, node.isLeaf());
                if (selected)
                    wireClassNodes[compNdx][wclass].select();
                else
                    wireClassNodes[compNdx][wclass].deselect();
                if (!node.isLeaf())
                    if (expanded)
                        wireClassNodes[compNdx][wclass].expand();
                    else
                        wireClassNodes[compNdx][wclass].collapse();
                return wireClassNodes[compNdx][wclass].getPanel();
            }
            
            return super.getTreeCellRendererComponent(tree, value, selected, expanded,
                    leaf, row, hasFocus);
        }
    }
    
    /**
     * Create Wire class nodes for the comparison represented by the supplied node 
     * @param data  comparison top-level tree node
     * @param areLeaves  true if Wire class nodes are leaves
     */
    private void createWireClassNodes(TreeNode data, boolean areLeaves) {
        int compNdx = data.compNdx;
        EquivRecReport[] mismEqRecs = parentPane.getMismatchEquivRecs(compNdx);
        int count = 0, len = wireClassNodes[compNdx].length;
        // max width of the first 3 columns in node names, height of a name 
        int maxWidth0 = 0, maxWidth1 = 0, maxWidth2 = 0, height = 0;
        Font font = getFont();
        boolean isHashPrinted = mismatches[compNdx].isHashFailuresPrinted();
        
        int i;
        if (isHashPrinted) 
            i = 0;
        else // in LP wire mismatches are grouped at the end of mismEqRecs
            i = mismatches[compNdx].getPartRecReports().size();
        // for each equiv. class        
        for (; i<mismEqRecs.length; i++) {
            if (isHashPrinted && mismEqRecs[i].hasParts()) 
                continue;
            count++;
            // number of classes might have been limited
            if (count > len) break;

            // list of mismatched NetObjects
            List<List<NetObjReport>> mism = mismEqRecs[i].getNotMatchedNetObjs(); 
            
            JLabel labels[] = new JLabel[4];
            String descr[] = new String[2];

            // get names of the first wires in both cells
            String instDescr = null;
            for (int cell=0; cell < 2; cell++) {
            	if (mism.get(cell).size() == 0) {
            		descr[cell] = "{ }";
            	} else if (mism.get(cell).size() > 0) {
            		instDescr = mism.get(cell).get(0).instanceDescription();
            		descr[cell] =  createFirstWireOverview(instDescr, (mism.get(cell).size() > 1));
            	}
            }
            
            // create count of Wires in the class
            StringBuffer lab3Name = new StringBuffer(24);
            lab3Name.append("[");
            
            int size;
            if (isHashPrinted)
                size = mismEqRecs[i].maxSize();
            else
                size = Math.max(mism.get(0).size(), mism.get(1).size()); 
            if (size > MAX_LIST_ELEMENTS)
                lab3Name.append("first " + MAX_LIST_ELEMENTS + " of ");
            lab3Name.append(size + "]");
            StringBuffer name = new StringBuffer(32);
            
            // class sequence number 
            labels[0] = new JLabel("#" + count + " : ");
            labels[0].setHorizontalAlignment(SwingConstants.RIGHT);
            name.append(labels[0].getText() + " ");
            maxWidth0 = Math.max(labels[0].getPreferredSize().width, maxWidth0);
            
            // name of the first Wire from the first Cell
            labels[1] = new JLabel(descr[0]);
            name.append(descr[0] + " ");
            maxWidth1 = Math.max(labels[1].getPreferredSize().width, maxWidth1);
            
            // name of the first Wire from the second Cell
            labels[2] = new JLabel(descr[1]);
            name.append(descr[1] + " ");
            maxWidth2 = Math.max(labels[2].getPreferredSize().width, maxWidth2);
            
            // max size of classes (count)
            labels[3] = new JLabel(lab3Name.toString());
            name.append(labels[3].getText());

            for (int j=0; j<4; j++) {
                labels[j].setBorder(border);
                labels[j].setFont(font);                
            }

            data.setFullName(name.toString());
            wireClassNodes[compNdx][count-1] = new WireClassNode(labels, areLeaves);
            if (count == 1) height = labels[0].getPreferredSize().height;
        }
        // apply the computed dimensions to all Wire class nodes
        if (count > 0) {
            Dimension dim[] = new Dimension[3];
            dim[0] = new Dimension(maxWidth0     , height);
            dim[1] = new Dimension(maxWidth1 + 10, height);
            dim[2] = new Dimension(maxWidth2 + 10, height);
            for (int j=0; j<count; j++)
                wireClassNodes[compNdx][j].setTextLabelDimension(dim);
        }
    }
    
    private String createFirstWireOverview(String instDescr, boolean hasMore) {
        // get the name of the first Wire in the class
        String descr = parentPane.cleanNetObjectName(instDescr);
        int ind = descr.indexOf(" in Cell: ");
        if (ind > 0) descr = descr.substring(0, ind).trim();
        // limit name length
        if (descr.length() > MAX_NAME_LEN)
            descr = descr.substring(0, MAX_NAME_LEN) + "...";
        // surround name by brackets
        if (hasMore) // if more than one Wire belongs to the class
            descr = "{ " + descr + ",...}";
        else
            descr = "{ " + descr + " }";
        return descr;
    }
    
    /**
     * This mouse adapter is used to get right mouse click events and
     * trigger the popup menu.
     */
    private class TreeMouseAdapter extends MouseAdapter {
        public void mousePressed(MouseEvent e) {
            if (! (e.getButton() == MouseEvent.BUTTON2 
                || e.getButton() == MouseEvent.BUTTON3))
                return;
            JTree aTree =  (JTree)e.getSource();
            if (aTree.getRowForLocation(e.getX(), e.getY()) != -1) {
                TreePath selPath = aTree.getPathForLocation(e.getX(), e.getY());
                DefaultMutableTreeNode node = (DefaultMutableTreeNode)selPath.getLastPathComponent();
                TreeNode data = (TreeNode)node.getUserObject();
                // extract node name to be copied to clipboard
                clipboard = data.toString();
                popup.show(e.getComponent(), e.getX(), e.getY());
            }
        }
    }
    
    /**
     * This class is a container of a tree node data. Each DefaultMutableTreeNode in
     * ComparisonTree has a TreeNode as a data object.  
     */
    static class TreeNode {
        /* --- node types ---*/
        public static final int TITLE = 0;
        public static final int COMP_TITLE = 1;
        public static final int EXPORTS = 2;
        public static final int PART = 3;
        public static final int WIRE = 4;
        public static final int PARTLEAF = 5;
        public static final int WIRELEAF = 6;        
        public static final int SIZES = 7;
        public static final int EXPORT_ASSERTS = 8;
        public static final int EXPORT_NET_CONF = 9;
        public static final int EXPORT_CHR_CONF = 10;
        public static final int UNRECOG_PART = 11;
        
        /** comparison index */  public final int compNdx;
        /** equiv. class     */  public final int eclass;
        /** type             */  public final int type;
        /** parent node      */  private TreeNode parent;
        
        /** full name. Used in tree node names      */ private String fullName;
        /** full name. Used in right pane row names */ private String shortName;

        /** If this node represents a wire class, then wireClassNum is the 
         * index of this class in EquivRecReport array of the corresponding 
         * NccComparisonResult object. Otherwise is -1. */
        private int wireClassNum = -1;
        
        public TreeNode(TreeNode parent, String fullName, 
                        int compNdx, int eclass, int type) {
            this.parent = parent;
            this.fullName = fullName;
            shortName = fullName;
            this.compNdx = compNdx;
            this.eclass = eclass;
            this.type = type;
        }
        
        public void setFullName(String n)   { fullName = n; }
        public void setShortName(String n)  { shortName = n;}
        
        /** Only for nodes with type WIRE */
        public void setWireClassNum(int num) {
            if (type != WIRE) return;
            wireClassNum = num; 
        }
        
        public String   getFullName()     { return fullName;     }
        public String   getShortName()    { return shortName;    }
        public TreeNode getParent()       { return parent;       }
        public int      getWireClassNum() { return wireClassNum; }
        public String   toString()        { return fullName;     }
    }
    
    /**
     * This class encapsulates a Wire class tree node
     * Specifically, it encapsulates the panel with labels used as its name 
     */
    private static class WireClassNode {
        /** top-level panel */ private JPanel treeNodePanel;
        /** text-only panel */ private JPanel textPanel;  
        /** icon label      */ private JLabel iconLabel;
        /** text labels     */ private JLabel textLabels[];
        
        /** true if node is leaf */     private boolean isLeaf;        
        /** true if node is expanded */ private boolean expanded = false;
        /** true if node is selected */ private boolean selected = false;
        
        /** true if shared data was initialized */ 
        private static boolean inited = false;
        
        /* --- shared data --- */
        private static Color selBackgnd, deselBackgnd, selText, deselText;
        private static Icon leafIcon, openIcon, closedIcon;
        private static Border border = BorderFactory.createEmptyBorder();
        
        public WireClassNode(JLabel labels[], boolean leaf) {
            if (!inited) init();
            textLabels = labels;
            isLeaf = leaf;
            
            textPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
            textPanel.setBorder(border);
            textPanel.setBackground(deselBackgnd);
            for (int j=0; j<4; j++) textPanel.add(textLabels[j]);
            
            treeNodePanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
            treeNodePanel.setBorder(border);
            treeNodePanel.setBackground(deselBackgnd);
            if (isLeaf)
                iconLabel = new JLabel(leafIcon);
            else
                iconLabel = new JLabel(closedIcon);
            iconLabel.setBorder(border);
            iconLabel.setText(" ");
            treeNodePanel.add(iconLabel);
            treeNodePanel.add(textPanel);
        }
        
        /**
         * Get colors and icons from default renderer
         * @param renderer  instance of the default tree node renderer 
         */
        private static void init() {
            DefaultTreeCellRenderer defCellRenderer = new DefaultTreeCellRenderer();
            selBackgnd = defCellRenderer.getBackgroundSelectionColor();
            deselBackgnd = defCellRenderer.getBackgroundNonSelectionColor();
            selText = defCellRenderer.getTextSelectionColor();
            deselText = defCellRenderer.getTextNonSelectionColor();
            leafIcon = defCellRenderer.getDefaultLeafIcon();
            openIcon = defCellRenderer.getDefaultOpenIcon();
            closedIcon = defCellRenderer.getDefaultClosedIcon();
            inited = true;
        }
        
        public void select()   {
            if (selected) return;
            textPanel.setBackground(selBackgnd);
            for (int i=0; i<4; i++) textLabels[i].setForeground(selText);
            selected = true;
        }
        public void deselect() {
            if (!selected) return;
            textPanel.setBackground(deselBackgnd);
            for (int i=0; i<4; i++) textLabels[i].setForeground(deselText);
            selected = false;
        }
        public void expand()   {
            if (isLeaf || expanded) return;
            iconLabel.setIcon(openIcon);
            expanded = true;
        }
        public void collapse() {
            if (isLeaf || !expanded) return;
            iconLabel.setIcon(closedIcon);
            expanded = false;
        }

        public void setTextLabelDimension(Dimension[] dim) {
            for (int j=0; j<dim.length; j++) {
                textLabels[j].setMinimumSize(dim[j]);
                textLabels[j].setPreferredSize(dim[j]);
            }
        }
        
        /**
         * Get top-level panel with all labels and icon
         * @return  the top-level panel to be rendered to a tree node
         */
        public JPanel getPanel() { return treeNodePanel; }
    }
}
