/*
 * $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.EventQueue;
import java.util.Vector;

import com.sun.javatest.Harness;
import com.sun.javatest.Parameters;
import com.sun.javatest.Status;
import com.sun.javatest.TestResult;
import com.sun.javatest.WorkDirectory;
import com.sun.javatest.util.DynamicArray;

/**
 * This class captures the state of the Harness for the purpose of
 * presenting the user with useful status/progress info.
 */

class MonitorState {
    MonitorState(Harness h) {
	h.addObserver(dispatcher);
	harness = h;
	startTime = -1l;
	finishTime = -1l;
	stats = new int[Status.NUM_STATES];
    }

    /**
     * Monitor the harness state.
     * By definition, all observations will be broadcast on the event thread.
     * Be short and timely in processing these events.
     */
    interface Observer {
	/**
	 * A new test run is starting.
	 */
	public void starting();

	/**
	 * The tests have stopped running, and the harness is now doing
	 * cleanup.
	 */
	public void postProcessing();

	/**
	  * A test run is being stopped by something.  This is not the same
	  * as finishing.
	  */
	public void stopping();

	/**
	 * A test run is finishing.
	 *
	 * @param allOk Did all the tests pass?
	 */
	public void finished(boolean allOk);
    }

    void addObserver(Observer o) {
	obs = (Observer[])DynamicArray.append(obs, o);
    }

    void removeObserver(Observer o) {
	obs = (Observer[])DynamicArray.remove(obs, o);
    }

    /**
     * Is the harness currently doing a test run.
     */
    boolean isRunning() {
	return running;
    }

    /**
     * Time since the start of the run.
     * 
     * @return Zero at the instant we start a run or when no run has been
     *         performed yet.
     */
    long getElapsedTime() {
	/*
	// we don't have a valid time available
	if (startTime == -1)
	    return 0l;

	// provide the current or last time
	long now = System.currentTimeMillis();

	// answer depends whether we are running now or not
	if (finishTime < 0) {
	    // we are still running
	    return now - startTime;
	}
	else {
	    return finishTime - startTime;
	}
	*/

	return harness.getElapsedTime();
    }

    /**
     * Find out the estimated time required to complete the remaining tests.
     *
     * @return A time estimate in milliseconds.  Zero if no run is in progress or
     *         no estimate is available.
     */
    synchronized long getEstimatedTime() {
	return harness.getEstimatedTime();
    }

    /**
     * Discover the number of tests found so far in this testsuite.
     *
     * @return estimated number of tests in the entire testsuite, -1 if unknown
     */
    synchronized int getEstimatedTotalTests() {
	WorkDirectory wd = harness.getResultTable().getWorkDir();
	if (wd == null)
	    return -1;

	int count = wd.getTestSuiteTestCount();

	if (count == -1)
	    count = wd.getTestSuite().getEstimatedTestCount();

	// make sure we return -1
	return (count < 0 ? -1 : count);
    }

    /**
     * Discover the number of tests found so far by the system to be executed
     * during this run.
     *
     * @return Number of tests found so far to be executed based on the current
     *         parameters.
     */
    synchronized int getTestsFoundCount() {
	return harness.getTestsFoundCount();
    }

    /**
     * Convienience method to determine how many tests have finished for the
     * current or last run.
     */
    synchronized int getTestsDoneCount() {
	int done = 0;
	for (int i = 0; i < stats.length; i++)
	    done += stats[i];
	
	return done;
    }

    /**
     * Convienience method to determine how many tests have still need
     * to be run for the current or last run.
     */
    synchronized int getTestsRemainingCount() {
	return getTestsFoundCount() - getTestsDoneCount();
    }

    /**
     * Get the test run statistics.
     *
     * @return array of counts, based on the indexes of Status types.
     */
    synchronized int[] getStats() {
	// we could consider trusting the client and not doing the copy
	int[] copy = new int[stats.length];
	System.arraycopy(stats, 0, copy, 0, stats.length);
	return copy;
    }

    TestResult[] getRunningTests() {
	TestResult[] trs = null;

	synchronized (vLock) {
	    trs = new TestResult[runningTests.size()];
	    runningTests.copyInto(trs);
	}

	return trs;
    }

    /**
     * Provide synchronized access to the statistics array.
     */
    private synchronized void incrementStat(int state) {
	// this mainly called by the harness observer implementation
	stats[state]++;
    }

    private synchronized void resetStats() {
	stats = new int[Status.NUM_STATES];
    }

    class Dispatcher implements Harness.Observer {

	public void startingTestRun(Parameters p) {
	    running = true;

	    startTime = System.currentTimeMillis();
	    finishTime = -1l;
	    resetStats();

	    synchronized (vLock) {
		runningTests.clear();
	    }

	    notifySimple(0);
	}

	public void startingTest(TestResult tr) {
	    synchronized (vLock) {
		runningTests.add(tr);
	    }
	}

	public void finishedTest(TestResult tr) {
	    synchronized (vLock) {
		runningTests.remove(tr);
	    }

	    incrementStat(tr.getStatus().getType());
	}

	public void stoppingTestRun() {
	    notifySimple(1);
	}

	public void finishedTesting() {
	    finishTime = System.currentTimeMillis();

	    synchronized (vLock) {
		runningTests.clear();
	    }

	    notifySimple(2);
	}

	public void finishedTestRun(boolean allOk) {
	    running = false;
	    notifyComplete(allOk);
	}

	public void error(String msg) {
	}

	// --------- private ----------
	private void notifySimple(final int which) {
	    if (!EventQueue.isDispatchThread()) {
		Runnable cmd = new Runnable() {
		    public void run() {
			MonitorState.Dispatcher.this.notifySimple(which);
		    }   // run()
		};	// end anon. class

		EventQueue.invokeLater(cmd);
	    }
	    else {	// now on event thread
		for (int i = 0; i < obs.length; i++)
		    switch (which) {
			case 0: 
			    obs[i].starting(); break;
			case 1:
			    obs[i].stopping(); break;
			case 2:
			    obs[i].postProcessing(); break;
			default:
			    throw new IllegalStateException();
		    }	// switch
		// end for
	    }	// else
	}

	private void notifyComplete(final boolean allOk) {
	    if (!EventQueue.isDispatchThread()) {
		Runnable cmd = new Runnable() {
		    public void run() {
			MonitorState.Dispatcher.this.notifyComplete(allOk);
		    }   // run()
		};	// end anon. class

		EventQueue.invokeLater(cmd);
	    }
	    else {	// now on event thread
		for (int i = 0; i < obs.length; i++)
		    obs[i].finished(allOk);
	    }
	}
    }

    // instance vars for MonitorState
    private Harness harness;
    private boolean running;
    private Observer[] obs = new Observer[0];
    private volatile int[] stats;

    private Vector runningTests = new Vector(1,2);
    private final Object vLock = new Object();

    /**
     * Basis for the elapsed time.
     */
    private long startTime;
    private long finishTime;	// only used if startTime != -1 and finishTime >= 0
    private Dispatcher dispatcher = new Dispatcher();
}

