/* -*- tab-width: 4 -*-
*
* Electric(tm) VLSI Design System
*
* File: ComparisonsPane.java
*
* Copyright (c) 2003, 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.user.ncc;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
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.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import javax.swing.BorderFactory;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.border.Border;
import javax.swing.tree.DefaultMutableTreeNode;

import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.text.CellName;
import com.sun.electric.database.variable.VarContext;
import com.sun.electric.tool.ncc.result.EquivRecReport;
import com.sun.electric.tool.ncc.result.NccResult;
import com.sun.electric.tool.ncc.result.NetObjReport;
import com.sun.electric.tool.ncc.result.PartReport;
import com.sun.electric.tool.ncc.result.WireReport;
import com.sun.electric.tool.user.Highlighter;
import com.sun.electric.tool.user.ncc.ComparisonsTree.TreeNode;

/**
 * This class implements the right side of the NCC GUI window.
 * It is a placeholder for tables of different types, such as Exports, 
 * Equivalence Classes, Sizes, etc.
 */
class ComparisonsPane extends JSplitPane implements ActionListener {
    
    /* what is currently displayed */
//    private static final int EMPTY = 0;
//    private static final int COMP_SUMMARY = 1;
//    private static final int EXPORTS = 2;
//    private static final int PARTS_WIRES = 3;
//    private static final int SIZES = 4;
//    private static final int EXPORT_ASSERTS = 5;
//    private static final int EXPORT_NET_CONF = 6;
//    private static final int EXPORT_CHR_CONF = 7;
//    private static final int UNRECOG_PART = 8;
    
    /** max number of concurrent equiv. classes */ 
    private static final int MAX_CONCUR_EQ_RECS = 5;
    
    private static final String emptyStr = " ";
    private static final String LSEP = System.getProperty("line.separator");
    
    /* --- GUI variables --- */
    protected String defaultTitles[] = {emptyStr, emptyStr};
    private String treeTitle = "  Mismatched Comparisons";
    private JLabel treeLabel = new JLabel(treeTitle);
    private ComparisonsTree tree;
    private JScrollPane treeScrollPane;
//    private int dispOnRight = EMPTY;
    
    private static Border border = BorderFactory.createEmptyBorder();
    
    /* tables corresponding to different tree node types */
    private JScrollPane exportsPanes[];    
    private JScrollPane exportAssertionsPanes[];
    private JScrollPane exportNetConflictPanes[];
    private JScrollPane exportChrConflictPanes[];
    private JScrollPane unrecognizedPartsPanes[];
    private EquivClassSplitPane rightSplPanes[];
    private JPanel sizesPanes[];
    
    /** Right-click popup for a tree node */
    protected JPopupMenu treePopup;
    
    /** Right-click popup for a table cell */
    protected JPopupMenu cellPopup;
    protected String clipboard;
    
    /* --- Data holders --- */
    /** Current list of mismatched comparisons */
    private NccGuiInfo mismatches[];
    /** Current list of Wire/Part equiv. classes with mismatches */
    private EquivRecReport mismEqRecs[][];
    /** Mismatched NetObjects in current EquivRecords */
    private List<NetObjReport>[] mismNetObjs[][];
    /** Matched NetObjects in current EquivRecords */    
    private List<NetObjReport>[] matchedNetObjs[][];
    /** Vector of currently selected equiv. class TreeNode objects */
    private Vector<TreeNode> curEqRecNodes = new Vector<TreeNode>();
    /** Vector of equiv. class TreeNode objects that should be displayed */
    private Vector<TreeNode> curEqRecNodesToDisplay = new Vector<TreeNode>();
    /** Vector of currently selected exclusive TreeNode objects 
      Exclusive nodes are those requiring exclusive access to the right pane.
      All nodes except for EquivRecord and TITLE are exclusive. */
    private Vector<TreeNode> curExlusiveNodes = new Vector<TreeNode>(); 
    
