/*
 * $Id$
 *
 * Copyright 1996-2008 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */
package com.sun.javatest.exec;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.Insets;
import java.awt.image.BufferedImage;
import java.util.Arrays;

import javax.swing.BorderFactory; 
import javax.swing.Box; 
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
//import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.JScrollPane;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants;
import javax.swing.plaf.metal.MetalLookAndFeel;

import com.sun.javatest.JavaTestError;
import com.sun.javatest.Parameters;
import com.sun.javatest.Status;
import com.sun.javatest.TestSuite;
import com.sun.javatest.TestResultTable;
import com.sun.javatest.tool.I18NUtils;
import com.sun.javatest.tool.PieChart;
import com.sun.javatest.tool.UIFactory;
import com.sun.javatest.util.Debug;

/**
 * Subpanel of BranchPanel.  This panel displays a summary of tests below the given
 * node which have passed, failed, errored, etc...
 */
class BP_SummarySubpanel extends BP_BranchSubpanel {
    BP_SummarySubpanel(UIFactory uif, BP_Model bpm, TestTreeModel ttm) {
	super("stats", uif, bpm, ttm, "br.summ");
	init();
    }

    void init() {
	if (pieColors == null) {
	    pieColors = new Color[I18NUtils.NUM_STATES];
	    pieColors[Status.PASSED] = I18NUtils.getStatusColor(Status.PASSED);
	    pieColors[Status.FAILED] = I18NUtils.getStatusColor(Status.FAILED);
	    pieColors[Status.ERROR] = I18NUtils.getStatusColor(Status.ERROR);
	    pieColors[Status.NOT_RUN] = I18NUtils.getStatusColor(Status.NOT_RUN);
	    pieColors[pieColors.length-1] = I18NUtils.getStatusColor(I18NUtils.FILTERED_OUT);
	}

	JPanel bodyPanel = uif.createPanel("br.summ.body", false);
	bodyPanel.setLayout(new GridBagLayout());

	JPanel statsPanel = uif.createPanel("br.summStats", false);
	statsPanel.setLayout(new GridBagLayout());
	statsPanel.setOpaque(false);

	GridBagConstraints gbc = new GridBagConstraints();

	// number of components in y-axis of layout before the text fields begin
	int pbarOffset = 3;

	gbc.gridx = 0;
	gbc.gridy = 0;
	gbc.ipady = 3;
	gbc.gridwidth=2;
	gbc.gridheight = 1;

	// --- could be static from here... ---
	sTypes = new JLabel[NUM_FIELDS];

	for (int i = 0; i < Status.NUM_STATES; i++) {
            // NOTE: intentional extra space in the line below
            //       to defeat static i18n checking in build
	    sTypes[i] = uif.createLabel( "br.summ.status" + i);
	    sTypes[i].setHorizontalAlignment(SwingConstants.LEFT);
	    sTypes[i].setDisplayedMnemonic(uif.getI18NString(
					"br.summ.status" + i + ".mne").charAt(0));
	    sTypes[i].setIcon(new LegendIcon(I18NUtils.getStatusBarColor(i), true));
	    uif.setAccessibleDescription(sTypes[i], "br.summ.status" + i);
	    // other label config here
	}   // for

	sTypes[SUBTOTAL_INDEX] = uif.createLabel("br.summ.subtotal");
	sTypes[FILTERED_INDEX] = uif.createLabel("br.summ.filtered");
	sTypes[TOTAL_INDEX] = uif.createLabel("br.summ.total");

	sTypes[FILTERED_INDEX].setIcon(
	    new LegendIcon(I18NUtils.getStatusColor(I18NUtils.FILTERED_OUT), true));

	// a11y
	sTypes[SUBTOTAL_INDEX].setDisplayedMnemonic(uif.getI18NString(
				    "br.summ.subtotal.mne").charAt(0));
	sTypes[FILTERED_INDEX].setDisplayedMnemonic(uif.getI18NString(
				    "br.summ.filtered.mne").charAt(0));
	sTypes[TOTAL_INDEX].setDisplayedMnemonic(uif.getI18NString(
				    "br.summ.total.mne").charAt(0));
	uif.setAccessibleDescription(sTypes[SUBTOTAL_INDEX], "br.summ.subtotal");
	uif.setAccessibleDescription(sTypes[FILTERED_INDEX], "br.summ.filtered");
	uif.setAccessibleDescription(sTypes[TOTAL_INDEX], "br.summ.total");

	sTypes[SUBTOTAL_INDEX].setBackground(MetalLookAndFeel.getPrimaryControlDarkShadow());
	sTypes[SUBTOTAL_INDEX].setForeground(MetalLookAndFeel.getWindowBackground());
	sTypes[SUBTOTAL_INDEX].setOpaque(true);
	sTypes[TOTAL_INDEX].setBackground(MetalLookAndFeel.getPrimaryControlDarkShadow());
	sTypes[TOTAL_INDEX].setForeground(MetalLookAndFeel.getWindowBackground());
	sTypes[TOTAL_INDEX].setOpaque(true);
	// --- to here ---

	sValues = new JTextField[NUM_FIELDS];

	for (int i = 0; i < Status.NUM_STATES; i++) {
	    sValues[i] = createPlainField(i);
	}   // for
	sValues[FILTERED_INDEX] = createPlainField(FILTERED_INDEX);
	sValues[SUBTOTAL_INDEX] = createTotalField(SUBTOTAL_INDEX);
	sValues[TOTAL_INDEX] = createTotalField(TOTAL_INDEX);

	// use blank icons to get indentation effect
	sTypes[SUBTOTAL_INDEX].setIcon(
	    new LegendIcon(sTypes[SUBTOTAL_INDEX].getBackground(), false));
	sTypes[TOTAL_INDEX].setIcon(
	    new LegendIcon(sTypes[TOTAL_INDEX].getBackground(), false));

	gbc.fill = GridBagConstraints.NONE;
	gbc.anchor = GridBagConstraints.WEST;
	gbc.gridwidth = 1;
	gbc.ipadx = 10;
	gbc.weightx = 0.0;

	// put it all into the layout
	for (int i = 0; i < NUM_FIELDS; i++) {
	    gbc.gridy = i + pbarOffset;
	    gbc.gridx = 1;
	    gbc.insets.right = NONTOTAL_FIELD_RMARGIN;

	    if (i == SUBTOTAL_INDEX || i == TOTAL_INDEX)
		gbc.fill = GridBagConstraints.NONE;
	    else
		gbc.fill = GridBagConstraints.HORIZONTAL;
	    
	    // add label
	    statsPanel.add(sTypes[i], gbc);

	    gbc.gridx = 2;

	    if (sValues[i] != subtotalTf &&
		sValues[i] != totalTf) {
		gbc.insets.right = NONTOTAL_FIELD_RMARGIN;
	    }
	    else {
		gbc.insets.right = TOTAL_FIELD_RMARGIN;
	    }

	    // add value
	    statsPanel.add(sValues[i], gbc);

	    // a11y
	    sTypes[i].setLabelFor(sValues[i]);
	}   // for

	// ----- now configure outer panel ----
	gbc = new GridBagConstraints();
	gbc.anchor = GridBagConstraints.SOUTH;
	gbc.weighty = 10.0;
	gbc.fill = GridBagConstraints.VERTICAL;
	gbc.ipadx = 10;

	// component to fill extra vertical space
	gbc.gridx = 1;
	gbc.gridy = 0;
	gbc.weighty = 10.0;
	gbc.gridwidth = 3;
	Component strut = Box.createVerticalStrut(1);
	bodyPanel.add(strut, gbc);

	// components to fill extra horizontal space
	gbc.gridx = 0;
	gbc.weighty = 1.0;
	gbc.gridwidth = 1;
	gbc.weightx = 3.0;
	gbc.fill = GridBagConstraints.HORIZONTAL;
	strut = Box.createHorizontalStrut(1);
	bodyPanel.add(strut, gbc);  // left
	gbc.gridx = 4;
	strut = Box.createHorizontalStrut(1);
	bodyPanel.add(strut, gbc);	 // right

	gbc.weighty = 0.0;

	// add folder and filter fields
	// folder name display
	JLabel lab = uif.createLabel("br.summ.fldlbl", true);
	lab.setOpaque(false);
	folderNameTf = uif.createOutputField("br.summ.fldtf", 35, lab);
	folderNameTf.setOpaque(false);
	folderNameTf.setBorder(BorderFactory.createEmptyBorder());
	gbc.fill = GridBagConstraints.NONE;
	gbc.anchor = GridBagConstraints.SOUTHEAST;
	gbc.weightx = 1.0;
	gbc.gridy = 1;
	gbc.gridx = 1;
	bodyPanel.add(lab, gbc);

	// output field
	gbc.gridx = 2;
	gbc.gridwidth = 2;
	gbc.weightx = 5.0;
	gbc.fill = GridBagConstraints.HORIZONTAL;
	gbc.anchor = GridBagConstraints.SOUTHWEST;
	bodyPanel.add(folderNameTf, gbc);

	// view filter display
	lab = uif.createLabel("br.summ.ftrlbl", true);
	lab.setOpaque(false);
	filterNameTf = uif.createOutputField("br.summ.ftrtf", 35, lab);
	filterNameTf.setOpaque(false);
	filterNameTf.setBorder(BorderFactory.createEmptyBorder());
	gbc.gridx = 1;
	gbc.gridy = 2;
	gbc.gridwidth = 1;
	gbc.weightx = 1.0;
	gbc.anchor = GridBagConstraints.SOUTHEAST;
	gbc.fill = GridBagConstraints.NONE;
	bodyPanel.add(lab, gbc);

	// output field
	gbc.gridx = 2;
	gbc.gridwidth = 2;
	gbc.weightx = 5.0;
	gbc.anchor = GridBagConstraints.SOUTHWEST;
	gbc.fill = GridBagConstraints.HORIZONTAL;
	bodyPanel.add(filterNameTf, gbc);

	filterDescTf = uif.createOutputField("br.summ.fdesc", 35);
	filterDescTf.setOpaque(false);
	filterDescTf.setBorder(BorderFactory.createEmptyBorder());
	gbc.gridy = 3;
	gbc.gridx = 2;
	gbc.weightx = 0.0;
	gbc.anchor = GridBagConstraints.SOUTHWEST;
	gbc.fill = GridBagConstraints.NONE;
	bodyPanel.add(filterDescTf, gbc);

	gbc.gridx = 1;
	gbc.gridy = 4;
	gbc.gridwidth = 3;
	gbc.fill = GridBagConstraints.HORIZONTAL;
	gbc.anchor = GridBagConstraints.CENTER;
	JComponent div = new Divider(1, 5);
	div.getInsets().top = 5;
	div.getInsets().bottom = 5;
	bodyPanel.add(div, gbc);

	// spacer
	/*
	gbc.gridx = 0;
	gbc.gridy = 4;
	gbc.gridwidth = 5;
	gbc.weightx = 1.0;
	gbc.weighty = 0.0;
	strut = Box.createVerticalStrut(12);
	bodyPanel.add(strut, gbc);
	*/

	gbc.gridx = 0;
	gbc.weighty = 1.0;
	gbc.gridwidth = 1;
	gbc.weightx = 3.0;
	gbc.fill = GridBagConstraints.HORIZONTAL;
	strut = Box.createHorizontalStrut(1);
	bodyPanel.add(strut, gbc);  // left

	JPanel pan = uif.createPanel("br.statpie", new GridBagLayout(), false);
	pan.setOpaque(false);
	GridBagConstraints gl = new GridBagConstraints();
	gl.fill = GridBagConstraints.NONE;
	gl.anchor = GridBagConstraints.CENTER;
	gl.gridy = 0;
	gl.gridx = 0;
	gl.weightx = 1.0;
	statsPanel.setMinimumSize(new Dimension(80,80));
	pan.add(statsPanel, gl);
	statsPanel.setMinimumSize(new Dimension(150,100));
	pie = new PieChart(new int[] {100},
			   new Color[] {I18NUtils.getStatusColor(I18NUtils.FILTERED_OUT)});
	//pie.setOpaque(false);
	gl.fill = GridBagConstraints.BOTH;
	gl.weightx = 2.0;
	gl.gridx = 1;
	pan.add(pie, gl);

	gbc.gridx = 1;
	gbc.gridy = 5;
	gbc.anchor = GridBagConstraints.CENTER;
	gbc.fill = GridBagConstraints.HORIZONTAL;
	gbc.gridheight = 1;
	gbc.gridwidth = 3;
	bodyPanel.add(pan, gbc);

	// stats
	/*
	gbc.weighty = 0.0;
	gbc.weightx = 2.0;
	gbc.gridx = 1;
	gbc.gridy = 4;
	gbc.gridheight = 1;
	gbc.gridwidth = 2;
	gbc.anchor = GridBagConstraints.EAST;
	gbc.fill = GridBagConstraints.HORIZONTAL;
	bodyPanel.add(statsPanel, gbc);

	// pie
	pie = new PieChart(new int[] {100},
			   new Color[] {I18NUtils.getStatusColor(I18NUtils.FILTERED_OUT)});
	pie.setOpaque(false);
	gbc.fill = GridBagConstraints.BOTH;
	gbc.anchor = GridBagConstraints.CENTER;
	gbc.gridwidth = 2;
	gbc.gridheight = 1;
	gbc.weightx = 2.0;
	gbc.weighty = 5.0;
	gbc.gridx = 3;
	bodyPanel.add(pie, gbc);
	*/

	// spacer to take up space
	gbc.gridwidth = 5;
	gbc.gridy = 6;
	gbc.gridx = 0;
	gbc.weightx = 10.0;
	gbc.weighty = 10.0;
	strut = Box.createVerticalStrut(5);
	bodyPanel.add(strut, gbc);

	/*
	// create the message area
	JTextArea ta = uif.createMessageArea("br.statInfo");
	ta.setColumns(30);
	ta.setRows(2);

	// add message area to bottom of panel
	gbc.weighty = 2.0;
	gbc.weightx = 10.0;
	gbc.gridy = 3;
	gbc.fill = GridBagConstraints.NONE;

	bodyPanel.add(ta, gbc);

	setBackground(Color.green);
	setOpaque(true);
	*/

	// this is necessary to make sure that the split pane can resize
	// this panel.  without setting the min., the panel seems to take
	// all it is given, and never gives it back.
	bodyPanel.setMinimumSize(new Dimension(150,100));
	bodyPanel.setBackground(Color.WHITE);
	bodyPanel.setOpaque(true);

	JScrollPane sp = uif.createScrollPane(bodyPanel,
		ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
		ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
	sp.setMinimumSize(new Dimension(150,100));
	setLayout(new BorderLayout());
	add(sp, BorderLayout.CENTER);
	setMinimumSize(new Dimension(150,100));
    }

    void dispose() {
	if (ct != null) {
	    ct.interrupt();
	    ct = null;
	}
    }

    protected JTextField createPlainField(int id) {
	JTextField f = uif.createOutputField("br.summ." + id, NTFIELD_WIDTH);
	f.setHorizontalAlignment(SwingConstants.RIGHT);
	f.setBorder(BorderFactory.createEmptyBorder());
	f.setOpaque(false);
	// other field config here

	return f;
    }

    protected JTextField createTotalField(int id) {
	JTextField f = uif.createOutputField("br.summ." + id, TFIELD_WIDTH);
	f.setHorizontalAlignment(JTextField.RIGHT);
	f.setBorder(null);
	f.setBackground(MetalLookAndFeel.getPrimaryControlDarkShadow());
	f.setForeground(MetalLookAndFeel.getWindowBackground());
	f.setEnabled(true);
	// other field config here

	return f;
    }

    protected void updateSubpanel(TestResultTable.TreeNode currNode) {
	super.updateSubpanel(currNode);

	// XXX consider doing it in the background anyway
	//     and altering the thread priority based on visibility
	//if (!isVisible())
	    //return;

	//if (ct != null && subpanelNode == ct.getNode())
	 //   return;

	if (debug > 1) {
	    Debug.println("BP_SP - updateSubpanel() called");
	}

	if (filterNameTf != null && filterDescTf != null &&
	    !(ttm.getTestFilter().getName().equals(filterNameTf.getText())) ) {
	    filterNameTf.setText(ttm.getTestFilter().getName());
	    filterDescTf.setText(ttm.getTestFilter().getDescription());
	}

	// for efficiency, we won't restart the thread on an update request.
	// this may lead to synchronization issues where the numbers will
	// be slightly off.
	// as a possibly solution, we might want to set a flag so once the
	//    current thread finishes, it restarts a new counter thread.
	// alternatively, we can assume that another update will occur
	//    in a short period of time without intervention
	/*
	if (ct != null && ct.isAlive())
	    ct.halt();
	*/

	if (ct != null && ct.getNode() == subpanelNode) {
	    // same node and we are counting
	    return;
	}
	else {
	    if (debug > 0) {
		Debug.println("BP_SP - updating panel");
	    }

	    // we are watching a new node, or need to update the current stats
	    if (ct == null) {
		ct = new CounterThread(subpanelNode, sValues);
		ct.setPriority(Thread.MIN_PRIORITY + 3);
		ct.start();
	    }

	    // in all cases, tell the thread what to look at
	    ct.setNode(subpanelNode);
	    String spn = subpanelNode.getName();
	    if (spn != null)
		folderNameTf.setText(spn);
	    else {
		Parameters p = ttm.getParameters();
		if (p != null) {
		    TestSuite ts = p.getTestSuite();
		    folderNameTf.setText((ts == null ? "" :
			uif.getI18NString("br.summ.fldtf.root", ts.getName())));
		}
	    }

	}
    }

    /**
     * Iterates to get the necessary data and updates the GUI.
     * The behavior depends on whether the harness is running or not.  If it is not
     * running tests, the GUI is incrementally updated.  If it is running, the
     * GUI is only updated after the new data is available.  The result should be
     * better behavior from the user's standpoint.
     */
    private class CounterThread extends Thread {
	/**
	 * @param tn The node whose stats are to be scanned and placed onscreen.
	 * @param fs Filters used to correctly generate stats.
	 * @param fields Components to be updated with stats.
	 */
	CounterThread(TestResultTable.TreeNode tn, JTextField[] fields) {
	    super("BP_SP.CounterThread");
	    node = tn;
	    tfs = fields;
	}

	/**
	 * Discover the node this thread is scanning.
	 */
	TestResultTable.TreeNode getNode() {
	    return node;
	}

	public void run() {
	    if (debug) {
		Debug.println("BP_SP.CT - thread running");
		Debug.println("   -> " + this);
	    }

	    if (stopping) {
		halt();
		return;
	    }

	    // high priority cache request
	    synchronized (this) {
		cache = ttm.getNodeInfo(node, true);
	    }

	    int[] stats;
	    int[] combinedStats;

	    // do not overwrite the info onscreen if we are in the middle of a run
	    // otherwise reset numbers to zero
	    if (!model.isRunning()) {
		EventQueue.invokeLater(
		    new BranchPanel.TextUpdater(BranchPanel.TextUpdater.CLEAR,
						tfs, null, uif));
		showMessage(uif.getI18NString("br.summ.working"));
	    }

	    while (true) {
		if (stopping) {	    
		    if (debug) {
			Debug.println("BP_SP.CT - thread terminating in run() due to halt()");
			Debug.println("   -> " + this);
		    }
		    // terminates the thread
		    break;
		}

		// refresh reference from model
		// cache == null usually indicates we changed nodes


		boolean isComplete = false;
		synchronized(this) {
		    //refresh everytime
		    //if (cache == null || !cache.isValid())
			cache = ttm.getNodeInfo(node, true);

		    if (cache == null)
			continue;

		    // update the GUI stats
		    stats = cache.getStats();
		    combinedStats = fillStats(stats, cache.getRejectCount(), true, true);
		    isComplete = cache.isComplete();
		}   // sync
		
		if (!Arrays.equals(lastStats, combinedStats)) {
		    notifyUpdate(stats, combinedStats);
		    lastStats = combinedStats;
		    updateMessage(stats, !isComplete);
		}
		else  {
		    // bypass update message
		}

		synchronized (this) {
		    try {
			if (cache == null || cache.isComplete())
			    wait(INACTIVE_FREQUENCY);
			else
			    wait(ACTIVE_FREQUENCY);
		    }
		    catch (InterruptedException e) {
			// someone wants this thread dead
			return;
		    }	// catch
		}
	    }   // while

	    // checkpoint
	    if (stopping) {
		// warning, possible incomplete data in GUI
		halt();
		return;
	    }
	    // update the GUI stats
	    // synchronzied to protect again NPE when setNode() nulls the cache ref.
	    synchronized (this) {
		if (cache == null)
		    cache = ttm.getNodeInfo(node, true);

		stats = cache.getStats();
		notifyUpdate(stats,
		    fillStats(stats, cache.getRejectCount(), true, true));
	    }	// sync

	    updateMessage(stats, false);

	    if (debug)
		Debug.println("counter thread done...");
	}	// run()

	/**
	 * Request that this list scanning thread terminate.
	 * The run method should exit promptly.
	 */
	public void halt() {
	    if (debug) {
		Debug.println("BP_SP.CT - thread stopping");
		Debug.println("   -> " + this);
	    }

	    stopping = true;
	}

	synchronized void setNode(TestResultTable.TreeNode tn) {
	    if (tn != node) {
		cache = null;
		node = tn;
	    }
	}

	/**
	 * Create an array of length tfs.length.  Containing the bsaic + filter stats,
	 * the subtotal and total fields empty.
	 *
	 * @param basic Array of length Status.NUM_STATES, probably output from
	 *        TreeIterator.getResultStats().
	 * @param filtered Number of tests filtered out.  Should be zero or greater.
	 * @param validSub Is the subtotal okay to calculate and display?
	 * @param validTot Is the total okay to calculate and display?
	 */
	private int[] fillStats(int[] basic, int filtered, boolean validSub,
				boolean validTot) {
	    int[] result = new int[tfs.length];

	    System.arraycopy(basic, 0, result, 0, basic.length);

	    // subtotal position
	    if (validSub) {
		int sub = 0;
		for (int i = 0; i < basic.length; i++)
		    sub += basic[i];

		result[SUBTOTAL_INDEX] = sub;
	    }
	    else	    // disable this field
		result[SUBTOTAL_INDEX] = -1;

	    result[FILTERED_INDEX] = filtered;

	    // total position
	    if (validTot)
		result[TOTAL_INDEX] = result[FILTERED_INDEX] + result[SUBTOTAL_INDEX];
	    else	    // disable this field
		result[TOTAL_INDEX] = -1;

	    return result;
	}

	private void updateMessage(int[] stats, boolean stillRunning) {
	    // calculate the message at the bottom
	    // XXX this needs to be moved to the outer class, but stats are
	    // convienient here.  also this panel may not always be updated if
	    // not visible, so this code will not run on time
	    int worstIndex = -1;
	    for (int i = 0; i < stateOrdering.length; i++) {
		if (stats[stateOrdering[i]] > 0) {
		    worstIndex = stateOrdering[i];
		    break;
		}
	    }	// for

	    // checkpoint
	    if (stopping) {
		halt();
		return;
	    }

	    if (worstIndex != -1) {
		// NOTE: this is the switch is used instead of this line to allow static
		// i18n string checking to work
		//statusTf.setText(uif.getI18NString("br.worst." + worstIndex));

		if (Status.NUM_STATES > 4)
		    throw new JavaTestError(uif.getI18NString("br.worstNum"));

		String msg = null;

		switch (worstIndex) {
		    case 0: msg = uif.getI18NString("br.worst.0"); break;
		    case 1: msg = uif.getI18NString("br.worst.1"); break;
		    case 2: msg = uif.getI18NString("br.worst.2"); break;
		    case 3: msg = uif.getI18NString("br.worst.3"); break;
		    default:
			throw new JavaTestError(uif.getI18NString("br.worstNum"));
		}

		if (stillRunning) {
		    msg = uif.getI18NString("br.stillWork") + msg;
		}

		showMessage(msg);

	    }
	    else
		showMessage(uif.getI18NString("br.none"));

	}

	private void notifyUpdate(final int[] basic, final int[] values) {
	    EventQueue.invokeLater(
		new BranchPanel.TextUpdater(BranchPanel.TextUpdater.UPDATE,
					    tfs,
					    values,
					    uif));
	    final int[] pieStats = new int[I18NUtils.NUM_STATES];
	    System.arraycopy(basic, 0, pieStats, 0, basic.length);
	    pieStats[I18NUtils.FILTERED_OUT] = values[FILTERED_INDEX];
	    EventQueue.invokeLater(new Runnable() {
		public void run() {
		    pie.setValue(pieStats, pieColors);
		/*
		    int sum = 0;
		    for (int i = 0; i < pieStats[i]; i++)
			sum += pieStats[i];

		    // hide pie if all filtered out or not run
		    if (pieStats[FILTERED_INDEX] == sum ||
			pieStats[I18NUtils.NOT_RUN] == sum)
			pie.setVisible(false);
		    else
			pie.setVisible(true);
		*/
		}
	    });
	}

	private volatile boolean stopping;
	private TestResultTable.TreeNode node;
	private TT_NodeCache cache;
	private JTextField[] tfs;
	private int[] lastStats;	// to optimize out extra GUI refreshes
	private boolean debug = Debug.getBoolean(BP_SummarySubpanel.class, "CounterThread");
    }

    class Divider extends JComponent {
	/**
	 * Creates a default 2 wide pair of lines.
	 */
	public Divider() {
	}

	public Divider(int thickness, int spacing) {
	    thick = thickness;
	    space = spacing;
	}

	protected void paintComponent(Graphics g) {
	    if (isOpaque()) {
		g.setColor(getBackground());
		g.fillRect(0,0, getWidth(), getHeight());
	    }

	    Insets inset = getInsets();
	    int width = getWidth() - inset.left - inset.right;
	    int height = getHeight() - inset.left - inset.right;

	    g.setColor(Color.BLACK);

	    /*
	    if (height < 2) 
		return;
	    else if (height < ((thick * 2) + space)) {
		*/
		// not enough space for two lines, draw one
		int mid = height / 2;
		g.fillRect(inset.left, mid-(thick/2), width, thick);
	    /*
		g.fillRect(inset.left, mid-(thick/2), width, thick);
	    }
	    //else if (height < ((thick * 2) + space) ) {
	    else {
		// draws two lines vertically centered, spanning the entire
		// usable width
		int mid = height / 2;
		g.fillRect(inset.left, mid-(space/2)-thick, width, thick);
		g.fillRect(inset.left, mid+(space/2), width, thick);
	    }
	    */
	}

	public Dimension getPreferredSize() {
	    return getMinimumSize();
	}

	public Dimension getMinimumSize() {
	    Insets inset = getInsets();
	    return new Dimension(10+inset.left+inset.right,
				 thick*2+space+inset.top+inset.bottom);
	}

	int thick = 2;
	int space = 4;
    }

    // incomplete
    class Divider2 extends JComponent {
	public Divider2() {
	}

	public Divider2(int thickness) {
	    thick = thickness;
	}

	protected void paintComponent(Graphics g) {
	    if (isOpaque()) {
		g.setColor(getBackground());
		g.fillRect(0,0, getWidth(), getHeight());
	    }

	    Insets inset = getInsets();
	    int width = getWidth() - inset.left - inset.right;
	    int height = getHeight() - inset.left - inset.right;

	    g.setColor(MetalLookAndFeel.getSeparatorForeground());

	    if (height < 2) 
		return;
	    else if (height < (thick * 2)) {
		// not enough space for two lines, draw one
		int mid = height / 2;
		g.fillRect(inset.left, mid-(thick/2), width, thick);
	    }
	    else {
		// usable width
		int mid = height / 2;
		g.fillRect(inset.left + arcWidth, mid-thick*2,
			   width-arcWidth, thick);
		g.fillArc(inset.left, mid-(thick*2), width, thick*2, -90, 0);
	    }
	}

	public Dimension getPreferredSize() {
	    return getMinimumSize();
	}

	public Dimension getMinimumSize() {
	    Insets inset = getInsets();
	    return new Dimension(arcWidth*2+inset.left+inset.right,
				 thick*2+inset.top+inset.bottom);
	}

	int thick = 2;
	int arcWidth = 15;
    }

    private class LegendIcon implements Icon {
	LegendIcon(Color c, boolean shadow) {
	    color = c;
	    shadowColor = new Color(0x555555); 
	    paintShadow = shadow;
	}

	// creates a transparent icon
	LegendIcon() {
	    color = shadowColor = null;
	}

	public int getIconWidth() { return 16; }
	public int getIconHeight() { return 16; }

	public void paintIcon(Component c, Graphics g, int x, int y) {
	    if (image == null) {
		image = new BufferedImage(getIconWidth(), getIconHeight(),
					  BufferedImage.TYPE_INT_ARGB);
		Graphics imageG = image.getGraphics();
		paintIt(imageG, paintShadow, color);
		imageG.dispose();
	    }

            g.drawImage(image, x, y, null);
	}

	private void paintIt(Graphics g, boolean shadow, Color c) {
	    if (c == null) {
		return;
	    }

	    int x = 2, y = 2, width, height;
	    if (shadow) {
		width = height = 10;
	    }
	    else
		width = height = 12;

	    if (shadow) {
		g.setColor(shadowColor);
		g.fillRect(x+2,y+2,width,height);
	    }
 
	    /*
	    if (c.equals(Color.WHITE)) {
		// outline for a white icon
		g.setColor(Color.BLACK);
		g.fillRect(x,y,width,height);
		g.setColor(c);
		g.fillRect(x,y,width-1,height-1);
	    }
	    else {
		*/
		g.setColor(c);
		g.fillRect(x,y,width,height);
	    //}
	}

	private Color color, shadowColor;
        private Image image;
	private boolean paintShadow;
    }

    private CounterThread ct;
    private int[] stats;
    private int filtered;

    private JLabel progLabel;
    private ProgressMeter pMeter;

    private /* static */ JLabel[] sTypes;
    private JTextField[] sValues;
    private JTextField subtotalTf;
    private JTextField totalTf;
    private JTextField folderNameTf;
    private JTextField filterNameTf;
    private JTextField filterDescTf;
    private PieChart pie;
    private static Color[] pieColors;
    // seven fields:
    // pass, fail, error, not run, subtotal, filtered out, total
    private static final int NUM_FIELDS = 7;
    private static final int SUBTOTAL_INDEX = 4;
    private static final int FILTERED_INDEX = 5;
    private static final int TOTAL_INDEX = 6;
    private static final int NONTOTAL_FIELD_RMARGIN = 10;
    private static final int TOTAL_FIELD_RMARGIN = 2;

    private static final int ACTIVE_FREQUENCY = 1000;	    // while iterator running
    private static final int INACTIVE_FREQUENCY = 2500;	    // while just waiting for updates

    private final int NTFIELD_WIDTH = 5;
    private final int TFIELD_WIDTH = 7;
    protected final int[] stateOrdering = {Status.ERROR, Status.FAILED, Status.NOT_RUN, Status.PASSED};
    private static int debug = Debug.getInt(BP_SummarySubpanel.class);

}
