/*
 * $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.lib;

import java.io.PrintWriter;
import com.sun.javatest.Status;

/**
 * When executing multiple test cases in the same test class, it is usually
 * easier for each test case to return a Status object representing whether that
 * individual test case passed or failed.  However, combining those individual
 * Status objects into one Status object to represent the overall Status of the
 * tests executed can be difficult.  This test library is designed to solve the
 * problem of generating an aggregate or overall Status from multiple Status
 * objects.  The static method overallStatus is designed to take an array of
 * Status objects from individual test cases and generate a Status object that
 * correctly reflects the overall status of all the individual the test
 * cases executed.
 *
 * <P>The rule for how MultiStatus calculates the overall Status of an array of
 * Status objects is based on the following precedence:
 * <BLOCKQUOTE>
 * If any of the test cases return a Status.FAILED, then the overall status is
 * Status.FAILED.<BR>
 * If all test cases return Status.PASSED, then the overall status is
 * Status.PASSED.<BR>
 * If at least one test case returns either a null Status or some other Status,
 * the overall status is Status.FAILED.
 * </BLOCKQUOTE>
 *
 * <P>For an example of how to use this library see the UmbrellaTest library or the
 * JCK test case: <TT>tests/api/java_lang/Double/SerializeTests.html</TT>.
 */
public class MultiStatus {

    /**
     * Create a MultiStatus object to accumulate individual Status objects.
     */
    public MultiStatus() {
    }

    /**
     * Create a MultiStatus object to accumulate individual Status objects.
     * @param out    A stream to which the report the outcome of the tests.
     *                   If the stream is null, no reporting is done.
     */
    public MultiStatus(PrintWriter out) {
        this.out = out;
    }

    /**
     * Get the number of individual test results that have been added.
     * @return the number of individual results that have been added.
     */
    public int getTestCount() {
        return iTestCases;
    }

    /**
     * Add another test result into the set for consideration.
     *
     * @param testID A name for this test case.  Should not be null.
     * @param status The outcome of this test case
     */
    public void add(String testID, Status status) {
        if (out != null) {
            out.println(testID + ": " + status);
        }

        ++iTestCases;

        if (status != null) {
            int t = status.getType();

            switch (t) {
            case Status.PASSED:
                ++iPassed;
                break;

            case Status.FAILED:
                ++iFail;
                break;

            case Status.ERROR:
                ++iError;
                break;

            default:
                ++iBad;
                break;
            }

            if (t != Status.PASSED && firstTestCase.length() == 0)
                firstTestCase = testID;
        }
    }

    /**
     * Get the aggregate outcome of all the outcomes passed to "add".
     * @return the aggregate outcome
     */
    public Status getStatus() {

        // If stream not null, flush it
        if( out != null ) {
            out.flush();
        }

        String summary;
        if (iTestCases == 0)
            summary = "No tests cases found (or all test cases excluded.)";
        else {
            summary = "test cases: " + iTestCases;
            if (iPassed > 0) {
                if (iPassed == iTestCases)
                    summary += "; all passed";
                else
                    summary += "; passed: " + iPassed;
            }

            if (iFail > 0) {
                if (iFail == iTestCases)
                    summary += "; all failed";
                else
                    summary += "; failed: " + iFail;
            }

            if (iError > 0) {
                if (iError == iTestCases)
                    summary += "; all had an error";
                else
                    summary += "; error: " + iError;
            }

            if (iBad > 0) {
                if (iBad == iTestCases)
                    summary += "; all bad";
                else
                    summary += "; bad status: " + iBad;
            }
        }

        /* Return a status object that reflects the aggregate of the various test cases. */

        /* At least one test case was bad */
        if (iBad > 0) {
            return Status.error(summary + "; first bad test case result found: " + firstTestCase);
        }
        /* At least one test case was bad */
        else if (iError > 0) {
            return Status.error(summary + "; first test case with error: " + firstTestCase);
        }
        /* At least one test case failed */
        else if (iFail > 0) {
            return Status.failed(summary + "; first test case failure: " + firstTestCase);
        }
        /* All test cases passed */
        else {
            return Status.passed(summary);
        }
    }


    /**
     * Generates a Status object that reflects an array of Status objects.
     * Uses the algorithm above to generate an overall status from an array of
     * Status objects.  This method prints out the individual Status values from
     * each test case to the PrintWriter supplied.  If the PrintWriter is null,
     * no output is generated.
     *
     * @param testIDs an array of names used to identify the individual test cases.
     * @param status an array of Status objects giving the outcomes of the individual test cases.
     * @param out a PrintWriter that can be used to output the individual test case
     *            status values. If null, no output is generated.
     * @return the aggregate status of the array of Status objects.
     */
    public static Status overallStatus(String testIDs[], Status status[], PrintWriter out) {

        /* Check the number of tests against the number of statuses */
        if( testIDs.length != status.length ) {
            return Status.failed( "mismatched array sizes; test cases: " + testIDs.length +
                                  " statuses: " + status.length );
        }

        /* Loop through status objects, check types,
         * increment appropriate counters, and identify the
         * first test that should be listed in the aggregate status.
         */

        MultiStatus ms = new MultiStatus(out);
        for( int i = 0; i < status.length; ++i ) {
            ms.add(testIDs[i], status[i]);
        }

        return ms.getStatus();
    }


    /**
     * Generates a Status object that reflects an array of Status objects.
     * Uses the algorithm above to generate an overall status from an array of
     * Status objects.  This method does not output any information
     *
     * @param testIDs an array of names used to identify the individual test cases.
     * @param status an array of Status objects giving the outcomes of the individual test cases.
     * @return overall status of the specified array of Status objects.
     */
    public static Status overallStatus(String testIDs[], Status status[]) {
        return MultiStatus.overallStatus(testIDs, status, null);
    }

    // These values accumulate the aggregate outcome, without requiring
    // the individual outcomes be stored
    private int iTestCases = 0;
    private int iPassed = 0;
    private int iFail = 0;
    private int iError = 0;
    private int iBad = 0;
    private String firstTestCase = "";

    private PrintWriter out = null;
}