    public ComparisonsPane() {
        super(JSplitPane.HORIZONTAL_SPLIT);
        setOneTouchExpandable(true);
        setDividerLocation(0.5);
        // after a resize the right half gets more resized
        setResizeWeight(0.2);
        
        createCellPopup();
        rightSplPanes = new EquivClassSplitPane[MAX_CONCUR_EQ_RECS];
        for (int i=0; i<MAX_CONCUR_EQ_RECS; i++)
            rightSplPanes[i] = new EquivClassSplitPane(this, i+1);
        setRightComponent(rightSplPanes[0]);
        
        JPanel leftPanel = new JPanel(new BorderLayout());
        leftPanel.add(treeLabel, BorderLayout.NORTH);
        leftPanel.setBorder(border);
        TreeNode rootNode = new TreeNode(null, treeTitle, 
                                          -1, -1, TreeNode.TITLE);
        tree = new ComparisonsTree(this, new DefaultMutableTreeNode(rootNode));
        treeScrollPane = new JScrollPane(tree);
        leftPanel.add(treeScrollPane, BorderLayout.CENTER);
        setLeftComponent(leftPanel);
    }
    
    /** Set the current set of mismatches to the provided one */
    public void setMismatches(List<NccGuiInfo> misms) {
        mismatches = misms.toArray(new NccGuiInfo[0]);
        // allocate arrays of for tables
        mismEqRecs = new EquivRecReport[mismatches.length][];
        mismNetObjs = new ArrayList[mismatches.length][][];
        matchedNetObjs = new ArrayList[mismatches.length][][];
        exportsPanes = new JScrollPane[mismatches.length];
        exportAssertionsPanes = new JScrollPane[mismatches.length];
        exportNetConflictPanes = new JScrollPane[mismatches.length];
        exportChrConflictPanes = new JScrollPane[mismatches.length];
        unrecognizedPartsPanes = new JScrollPane[mismatches.length];
        sizesPanes = new JPanel[mismatches.length];
        
        // clear lists of selected/displayed TreeNode objects 
        curEqRecNodes.clear();
        curEqRecNodesToDisplay.clear();
        curExlusiveNodes.clear();
        
        // display summary of the first comparison
        int divPos = getDividerLocation();
//        dispOnRight = COMP_SUMMARY;
        displayComparisonSummary(0);
        setDividerLocation(divPos);

        // update tree
        StringBuffer buf = new StringBuffer(treeTitle + " [");
        if (mismatches.length > ComparisonsTree.MAX_COMP_NODES)
            buf.append("first " + ComparisonsTree.MAX_COMP_NODES + " of ");
        buf.append(mismatches.length + "]");
        treeLabel.setText(buf.toString());
        tree.update(mismatches);
        treeScrollPane.getVerticalScrollBar().setValue(0);
        getExportsPane(0);  // preload exports of the first comparison
    }
    
    /** 
     * Set the array of Part/Wire equiv. classes for comparison number compNdx
     * @param compNdx index of the comparison
     * @param equivRecs array of equiv. classes
     */
    protected void setMismatchEquivRecs(int compNdx, EquivRecReport[] equivRecs) {
        mismEqRecs[compNdx] = equivRecs;
        mismNetObjs[compNdx] = new ArrayList[equivRecs.length][];
        matchedNetObjs[compNdx] = new ArrayList[equivRecs.length][];
    }

    /** 
     * Get the array of Part/Wire equiv. classes for comparison number compNdx
     * @param compNdx  index of the comparison
     * @return  array of equiv. classes
     */
    protected EquivRecReport[] getMismatchEquivRecs(int compNdx) {
        return mismEqRecs[compNdx];
    }    
    
    /**
     * Reset the content of the right pane to empty
     */
    private void resetRightPane() {
        // reset right pane view
        int divPos = getDividerLocation();
        for (int j=0; j<2; j++) {
            rightSplPanes[0].setLabelText(0,j,defaultTitles[j]);
            rightSplPanes[0].setCellText(0,j,emptyStr);
        }
        setRightComponent(rightSplPanes[0]);
        setDividerLocation(divPos);
//        dispOnRight = EMPTY;
    }
    
