/*
 * $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.Component;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.lang.reflect.InvocationTargetException;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.Timer;
import javax.swing.border.Border;

import com.sun.javatest.Status;
import com.sun.javatest.TestResult;
import com.sun.javatest.tool.I18NUtils;
import com.sun.javatest.tool.ToolDialog;
import com.sun.javatest.tool.UIFactory;

class ProgressMonitor extends ToolDialog {

    ProgressMonitor(Component parent, UIFactory uif, MonitorState state) {
	super(parent, uif, "pm");

	/*ToolDialog
	super(parent, uif.getI18NString("pm.title"), false);
	*/

	// ToolDialog this.uif = uif;
	this.state = state;
    }

    protected void initGUI() {
	setI18NTitle("pm.title");

	subpanels = new StatusSubpanel[4];

	/*ToolDialog
	Container content = getContentPane();
	content.setName("progressD");
	content.setLayout(new BorderLayout());
	*/

	JPanel body = new JPanel(new GridBagLayout());
	GridBagConstraints c = new GridBagConstraints();
	c.fill = GridBagConstraints.BOTH;

	// TOP 
	c.gridwidth = 2;
	c.gridx = 0;
	c.gridy = 0;
	c.insets.left = 8;
	c.insets.right = 8;
	c.insets.top = 5;
	c.weightx = 1;
	c.weighty = 0;
	progressSubpanel = new ProgressSubpanel();
	subpanels[0] = progressSubpanel;
	body.add(progressSubpanel, c);

	// LEFT SIDE
	c.gridheight = 1;
	c.gridwidth = 1;
	c.gridx = 0;
	c.gridy = 1;
	c.insets.right = 0;
	c.weightx = 1;
	c.weighty = 2;

	timeSubpanel = new TimeSubpanel();
	subpanels[1] = timeSubpanel;
	body.add(timeSubpanel, c);

	c.gridy = 2;
	memorySubpanel = new MemorySubpanel();
	subpanels[2] = memorySubpanel;
	body.add(memorySubpanel, c);

	// RIGHT SIDE
	// list of tests running
	c.fill = GridBagConstraints.BOTH;
	c.gridheight = 2;
	c.gridwidth = 1;
	c.gridx = 1;
	c.gridy = 1;
	c.insets.right = 8;
	c.weightx = 3;
	c.weighty = 1;
	activitySubpanel = new ActivitySubpanel();
	subpanels[3] = activitySubpanel;
	body.add(activitySubpanel, c);
	
	setBody(body);
	/*ToolDialog
	content.add(body, BorderLayout.CENTER);
	*/
	
	/*ToolDialog
	JPanel btnPanel = new JPanel(new GridBagLayout());
	GridBagConstraints bc = new GridBagConstraints();
	bc.anchor = GridBagConstraints.EAST;
	bc.insets.top = 5;
	bc.insets.bottom = 11;  // value from JL&F Guidelines
	bc.insets.right = 5;   // value from JL&F Guidelines
	bc.weightx = 1;

	JButton helpBtn = uif.createHelpButton("pm.help", "browse.testMonitor.csh");
	btnPanel.add(helpBtn, bc);

	JButton closeBtn = uif.createCloseButton("pm.close");
	bc.insets.right = 11;   // value from JL&F Guidelines
	bc.weightx = 0;
	btnPanel.add(closeBtn, bc);

	content.add(btnPanel, BorderLayout.SOUTH);
	*/
	JButton closeBtn = uif.createCloseButton("pm.close");
	setButtons(new JButton[] { closeBtn }, closeBtn);

	// other stuff
	state.addObserver(listener);

	if (state.isRunning())
	    listener.starting();

	/*ToolDialog
	HelpBroker b = uif.getHelpBroker();
	//Desktop.addHelpDebugListener(progDialog);
	b.enableHelpKey(getRootPane(), "browse.testMonitor.csh", null);
	*/

	//ToolDialog pack();

	for (int i = 0; i < subpanels.length; i++) {
	    // make sure the panels are in any initial state
	    // important for getting the "last" run info, less important if
	    // there's a run in progress or a future runs
	    subpanels[i].update();
	}

	/*ToolDialog
	getAccessibleContext().setAccessibleDescription(
			    uif.getI18NString("pm.desc"));
	*/
    }

    void setTreePanelModel(TreePanelModel tpm) {
	this.tpm = tpm;
    }
    
    //----------member variables-----------------------------------------------------

    private MonitorState state;
    private TreePanelModel tpm;

    private ProgressSubpanel progressSubpanel;
    private ActivitySubpanel activitySubpanel;
    private TimeSubpanel timeSubpanel;
    private MemorySubpanel memorySubpanel;

    private StatusSubpanel[] subpanels;

    private volatile boolean running;
    private Listener listener = new Listener();

    private static final int UPDATE_FREQUENCY = 1000;
    private static int componentCount; // for generating names


    //----------nested classes-------------------------------------------------------

    class Listener implements MonitorState.Observer, ActionListener {
	Listener() {
	    timer = new Timer(UPDATE_FREQUENCY, this);
	}

	public void actionPerformed(ActionEvent e) {
	    if (e.getSource() == timer) {
		for (int i = 0; i < subpanels.length; i++)
		    subpanels[i].update();
	    }
	}

	// these are probably on whichever thread the harness is running on
	// be sure to switch them onto the event thread
	public void starting() {
	    running = true;

	    for (int i = 0; i < subpanels.length; i++)
		subpanels[i].starting();

	    timer.start();
	}

	public void postProcessing() {
	    for (int i = 0; i < subpanels.length; i++) {
		    // make sure the panels update one last time
		subpanels[i].update();
		subpanels[i].postProcessing();
	    }	// for
	}

	public void stopping() {
	    // it is assumed that finished() will be called right away
	}

	public void finished(final boolean allOk) {
	    running = false;
	    for (int i = 0; i < subpanels.length; i++) {
		// make sure the panels update one last time
		subpanels[i].update();
		subpanels[i].stopping();
	    }	// for

	    timer.stop();
	}

	private Timer timer;
    }

    private abstract class StatusSubpanel extends JPanel {
	/**
	 * Refresh any data in this subpanel.
	 * This method is always called on the event dispatch thread.
	 */
	abstract void update();

	/**
	 * Test run is starting.  This method can be ignored if it does not apply.
	 */
	void starting() {
	}

	void postProcessing() {
	}

	/**
	 * Test run is finishing.  This method can be ignored if it does not apply.
	 */
	void stopping() {
	}
    }

    private class ProgressSubpanel extends StatusSubpanel
    {
	ProgressSubpanel() {
	    setBorder(uif.createTitledBorder("pm.prog"));
	    setLayout(new GridBagLayout());

	    // Line label
	    GridBagConstraints lnc = new GridBagConstraints();
	    lnc.gridx = 0;
	    lnc.anchor = GridBagConstraints.EAST;
	    lnc.insets.right = 10;

	    // Field label
	    GridBagConstraints lc = new GridBagConstraints();
	    lc.insets.right = 5;  

	    // Field
	    GridBagConstraints fc = new GridBagConstraints(); 
	    fc.weightx = 1;
	    fc.fill = GridBagConstraints.HORIZONTAL; 
	    fc.insets.right = 15;
	    
	    // Remaining field
	    GridBagConstraints rc = new GridBagConstraints();
	    rc.weightx = 1;
	    rc.fill = GridBagConstraints.BOTH;
	    rc.gridwidth = GridBagConstraints.REMAINDER;

	    JTextField tf = uif.createHeading("pm.tests");
	    uif.setAccessibleInfo(tf, "pm.tests");
	    add(tf, lnc);
	    JLabel lab = uif.createLabel("pm.tests.pass");
	    add(lab, lc);
	    passTf = uif.createOutputField("pm.tests.pass", 6);
	    passTf.setHorizontalAlignment(JTextField.RIGHT);
	    lab.setLabelFor(passTf);
	    lab.setDisplayedMnemonic(uif.getI18NString("pm.tests.pass.mne").charAt(0));
	    add(passTf, fc);

	    lab = uif.createLabel("pm.tests.fail");
	    lab.setDisplayedMnemonic(uif.getI18NString("pm.tests.fail.mne").charAt(0));
	    add(lab, lc);
	    failTf = uif.createOutputField("pm.tests.fail", 6);
	    failTf.setHorizontalAlignment(JTextField.RIGHT);
	    lab.setLabelFor(failTf);
	    add(failTf, fc);

	    lab = uif.createLabel("pm.tests.err");
	    add(lab, lc);
	    errorTf = uif.createOutputField("pm.tests.err", 6);
	    lab.setDisplayedMnemonic(uif.getI18NString("pm.tests.err.mne").charAt(0));
	    errorTf.setHorizontalAlignment(JTextField.RIGHT);
	    lab.setLabelFor(errorTf);
	    add(errorTf, fc);

	    lab = uif.createLabel("pm.tests.nr");
	    lab.setDisplayedMnemonic(uif.getI18NString("pm.tests.nr.mne").charAt(0));
	    add(lab, lc);
	    notRunTf = uif.createOutputField("pm.tests.nr", 6);
	    notRunTf.setHorizontalAlignment(JTextField.RIGHT);
	    lab.setLabelFor(notRunTf);
	    add(notRunTf, rc);
	    add(Box.createVerticalStrut(10), rc);

	    Color[] colors = new Color[Status.NUM_STATES];
	    colors[Status.PASSED] = I18NUtils.getStatusBarColor(Status.PASSED);
	    colors[Status.FAILED] = I18NUtils.getStatusBarColor(Status.FAILED);
	    colors[Status.ERROR] = I18NUtils.getStatusBarColor(Status.ERROR);
	    colors[Status.NOT_RUN] = I18NUtils.getStatusBarColor(Status.NOT_RUN);

	    /*
	    String[] actions = new String[5 + 1];
	    actions[Status.PASSED] = Integer.toString(Status.PASSED);
	    actions[Status.FAILED] = Integer.toString(Status.FAILED);
	    actions[Status.ERROR] = Integer.toString(Status.ERROR);
	    actions[Status.NOT_RUN] = Integer.toString(Status.NOT_RUN);
	    actions[actions.length - 1] = "-1";
	    */

	    tf = uif.createHeading("pm.tests.mtr");
	    uif.setAccessibleInfo(tf, "pm.tests.mtr");
	    add(tf, lnc);
	    add(meter = new ProgressMeter(colors, state), rc);
	    uif.setAccessibleInfo(meter, "pm.tests.bar");
	    /*
	    meter.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));

	    ActionListener al = new ActionListener() {
		public void actionPerformed(ActionEvent e) {
		    model.showSummary(Integer.parseInt(e.getActionCommand()));
		}
	    };

	    meter.addActionListener(al);
	    */
	    uif.setToolTip(this, "pm.tests");
	}

	public void update() {
	    updateAll();
	}

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

	private void updateAll() {
	    meter.update();

	    int[] stats = state.getStats();

	    setCount(passTf, stats[Status.PASSED]);
	    setCount(failTf, stats[Status.FAILED]);
	    setCount(errorTf, stats[Status.ERROR]);
	    setCount(notRunTf,state.getTestsRemainingCount());
	}

	private final void setCount(JTextField tf, int value) {
	    if (EventQueue.isDispatchThread())
		tf.setText(Integer.toString(value));
	    else
		try {
		    EventQueue.invokeAndWait(new BranchPanel.TextUpdater(tf,
					     Integer.toString(value), uif));
		}
		catch (InterruptedException e) {
		}
		catch (InvocationTargetException e) {
		}
	}
	
	private JTextField passTf;
	private JTextField failTf;
	private JTextField errorTf;
	private JTextField notRunTf;

	private ProgressMeter meter;
	private int[] meterStats;
    }

    private class ActivitySubpanel extends StatusSubpanel {
	ActivitySubpanel() {
	    setBorder(uif.createTitledBorder("pm.activity"));
	    setLayout(new CardLayout());
	    
	    idleCard = createSimpleCard("pm.idle");
	    addCard(idleCard);
	    initRunningCard();
	    showCard(idleCard);
	}

	void starting() {
	    showCard(runningCard);
	    //StatusDialog.this.validate();
	}

	void postProcessing() {
	    showCard(idleCard);
	}

	void stopping() {
	}

	void update() {
	    testListData.removeAllElements();
	    TestResult[] rt = state.getRunningTests();
	    for (int i = 0; i < rt.length; i++)
		testListData.addElement(rt[i]);
	}

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

	private JComponent createSimpleCard(String uiKey) {
	    JPanel card = new JPanel();
	    card.setLayout(new GridBagLayout());
	    GridBagConstraints c = new GridBagConstraints();
	    c.anchor = GridBagConstraints.CENTER;
	    c.gridwidth = GridBagConstraints.REMAINDER;
	    c.weightx = 1;
	    c.weighty = 1;
	    JTextField tf = uif.createHeading(uiKey);
	    uif.setAccessibleInfo(tf, uiKey);
	    card.add(tf, c);

	    JLabel icon = uif.createIconLabel(uiKey);
	    c.anchor = GridBagConstraints.WEST;
	    c.weighty = 0;
	    card.add(icon, c);

	    return card;
	}

	private void initRunningCard() {
	    testListData = new DefaultListModel();
	    final JList list = uif.createList("pm.runlist", testListData);
	    list.setBorder(BorderFactory.createEtchedBorder());
	    list.setCellRenderer(RenderingUtilities.createTestListRenderer());
	    list.addMouseListener(new MouseAdapter() {
		    public void mouseClicked(MouseEvent e) {
			int index = list.locationToIndex(e.getPoint());
			Object target = list.getModel().getElementAt(index);
			if (target instanceof TestResult && tpm != null) {
			    tpm.showTest(((TestResult)target).getTestName());
			}
		    }
		}
	    );
	    runningCard = 
		uif.createScrollPane(list, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
				JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
	    addCard(runningCard);
	}

	private void addCard(JComponent comp) {
	    String s = comp.getName();
	    if (s == null) 
		comp.setName("StatusDialog.component" + componentCount++);
	    add(comp, comp.getName());
	}

	private void showCard(JComponent comp) {
	    //setVisible(false);
	    ((CardLayout)(getLayout())).show(this, comp.getName());
	    //setVisible(true);
	}
		
	private JComponent idleCard;
	private JComponent runningCard;

	private JTextField fileField;
	private DefaultListModel testListData;
	private JList testList;
	private String rootDir;
    }

    private class TimeSubpanel extends StatusSubpanel {
	TimeSubpanel() {
	    setLayout(new GridBagLayout());
	    Border b1 = uif.createTitledBorder("pm.time");
	    Border b2 = BorderFactory.createEmptyBorder(10, 5, 10, 5);
	    setBorder(BorderFactory.createCompoundBorder(b1, b2));

	    // Line label
	    GridBagConstraints lnc = new GridBagConstraints();
	    lnc.gridx = 0;
	    lnc.anchor = GridBagConstraints.EAST;
	    lnc.insets.right = 10;
	    
	    // Remaining field
	    GridBagConstraints rc = new GridBagConstraints();
	    rc.fill = GridBagConstraints.BOTH;
	    rc.gridwidth = GridBagConstraints.REMAINDER;
	    rc.weightx = 1;

	    JLabel lab = uif.createLabel("pm.time.sofar");
	    lab.setDisplayedMnemonic(uif.getI18NString("pm.time.sofar.mne").charAt(0));
	    add(lab, lnc);
	    add(elapsedField = uif.createOutputField("pm.time.sofar", 8), rc);
	    elapsedField.setText("00:00:00");
	    lab.setLabelFor(elapsedField);

	    lab = uif.createLabel("pm.time.remain");
	    lab.setDisplayedMnemonic(uif.getI18NString("pm.time.remain.mne").charAt(0));
	    add(lab, lnc);
	    add(estimatedRemainingField = uif.createOutputField("pm.time.remain", 8), rc);
	    estimatedRemainingField.setText("00:00:00");
	    lab.setLabelFor(estimatedRemainingField);

	    // workaround unknown GridBagLayout problem
	    add(Box.createVerticalStrut(10), rc);

	    uif.setToolTip(this, "pm.time");
	}

	void update() {
	    long elapsed = state.getElapsedTime();
	    elapsedField.setText(ElapsedTimeMonitor.millisToString(elapsed));

	    long remain = state.getEstimatedTime();
	    estimatedRemainingField.setText(ElapsedTimeMonitor.millisToString(remain));
	}

	private JTextField elapsedField;
	private JTextField estimatedRemainingField;
	//private ProgressMeter meter;
	private int[] meterStats = new int[2];
    }

    private class MemorySubpanel extends StatusSubpanel {
	MemorySubpanel() {
	    setLayout(new GridBagLayout());
	    Border b1 = uif.createTitledBorder("pm.memory");
	    Border b2 = BorderFactory.createEmptyBorder(10, 5, 10, 5);
	    setBorder(BorderFactory.createCompoundBorder(b1, b2));

	    // Line label
	    GridBagConstraints lnc = new GridBagConstraints();
	    lnc.gridx = 0;
	    lnc.anchor = GridBagConstraints.EAST;
	    lnc.insets.right = 10;
	    lnc.weightx = 0;
	    lnc.fill = GridBagConstraints.NONE;
	    
	    // Remaining field
	    GridBagConstraints rc = new GridBagConstraints();
	    rc.fill = GridBagConstraints.BOTH;
	    rc.gridwidth = GridBagConstraints.REMAINDER;
	    rc.weightx = 1;

	    JLabel lab = uif.createLabel("pm.memory.used");
	    lab.setDisplayedMnemonic(uif.getI18NString("pm.memory.used.mne").charAt(0));
	    add(lab, lnc);
	    add(usedField = uif.createOutputField("pm.memory.used", 10), rc);
	    lab.setLabelFor(usedField);

	    lab = uif.createLabel("pm.memory.ttl");
	    lab.setDisplayedMnemonic(uif.getI18NString("pm.memory.ttl.mne").charAt(0));
	    add(lab, lnc);
	    add(totalField = uif.createOutputField("pm.memory.ttl", 10), rc);
	    lab.setLabelFor(totalField);

	    // workaround unknown GridBagLayout problem
	    add(Box.createVerticalStrut(10), rc);

	    /*
	    rc.fill = GridBagConstraints.VERTICAL;
	    Color[] mc = {
		new Color(128, 0, 0), 
		new Color(0, 128, 0)
	    };
	    add(meter = new ProgressMeter(mc, state), rc);
	    */
	    uif.setToolTip(this, "pm.memory");
	}

	void update() {
	    int freeMem = (int)(runtime.freeMemory() / 1024);
	    int totalMem = (int)(runtime.totalMemory() / 1024);
	    int usedMem = totalMem - freeMem;
	    usedField.setText(usedMem + "K");
	    totalField.setText(totalMem + "K");

	}
	
	private JTextField usedField;
	private JTextField totalField;
	private ProgressMeter meter;
	private int[] meterStats = new int[2];
	private Runtime runtime = Runtime.getRuntime();
    }
}
