package junit.framework;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;

/**
 * A <code>TestResult</code> collects the results of executing
 * a test case. It is an instance of the Collecting Parameter pattern.
 * The test framework distinguishes between <i>failures</i> and <i>errors</i>.
 * A failure is anticipated and checked for with assertions. Errors are
 * unanticipated problems like an {@link ArrayIndexOutOfBoundsException}.
 *
 * @see Test
 */
public class TestResult {
    protected List<TestFailure> fFailures;
    protected List<TestFailure> fErrors;
    protected List<TestListener> fListeners;
    protected int fRunTests;
    private boolean fStop;

    public TestResult() {
        fFailures = new ArrayList<TestFailure>();
        fErrors = new ArrayList<TestFailure>();
        fListeners = new ArrayList<TestListener>();
        fRunTests = 0;
        fStop = false;
    }

    /**
     * Adds an error to the list of errors. The passed in exception
     * caused the error.
     */
    public synchronized void addError(Test test, Throwable e) {
        fErrors.add(new TestFailure(test, e));
        for (TestListener each : cloneListeners()) {
            each.addError(test, e);
        }
    }

    /**
     * Adds a failure to the list of failures. The passed in exception
     * caused the failure.
     */
    public synchronized void addFailure(Test test, AssertionFailedError e) {
        fFailures.add(new TestFailure(test, e));
        for (TestListener each : cloneListeners()) {
            each.addFailure(test, e);
        }
    }

    /**
     * Registers a TestListener.
     */
    public synchronized void addListener(TestListener listener) {
        fListeners.add(listener);
    }

    /**
     * Unregisters a TestListener.
     */
    public synchronized void removeListener(TestListener listener) {
        fListeners.remove(listener);
    }

    /**
     * Returns a copy of the listeners.
     */
    private synchronized List<TestListener> cloneListeners() {
        List<TestListener> result = new ArrayList<TestListener>();
        result.addAll(fListeners);
        return result;
    }

    /**
     * Informs the result that a test was completed.
     */
    public void endTest(Test test) {
        for (TestListener each : cloneListeners()) {
            each.endTest(test);
        }
    }

    /**
     * Gets the number of detected errors.
     */
    public synchronized int errorCount() {
        return fErrors.size();
    }

    /**
     * Returns an Enumeration for the errors.
     */
    public synchronized Enumeration<TestFailure> errors() {
        return Collections.enumeration(fErrors);
    }


    /**
     * Gets the number of detected failures.
     */
    public synchronized int failureCount() {
        return fFailures.size();
    }

    /**
     * Returns an Enumeration for the failures.
     */
    public synchronized Enumeration<TestFailure> failures() {
        return Collections.enumeration(fFailures);
    }

    /**
     * Runs a TestCase.
     */
    protected void run(final TestCase test) {
        startTest(test);
        Protectable p = new Protectable() {
            public void protect() throws Throwable {
                test.runBare();
            }
        };
        runProtected(test, p);

        endTest(test);
    }

    /**
     * Gets the number of run tests.
     */
    public synchronized int runCount() {
        return fRunTests;
    }

    /**
     * Runs a TestCase.
     */
    public void runProtected(final Test test, Protectable p) {
        try {
            p.protect();
        } catch (AssertionFailedError e) {
            addFailure(test, e);
        } catch (ThreadDeath e) { // don't catch ThreadDeath by accident
            throw e;
        } catch (Throwable e) {
            addError(test, e);
        }
    }

    /**
     * Checks whether the test run should stop.
     */
    public synchronized boolean shouldStop() {
        return fStop;
    }

    /**
     * Informs the result that a test will be started.
     */
    public void startTest(Test test) {
        final int count = test.countTestCases();
        synchronized (this) {
            fRunTests += count;
        }
        for (TestListener each : cloneListeners()) {
            each.startTest(test);
        }
    }

    /**
     * Marks that the test run should stop.
     */
    public synchronized void stop() {
        fStop = true;
    }

    /**
     * Returns whether the entire test was successful or not.
     */
    public synchronized boolean wasSuccessful() {
        return failureCount() == 0 && errorCount() == 0;
    }
}