    /**
     * Get the Exports table for comparison number compNdx
     * @param compNdx  comparison index
     * @return Exports table wrapped into a JScrollPane
     */
    private JScrollPane getExportsPane(int compNdx) {
        if (compNdx < 0 || compNdx >= exportsPanes.length) return null;
        if (exportsPanes[compNdx] == null) {
            ExportMismatchTable table = new ExportMismatchTable(mismatches[compNdx]);
            exportsPanes[compNdx] = new JScrollPane(table);
            exportsPanes[compNdx].setBackground(Color.WHITE);
        }
        return exportsPanes[compNdx];
    }

    /**
     * Get the Export Assertions table for comparison number compNdx
     * @param compNdx  comparison index
     * @return Export Assertions table wrapped into a JScrollPane
     */
    private JScrollPane getExportAssertionPane(int compNdx) {
        if (compNdx < 0 || compNdx >= exportAssertionsPanes.length) return null;
        if (exportAssertionsPanes[compNdx] == null) {
            ExportAssertionTable table = new ExportAssertionTable(mismatches[compNdx]);
            exportAssertionsPanes[compNdx] = new JScrollPane(table);
            exportAssertionsPanes[compNdx].setBackground(Color.WHITE);
        }
        return exportAssertionsPanes[compNdx];
    }    

    /**
     * Get the Export/Global Network Conflict table for comparison number compNdx
     * @param compNdx  comparison index
     * @return Export/Global Network Conflict table wrapped into a JScrollPane
     */
    private JScrollPane getExportNetConflictPane(int compNdx) {
        if (compNdx < 0 || compNdx >= exportNetConflictPanes.length) return null;
        if (exportNetConflictPanes[compNdx] == null) {
            ExportConflictTable table = 
                new ExportConflictTable.NetworkTable(mismatches[compNdx]);
            exportNetConflictPanes[compNdx] = new JScrollPane(table);
            exportNetConflictPanes[compNdx].setBackground(Color.WHITE);
        }
        return exportNetConflictPanes[compNdx];
    }    

    /**
     * Get the Export/Global Characteristics Conflict table for comparison number compNdx
     * @param compNdx  comparison index
     * @return Export/Global Characteristics Conflict table wrapped into a JScrollPane
     */
    private JScrollPane getExportChrConflictPane(int compNdx) {
        if (compNdx < 0 || compNdx >= exportChrConflictPanes.length) return null;
        if (exportChrConflictPanes[compNdx] == null) {
            ExportConflictTable table = 
                new ExportConflictTable.CharacteristicsTable(mismatches[compNdx]);
            exportChrConflictPanes[compNdx] = new JScrollPane(table);
            exportChrConflictPanes[compNdx].setBackground(Color.WHITE);
        }
        return exportChrConflictPanes[compNdx];
    }    

    /**
     * Get the Unrecognized Parts table for comparison number compNdx
     * @param compNdx  comparison index
     * @return Unrecognized Parts table wrapped into a JScrollPane
     */
    private JScrollPane getUnrecognizedPartsPane(int compNdx) {
        if (compNdx < 0 || compNdx >= unrecognizedPartsPanes.length) return null;
        if (unrecognizedPartsPanes[compNdx] == null) {
            UnrecognizedPartTable table = new UnrecognizedPartTable(mismatches[compNdx]);
            unrecognizedPartsPanes[compNdx] = new JScrollPane(table);
            unrecognizedPartsPanes[compNdx].setBackground(Color.WHITE);
        }
        return unrecognizedPartsPanes[compNdx];
    }    
    
    /**
     * Get the Sizes table for comparison number compNdx
     * @param compNdx  comparison index
     * @return Sizes table wrapped into a JScrollPane
     */
    private JPanel getSizesPane(int compNdx) {
        if (compNdx < 0 || compNdx >= sizesPanes.length) return null;        
        if (sizesPanes[compNdx] == null)
            sizesPanes[compNdx] = new SizeMismatchPane(mismatches[compNdx]);
        return sizesPanes[compNdx];
    }        
    
