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

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Iterator;

import com.sun.javatest.TestDescription;
import com.sun.javatest.TestFinder;

/**
 * ShowTests is a utility program to show the tests that are read by
 * a test finder.
 */
public class ShowTests
{
    /**
     * This exception is used to report bad command line arguments.
     */
    public class BadArgs extends Exception {
        /**
         * Create a BadArgs exception.
         * @param msg A detail message about an error that has been found.
         */
        BadArgs(String msg) {
            super(msg);
        }
    }

    /**
     * This exception is used to report problems that occur while running.
     */

    public class Fault extends Exception {
        /**
         * Create a Fault exception.
         * @param msg A detail message about a fault that has occurred.
         */
        Fault(String msg) {
            super(msg);
        }
    }

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

    /**
     * Standard program entry point.
     * @param args      An array of strings, typically provided via the command line.
     * The arguments should be of the form:<br>
     * <em>[options]</em> <em>testsuite</em>
     * <table><tr><th colspan=2>Options</th></tr>
     * <tr><td>-finder <em>finderClass</em> <em>finderArgs</em> <em>...</em> -end
     *          <td>The name of a test finder class and any arguments it might take.
     *          The results of reading this test finder will be stored in the
     *          output file.
     * <tr><td>-initial <em>initial-file</em>
     *          <td>An initial file within the test suite at which to start reading tests.
     * <tr><td>-o <em>output-file</em>
     *          <td>The output file in which to write the results. If omitted,
     *          The results will be written to the standard console output stream.
     * <tr><td>-nodes
     *          <td>By default, only the names of the tests are output.
     *          If you specify this option, the names of the parent directories
     *          will be displayed as well.
     * <tr><td>-full]tests
     *          <td>By default, only the names of the tests are output.
     *          If you specify this option, the contents of the test descriptions
     *          will be displayed as well.
     * </table>
     */
    public static void main(String[] args) {
        try {
            if (args.length == 0)
                usage(System.out);
            else {
                ShowTests m = new ShowTests();
                m.run(args);
            }
        }
        catch (BadArgs e) {
            System.err.println("Bad Arguments: " + e.getMessage());
            usage(System.err);
            System.exit(1);
        }
        catch (Fault f) {
            System.err.println("Error: " + f.getMessage());
            System.exit(2);
        }
        catch (IOException e) {
            System.err.println("Error: " + e);
            System.exit(3);
        }
    }

    /**
     * Print out command-line help.
     */
    private static void usage(PrintStream out) {
        String prog = System.getProperty("program", "java " + ShowTests.class.getName());
        out.println("Usage:");
        out.println("  " + prog + " [options]  test-suite");
        out.println("Options:");
        out.println("  -finder finderClass finderArgs... -end");
        out.println("                          specify the test finder to be used");
        out.println("  -initial initial-file   specify a starting point (optional)");
        out.println("  -o output-file          output file (default is standard output)");
        out.println("  -nodes                  show nodes in the tree");
        out.println("  -fulltests              show contents of tests");
    }

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

    /**
     * Main work method.
     * Reads all the arguments on the command line, makes sure a valid
     * testFinder is available, and then calls methods to create the tree of tests
     * and then write the binary file.
     *
     * @param args      An array of strings, typically provided via the command line
     * @throws ShowTests.BadArgs
     *                  if a problem is found in the arguments provided
     * @throws ShowTests.Fault
     *                  if a fault is found while running
     * @throws IOException
     *                  if a problem is found while trying to read a file
     * @see #main
     */
    public void run(String[] args) throws BadArgs, Fault, IOException {
        File testSuite = null;
        String finder = null;
        String[] finderArgs = null;
        File outFile = null;
        File initialFile = null;

        for (int i = 0; i < args.length; i++) {
            if (args[i].equalsIgnoreCase("-finder") && (i + 1 < args.length)) {
                finder = args[++i];
                int j = ++i;
                while ((i < args.length - 1) && !(args[i].equalsIgnoreCase("-end")))
                    ++i;
                finderArgs = new String[i - j];
                System.arraycopy(args, j, finderArgs, 0, finderArgs.length);
            }
            else if (args[i].equalsIgnoreCase("-initial") && (i + 1 < args.length)) {
                initialFile = new File(args[++i]);
            }
            else if (args[i].equalsIgnoreCase("-o") && (i + 1 < args.length)) {
                outFile = new File(args[++i]);
            }
            else if (args[i].equalsIgnoreCase("-nodes")) {
                nodes = true;
            }
            else if (args[i].equalsIgnoreCase("-fulltests")) {
                fullTests = true;
            }
            else if (args[i].startsWith("-") ) {
                throw new BadArgs(args[i]);
            }
            else
                testSuite = new File(args[i]);
        }

        if (finder == null)
            throw new BadArgs("no test finder specified");

        if (testSuite == null)
            throw new BadArgs("testsuite.html file not specified");

        testFinder = initializeTestFinder(finder, finderArgs, testSuite);

        if (initialFile == null)
            initialFile = testFinder.getRoot(); // equals testSuite, adjusted by finder as necessary .. e.g. for dirWalk, webWalk etc

        if (outFile == null)
            out = System.out;
        else
            out = new PrintStream(new BufferedOutputStream(new FileOutputStream(outFile)));

        // read the tests and write them to output
        list(initialFile);
    }

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

    /**
     * Creates and initializes an instance of a test finder
     *
     * @param finder The class name of the required test finder
     * @param args any args to pass to the TestFinder's init method.
     * @param ts The testsuite root file
     * @return The newly created TestFinder.
     */
    private TestFinder initializeTestFinder(String finder, String[] args, File ts) throws Fault {
        TestFinder testFinder;

        try {
            Class c = Class.forName(finder);
            testFinder = (TestFinder) (c.newInstance());
            testFinder.init(args, ts, null);
        }
        catch (ClassNotFoundException e) {
            throw new Fault("Error: Can't find class for TestFinder specified");
        }
        catch (InstantiationException e) {
            throw new Fault("Error: Can't create new instance of TestFinder");
        }
        catch (IllegalAccessException e) {
            throw new Fault("Error: Illegal Access Exception");
        }
        catch (TestFinder.Fault e) {
            throw new Fault("Error: Can't initialize test-finder: " + e.getMessage());
        }

        return testFinder;
    }

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

    private void list(File file) {
        if (nodes)
            out.println(file);

        testFinder.read(file);
        TestDescription[] tests = testFinder.getTests();
        File[] files = testFinder.getFiles();

        if (tests != null) {
            for (int i = 0; i < tests.length; i++) {
                TestDescription td = tests[i];
                out.println("    " + td.getRootRelativeURL());
                if (fullTests) {
                    for (Iterator iter = td.getParameterKeys(); iter.hasNext(); ) {
                        String key = (String) (iter.next());
                        String value = td.getParameter(key);
                        out.print("        ");
                        out.print(key);
                        pad(key, 15);
                        out.print(value);
                        out.println();
                    }
                }
            }
        }

        if (files != null) {
            for (int i = 0; i < files.length; i++)
                list(files[i]);
        }
    }

    void pad(String s, int length) {
        for (int i = s.length(); i < length; i++)
            out.write(' ');
        out.write(' ');
    }

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

    private TestFinder testFinder;
    private PrintStream out;
    private boolean nodes;
    private boolean fullTests;
}