    /**
     * Notify Comparisons Pane that the tree selection has changed. The supplied 
     * TreeNode was either added (added is true) or removed (added is false) 
     * from the current selection
     * @param node  a TreeNode whose state has changed
     * @param added  true if the node was added, false otherwise
     */
    public void treeSelectionChanged(TreeNode node, boolean added) {
        if (node == null) return;
        int type = node.type;
        
        if (type == TreeNode.TITLE) return;
        
        if (type == TreeNode.PARTLEAF || type == TreeNode.WIRELEAF) {
            node = node.getParent();
            type = node.type;
        }
        
        if (!added) {  // if node is removed from selection
            if (type == TreeNode.PART || type == TreeNode.WIRE)
                curEqRecNodes.remove(node);
            else
                curExlusiveNodes.remove(node);
        } else {  // if node is added to selection
            if (type == TreeNode.PART || type == TreeNode.WIRE)
                curEqRecNodes.add(node);
            else
                curExlusiveNodes.add(node);
        }
    }
    
    /**
     * Update right pane. Content of the pane depends on the current tree selection
     * and should be updated every time the selection changes.
     */
    public void updateRightPane() {
        int divPos = getDividerLocation();        
        if (curExlusiveNodes.size() > 0) {  // if an exclusive node is selected
            // get the first node (it is selected for the longest time)
            TreeNode exNode = curExlusiveNodes.firstElement();
            int exType = exNode.type;
            switch (exType) {
                case TreeNode.COMP_TITLE: 
//                    dispOnRight = COMP_SUMMARY;
                    displayComparisonSummary(exNode.compNdx);
                    break;                    
                case TreeNode.EXPORTS:
//                    dispOnRight = EXPORTS;
                    setRightComponent(getExportsPane(exNode.compNdx));
                    break;
                case TreeNode.SIZES:
//                    dispOnRight = SIZES;
                    setRightComponent(getSizesPane(exNode.compNdx));
                    break;
                case TreeNode.EXPORT_ASSERTS:
//                    dispOnRight = EXPORT_ASSERTS;
                    setRightComponent(getExportAssertionPane(exNode.compNdx));
                    break;
                case TreeNode.EXPORT_NET_CONF:
//                    dispOnRight = EXPORT_NET_CONF;
                    setRightComponent(getExportNetConflictPane(exNode.compNdx));
                    break;  
                case TreeNode.EXPORT_CHR_CONF:
//                    dispOnRight = EXPORT_CHR_CONF;
                    setRightComponent(getExportChrConflictPane(exNode.compNdx));
                    break;
                case TreeNode.UNRECOG_PART:
//                    dispOnRight = UNRECOG_PART;
                    setRightComponent(getUnrecognizedPartsPane(exNode.compNdx));
                    break;                                        
            }
            setDividerLocation(divPos); // restore divider position
            return;
        } else if (curEqRecNodes.size() == 0) {  // if no equiv. class is selected
            resetRightPane();  // display an empty right pane
            return;            
        }
        
        // the right pane will display one or more equiv. classes
//        dispOnRight = PARTS_WIRES;
        
        // get equiv. classes to be displayed
        curEqRecNodesToDisplay.clear();
        int i = 0;
        for (Iterator<TreeNode> it=curEqRecNodes.iterator(); it.hasNext() && i<MAX_CONCUR_EQ_RECS;) {
            TreeNode eqRecNode = it.next();
            if (curEqRecNodesToDisplay.contains(eqRecNode)) continue; // skip if already displayed
            curEqRecNodesToDisplay.add(eqRecNode);
            i++;
        }
        // get a pane with proper number of rows
        EquivClassSplitPane rightSplPane = rightSplPanes[curEqRecNodesToDisplay.size()-1]; 
        i = 0;
        // fill pane with data row by row 
        for (Iterator<TreeNode> it=curEqRecNodesToDisplay.iterator(); it.hasNext(); i++) {
            TreeNode eqRecNode = it.next();
            String partitionTitle = eqRecNode.getParent().getShortName() + " : "
                                  + eqRecNode.getShortName();
            rightSplPane.setPartitionTitle(i, partitionTitle);
            List<NetObjReport>[] mism = new ArrayList[2];

            EquivRecReport eqRec = mismEqRecs[eqRecNode.compNdx][eqRecNode.eclass];
            mism[0] = eqRec.getNotMatchedNetObjs().get(0);
            mism[1] = eqRec.getNotMatchedNetObjs().get(1);
            mismNetObjs[eqRecNode.compNdx][eqRecNode.eclass] = mism;
            List<NetObjReport>[] matched = new ArrayList[2]; 
            matched[0] = eqRec.getMatchedNetObjs().get(0);
            matched[1] = eqRec.getMatchedNetObjs().get(1);
            matchedNetObjs[eqRecNode.compNdx][eqRecNode.eclass] = matched;

//            if (mismatches[eqRecNode.compNdx].isHashFailuresPrinted())
//                fillHashPartitionResults(eqRecNode, rightSplPane, i);
//            else
                fillLocalPartitionResults(eqRecNode, rightSplPane, i);
        }
        setRightComponent(rightSplPane);
        setDividerLocation(divPos);
        rightSplPane.updateLayout();
    }
    
    /**
     * Fill the provided split cell with parts or wires from compared cells that 
     * belong to the local partitioning equivalence class identified by 
     * the provided tree node.  
     * @param node  tree node
     * @param pane  split pane
     * @param row  row in the split pane (>1 rows if >1 node selected)
     */
    private void fillLocalPartitionResults(TreeNode node, EquivClassSplitPane pane, int row) {
        int swap = 0;
        if (mismatches[node.compNdx].isSwapCells()) swap = 1;
        List<NetObjReport>[] matched = matchedNetObjs[node.compNdx][node.eclass];
        List<NetObjReport>[] mism = mismNetObjs[node.compNdx][node.eclass];
        
        String href = "<a style=\"text-decoration: none\" href=\"";
        StringBuffer html = new StringBuffer(256);
        for (int cell=0; cell<2; cell++) {
            int ndx = (cell + swap)%2;
            StringBuffer curCellText = pane.getCellPlainTextBuffer(row,ndx);
            curCellText.setLength(0);
            html.setLength(0);
            // fonts: Courier, Dialog, Helvetica, TimesRoman, Serif
            html.append("<html><FONT SIZE=3><FONT FACE=\"Helvetica, TimesRoman\">");
            
            // mismatched netobjects (printed in red)
            if (mism[ndx].size() > 0) {
                html.append("<font COLOR=\"red\">");
                for (int k=0; k<mism[ndx].size() && k<ComparisonsTree.MAX_LIST_ELEMENTS; k++) {
                    String descr = cleanNetObjectName(
                               (mism[ndx].get(k)).instanceDescription());
                    
                    html.append(href + (row*100000 + ndx*10000 + k) +"\">"+ descr + "</a>");
                    curCellText.append(descr);
                    html.append("<br>");
                    curCellText.append(LSEP);
                }
                html.append("</font>");
            }
            // if this cell has fewer mismatches than the other one
            // and matches are going to printed in this cell, then add empty lines
            int sizeDiff = mism[(ndx+1)%2].size() - mism[ndx].size();
            if (matched[ndx].size() > 0) while (sizeDiff-- > 0) {
                html.append("<br>");
                curCellText.append(LSEP);
            }
           
            // matched netobjects  (printed in green)
            if (matched[ndx].size() > 0) {
                html.append("<font COLOR=\"green\">");
                for (int k=0; k<matched[ndx].size() && k<ComparisonsTree.MAX_LIST_ELEMENTS; k++) {
                    String descr = cleanNetObjectName(
                               (matched[ndx].get(k)).instanceDescription());
                    
                    html.append(href + (row*100000 + ndx*10000 + (mism[ndx].size()+k)) 
                                + "\">"+ descr + "</a>");
                    curCellText.append(descr);
                    html.append("<br>");
                    curCellText.append(LSEP);
                }
                html.append("</font>");
            }
            int len = mism[ndx].size() + matched[ndx].size();
            // if nothing was printed, then print "none"
            if (len == 0) {
                html.append("<b>none</b>");
                curCellText.append("none");
            }
            
            html.append("</font></html>");
            pane.setCellText(row,ndx,html.toString());
                        
            String title = mismatches[node.compNdx].getNames()[ndx];
            if (node.type == TreeNode.WIRE)
                pane.setLabelText(row,ndx, "  "+ len +" Wire(s) in " + title);
            else
                pane.setLabelText(row,ndx, "  "+ len +" Part(s) in " + title);
        }
    }

//    /**
//     * Fill the provided split cell with parts or wires from compared cells that 
//     * belong to the hashcode equivalence class identified by the provided tree node.  
//     * @param node  tree node
//     * @param pane  split pane
//     * @param row  row in the split pane (>1 rows if >1 node selected)
//     */
//    private void fillHashPartitionResults(TreeNode node, EquivClassSplitPane pane, int row) {
//        int swap = 0;
//        if (mismatches[node.compNdx].isSwapCells()) swap = 1;
//        EquivRecReport eqRec = mismEqRecs[node.compNdx][node.eclass];
//        
//        String href = "<a style=\"text-decoration: none\" href=\"";
//        StringBuffer html = new StringBuffer(256);
//        int cell = 0;
//        for (List<NetObjReport> ckt : eqRec.getNotMatchedNetObjs()) {
//            int ndx = (cell + swap)%2;
//            StringBuffer curCellText = pane.getCellPlainTextBuffer(row,ndx);
//            curCellText.setLength(0);
//            html.setLength(0);
//            // fonts: Courier, Dialog, Helvetica, TimesRoman, Serif
//            html.append("<html><FONT SIZE=3><FONT FACE=\"Helvetica, TimesRoman\">");
//            
//            int len = ckt.size();
//            int k=0;
//            for (NetObjReport r : ckt) {
//            	if (k>=ComparisonsTree.MAX_LIST_ELEMENTS) break;
//                String descr = cleanNetObjectName(r.instanceDescription());
//                
//                html.append(href + (row*100000 + cell*10000 + k) +"\">"+ descr + "</a>");
//                curCellText.append(descr);
//                html.append("<br>");
//                curCellText.append(LSEP);
//            	k++;
//            }
//            if (len == 0) {
//                html.append("<b>none</b>");
//                curCellText.append("none");
//            }
//            
//            html.append("</font></html>");
//            pane.setCellText(row,ndx,html.toString());
//                        
//            String title = mismatches[node.compNdx].getNames()[ndx];
//            if (node.type == TreeNode.WIRE)
//                pane.setLabelText(row,ndx, "  "+ len +" Wire(s) in " + title);
//            else
//                pane.setLabelText(row,ndx, "  "+ len +" Part(s) in " + title);
//        }
//        cell++;
//    }
    
    /**
     * Display number of parts, wires, ports in the cells in comparison
     * number compNdx
     * @param compNdx  comparison index
     */
    private void displayComparisonSummary(int compNdx) {
        EquivClassSplitPane rightSplPane = rightSplPanes[0];
        Cell cells[] = mismatches[compNdx].getCells();
        NccResult.CellSummary summary = mismatches[compNdx].getCellSummary();
        StringBuffer html = new StringBuffer(256);
        int swap = 0;
        if (mismatches[compNdx].isSwapCells()) swap = 1;
        for (int i=0; i<2; i++) {
            int ndx = (i + swap)%2;
            StringBuffer curCellText = rightSplPane.getCellPlainTextBuffer(0,ndx);
            curCellText.setLength(0);
            html.setLength(0);
            html.append("<html><FONT SIZE=3><FONT FACE=\"Helvetica, TimesRoman\">");
            if (summary.cantBuildNetlist[ndx]) {
            	html.append(/*" Can't build netlist!<br>" +*/
            			    "  See problems listed<br>in tree pane.<br>(left most pane). ");
            	curCellText.append(/*" Can't build netlist!" + LSEP + */
            			           "  See problems listed"+LSEP+"in tree pane."+LSEP+"(left most pane). ");
            } else {
            	html.append(summary.numParts[ndx] + " Parts<br>"
            				+ summary.numWires[ndx] + " Wires<br>"
							+ summary.numPorts[ndx] + " Ports<br>");
                curCellText.append(summary.numParts[ndx] + " Parts" + LSEP
                        	+ summary.numWires[ndx] + " Wires" + LSEP
							+ summary.numPorts[ndx] + " Ports");
            }
            html.append("</font></html>");
            rightSplPane.setCellText(0, ndx, html.toString());

            CellName cellName = cells[i].getCellName();
            String name = "  Summary of " + cellName.getName() + " " 
                        + cellName.getView().getAbbreviationExtension();
            rightSplPane.setLabelText(0, ndx, name);
        }
        setRightComponent(rightSplPane);
    }
    
    /**
     * Follow hyperlink with the given index and highlight required items
     * @param index  hyperlink index
     */
    void highlight(int index) {
        int recNdx = index/100000;
        int cellNdx = (index/10000)%10;
        int line = index%10000;
        TreeNode eqRecNode = curEqRecNodesToDisplay.elementAt(recNdx);

        // in case of hashcode partitions, get NetObject from Circuits
        NetObjReport partOrWire = null;
//        if (mismatches[eqRecNode.compNdx].isHashFailuresPrinted()) {
//            EquivRecReport eqRec = mismEqRecs[eqRecNode.compNdx][eqRecNode.eclass];
//            int c = 0, k = 0;
//            Circuit ckt = null;
//            for (Iterator<Circuit> it=eqRec.getCircuits(); it.hasNext(); c++, it.next())
//                if (c == cellNdx) {
//                    ckt = it.next(); 
//                    break;
//                }
//            for (Iterator<NetObjReport> it=ckt.getNetObjs(); it.hasNext(); k++, it.next())
//                if (k == line) {
//                    partOrWire = it.next();
//                    break;
//                }
//        } else { // in case of LP, get the NetObjeect from the array
            List<NetObjReport>[] mism = mismNetObjs[eqRecNode.compNdx][eqRecNode.eclass];
            List<NetObjReport>[] matched = matchedNetObjs[eqRecNode.compNdx][eqRecNode.eclass];
            if (line >= mism[cellNdx].size()) 
                partOrWire = matched[cellNdx].get(line - mism[cellNdx].size());
            else
                partOrWire = mism[cellNdx].get(line);
//        }
        
        Cell cell = null;
        VarContext context = null;
        if (partOrWire instanceof PartReport) {
            cell = ((PartReport)partOrWire).getNameProxy().leafCell();
            context = ((PartReport)partOrWire).getNameProxy().getContext();
        } else if (partOrWire instanceof WireReport) {
            cell = ((WireReport)partOrWire).getNameProxy().leafCell();
            context = ((WireReport)partOrWire).getNameProxy().getContext();
        }
        
        // find the highlighter corresponding to the cell
        Highlighter highlighter = HighlightTools.getHighlighter(cell, context);
        if (highlighter == null) return;

        if (partOrWire instanceof PartReport)
            HighlightTools.highlightPart(highlighter, cell, (PartReport)partOrWire);
        else if (partOrWire instanceof WireReport)
            HighlightTools.highlightWire(highlighter, cell, (WireReport)partOrWire);
        
        highlighter.finished();
    }
    
    /**
     * Remove unnecessary parts from a NetObject name 
     * @param descr  NetObject name
     * @return cleaned name 
     */
    public String cleanNetObjectName(String descr) {
        // drop "Part:" or "Wire:" prefices
        if (descr.startsWith("Wire: ") || descr.startsWith("Part: "))
            descr = descr.substring(6);
        // drop "Cell instance:" info
        int ind = descr.indexOf(" Cell instance:");
        if (ind > 0) descr = descr.substring(0, ind).trim();
        // drop {sch} or {lay} suffices
        if (descr.endsWith("{sch}") || descr.endsWith("{lay}"))
            descr = descr.substring(0, descr.length()-5);
        return descr;
    }
    
    /* (non-Javadoc)
     * Action Listener 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);                    
    }

    /**
     * Initialize popup menu for table cells
     */
    private void createCellPopup() {
        cellPopup = new JPopupMenu();
        JMenuItem menuItem = new JMenuItem("Copy Cell Text To Clipboard");
        menuItem.addActionListener(this);
        cellPopup.add(menuItem);
    }

    /**
     * Display a cell popup on top of Component c, with origin at (x,y) 
     * in component's coord system
     * @param text  The text to be copied to clipboard if menu item is activated
     * @param c  Component to place the popup menu on top of 
     * @param x  x origin coordinate (in component coord system)
     * @param y  y origin coordinate (in component coord system)
     */
    void showCellPopup(String text, Component c, int x, int y) {
        clipboard = text;
        cellPopup.show(c,x,y);
    }   
}
