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

import java.awt.EventQueue;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URL;
import java.text.MessageFormat;
import java.util.Set;

import com.sun.javatest.Harness;
import com.sun.javatest.InterviewParameters;
import com.sun.javatest.JavaTestError;
import com.sun.javatest.JavaTestSecurityManager;
import com.sun.javatest.ProductInfo;
import com.sun.javatest.Status;
import com.sun.javatest.util.Debug;
import com.sun.javatest.util.ExitCount;
import com.sun.javatest.util.I18NResourceBundle;

/**
 * The main program class for JT Harness.
 */
public class Main
{

    /**
     * Thrown when a bad command line argument is encountered.
     */
    public static class Fault extends Exception
    {
	/**
	 * Create a BadArgs exception.
	 * @param i18n A resource bundle in which to find the detail message.
	 * @param key The key for the detail message.
	 */
	public Fault(I18NResourceBundle i18n, String key) {
	    super(i18n.getString(key));
	}

	/**
	 * Create a BadArgs exception.
	 * @param i18n A resource bundle in which to find the detail message.
	 * @param key The key for the detail message.
	 * @param arg An argument to be formatted with the detail message by
	 * {@link java.text.MessageFormat#format}
	 */
	public Fault(I18NResourceBundle i18n, String key, Object arg) {
	    super(i18n.getString(key, arg));
	}

	/**
	 * Create a BadArgs exception.
	 * @param i18n A resource bundle in which to find the detail message.
	 * @param key The key for the detail message.
	 * @param args An array of arguments to be formatted with the detail message by
	 * {@link java.text.MessageFormat#format}
	 */
	public Fault(I18NResourceBundle i18n, String key, Object[] args) {
	    super(i18n.getString(key, args));
	}
    }


    /**
     * Run JT Harness with command-line args.
     * @param args Arguments, per the command-line spec
     */
    public static void main(String[] args) {
	tracing = Boolean.getBoolean("javatest.trace.startup");
	if (tracing)
	    traceStartTime = System.currentTimeMillis();

	String javaVersion = System.getProperty("java.version");
	if (javaVersion != null) {
	    String[] oldVersions = {"1.0", "1.1", "1.2", "1.3", "1.4"};
	    for (int i = 0; i < oldVersions.length; i++) {
		if (javaVersion.startsWith(oldVersions[i])) {
		    // I18N?
		    System.err.println("Please use Java(TM) Standard Edition, Version 5.0 or better to run the JT Harness(TM) harness.");
		    System.exit(1);
		}
	    }
	}
        
	main0(args);
    }

    private static void main0(String[] args) {
	Debug.setProperties(System.getProperties());

	PrintWriter out = new PrintWriter(System.err) {
		public void close() {
		    flush();
		}
	    };

	if (tracing) 
	    traceOut = out;

	try {
	    ExitCount.inc();
	    Main m = new Main();

	    CommandContext ctx = new CommandContext(out);
	    m.run(args, ctx);

	    if (tracing) 
		trace("Main.run complete");

	    out.flush(); // flush now in case ExitCount exits
	    
	    int[] stats = ctx.getTestStats();
	    int rc = (stats[Status.ERROR] > 0 ? RC_BATCH_TESTS_ERROR 
		      : stats[Status.FAILED] > 0 ? RC_BATCH_TESTS_FAILED
		      : RC_OK);

	    ExitCount.dec(true, rc);
	    
	    // all initialization is done; this thread has nothing left to do, so...
	    boolean preload = 
		System.getProperty("javatest.preload.classes", "true").equals("true");

	    if (preload) {
		if (tracing)
		    trace("preloading classes...");

		preloadUsefulClasses();

		if (tracing)
		    trace("preloaded classes");
	    }

	    out.flush();
	}
	catch (Command.Fault e) {
	    // occurs when executing default commands at end of command line
	    out.println(CommandContext.TRACE_PREFIX + e.getCommand().toString());
	    out.println(e.getMessage());
	    out.flush();
	    exit(RC_USER_ERROR);
	}
	catch (CommandContext.Fault e) {
	    // occurs when executing commands on command line or in command file
	    Throwable t = e.getCause();
	    if (t instanceof Command.Fault) {
		CommandContext ctx = e.getContext();
		boolean verboseCommands =
		    ctx.getVerboseOptionValue(CommandContext.VERBOSE_COMMANDS);
		if (!verboseCommands) {
		    Command.Fault ce = (Command.Fault) t;
		    Command c = ce.getCommand();
		    out.println(CommandContext.TRACE_PREFIX + c.toString());
		}
	    }
	    out.println(e.getMessage());
	    out.flush();
	    exit(RC_USER_ERROR);
	}
	catch (CommandParser.Fault e) {
	    // occurs when parsing commands on command line or in command file
	    Throwable t = e.getCause();
	    if (t instanceof Command.Fault) {
		Command.Fault ce = (Command.Fault) t;
		Command c = ce.getCommand();
		out.println(CommandParser.TRACE_PREFIX + c.toString());
	    }
	    out.println(e.getMessage());
	    out.flush();
	    exit(RC_USER_ERROR);
	}
	catch (Fault e) {
	    out.println(e.getMessage());
	    out.flush();
	    exit(RC_INTERNAL_ERROR);
	}
	catch (Error e) {
	    e.printStackTrace(out);
	    out.flush();
	    exit(RC_INTERNAL_ERROR);
	}
	catch (RuntimeException e) {
	    e.printStackTrace(out);
	    out.flush();
	    exit(RC_INTERNAL_ERROR);
	}
    }

    /**
     * The main routine to run JT Harness.
     * @param args Arguments for JT Harness, per the command-line spec.
     * @param out  A stream to which to write standard messages, such as
     *   command-line help, version info etc. Some error messages will
     *   still be sent to System.err.
     * @throws Main.Fault if there is a problem initializing the harness
     * @throws Command.Fault if there is a problem with a command's arguments
     * @throws CommandContext.Fault if there is a problem executing a command
     * @throws CommandParser.Fault if there is a problem parsing the args
     */
    public final void run(String[] args, PrintWriter out) 
	throws Fault, Command.Fault, CommandContext.Fault, CommandParser.Fault
    {
	run(args, new CommandContext(out));
    }
    
    /**
     * A routine to run JT Harness.
     * @param args Arguments for JT Harness, per the command-line spec.
     * @param ctx A context to use to execute the commands in the args
     * @throws Main.Fault if there is a problem initializing the harness
     * @throws Command.Fault if there is a problem with a command's arguments
     * @throws CommandContext.Fault if there is a problem executing a command
     * @throws CommandParser.Fault if there is a problem parsing the args
     */
    public final void run(String[] args, final CommandContext ctx) 
	throws Fault, Command.Fault, CommandContext.Fault, CommandParser.Fault
    {
	if (commandManagers == null) {
	    desktopManager = new DesktopManager();
	    helpManager = new HelpManager();
	    try {
		ManagerLoader ml = new ManagerLoader(CommandManager.class, System.err);
		Set mgrs = ml.loadManagers(CMDMGRLIST);
		mgrs.add(desktopManager);
		mgrs.add(helpManager);
		commandManagers = (CommandManager[]) mgrs.toArray(new CommandManager[mgrs.size()]);
		helpManager.setCommandManagers(commandManagers);
	    }
	    catch (IOException e) {
		throw new Fault(i18n, "main.cantAccessResource", new Object[] { CMDMGRLIST, e });
	    }
	}

	CommandParser p = new CommandParser(commandManagers);
	boolean urlEncoded = Boolean.getBoolean("javatest.command.urlEncoded");
	p.parse(args, urlEncoded, ctx);

	if (!initialized) {
	    File classDir = ProductInfo.getJavaTestClassDir();
	    Harness.setClassDir(classDir);
		
	    // Install our own security manager. This is primarily for self-defense
	    // against sameJVM tests, and not to prevent access outside the sandbox.
	    // Moan to stderr if it can't be installed.
	    JavaTestSecurityManager.install();

	    // mark initialization done
	    initialized = true;
	}

	final Command[] cmds = ctx.getCommands();

        // special case, when user requested info
	boolean helpInfoRequired = helpManager.isInfoRequired();
	if (helpInfoRequired) {
	    helpManager.showRequiredInfo(ctx.getLogWriter(), ctx);
	    if (cmds.length == 0) 
		return;
	}

	final Desktop desktop;
	boolean needDesktop = ctx.isDesktopRequired(); 

	if (needDesktop) {
	    if (tracing)
		trace("creating desktop...");

	    // should really be on event thread
	    desktop = desktopManager.createDesktop(ctx);

	    // show splash screen, using event thread
	    Runnable task = new Runnable() {
		public void run() {
		    if (tracing)
			trace("creating splash screen...");
                    Startup su = null;
		    // lots of GUI construction caused by the few lines below
                    for (int i = 0; i < cmds.length; i++) {
                        URL ss = cmds[i].getCustomSplash();
                        if (ss != null) {
                            su = new Startup(ss);
                            break;
                        }
                    }   // for

                    if (su == null)
                        su = new Startup();

		    final Startup splashScreen = su;

		    Thread splashTimer = new Thread() {
			    public void run() {
				// pause for 15 seconds before displaying desktop and 
				// hiding splash screen; if the desktop is made ready sooner,
				// it will be displayed sooner
				int splashSecs = 
				    Integer.getInteger("javatest.splashScreen.time", 15).intValue();
				try {
				    Thread.currentThread().sleep(splashSecs*1000);
				}
				catch (InterruptedException e) {
				}
				if (tracing)
				    trace("splash timer done: showing desktop...");

				Runnable task2 = new Runnable() {
				    public void run() {
					desktop.setVisible(true);
					splashScreen.disposeLater();
				    }
				};

				try {
				    EventQueue.invokeAndWait(task2);
				} catch (InterruptedException e) {
				    if (tracing)
					e.printStackTrace();
				} catch (java.lang.reflect.InvocationTargetException e) {
				    if (tracing)
					e.printStackTrace();
				}

			    }
			};
		    splashTimer.setDaemon(true);
		    splashTimer.start();
		}   // run()
	    };	// runnable

	    try {
		EventQueue.invokeAndWait(task);
	    } catch (InterruptedException e) {
		if (tracing)
		    e.printStackTrace();
	    } catch (java.lang.reflect.InvocationTargetException e) {
		if (tracing)
		    e.printStackTrace();
	    }

	    ctx.setDesktop(desktop);
	    // set context log to display in a log tool
	}
	else
	    desktop = null;
	
	// execute the commands on the command line
	if (tracing)
	    trace("executing command line...");

	ctx.runCommands();

	if (tracing)
	    trace("command line done");

	if (desktop != null) {
	    if (ctx.isCloseDesktopWhenDoneEnabled() 
		&& desktop.isOKToAutoExit()) {
		Runnable task = new Runnable() {
		    public void run() {
			desktop.setVisible(false);
			desktop.dispose();
		    }
		};
		try {
		    EventQueue.invokeAndWait(task);
		} catch (InterruptedException e) {
		    if (tracing)
			e.printStackTrace();
		} catch (java.lang.reflect.InvocationTargetException e) {
		    if (tracing)
			e.printStackTrace();
		}
	    }
	    else {
		final InterviewParameters ip;
		if (desktop.isEmpty() && ctx.hasConfig())
		    try {
			ip = ctx.getConfig();
		    } catch (CommandContext.Fault e) {
			throw new Fault(i18n, "main.cantGetConfig", e.getMessage());
		    }
		else
		    ip = null;

		Runnable task = new Runnable() {
		    public void run() {
			// if a desktop has been started, make sure it is not empty
			if (desktop.isEmpty()) {
			    if (ctx.hasConfig()) {
				if (tracing) {
				    trace("show specified test suite");
                                }
                                desktop.restoreHistory();
                                desktop.addDefaultTool(ip);
			    }
			    else if (desktop.isFirstTime()) {
				if (tracing)
				    trace("show default");
				desktop.addDefaultTool();
			    }
			    else {
				if (tracing)
				    trace("restore desktop");
				desktop.restore();
			    }
			}
			if (tracing)
			    trace("set desktop visible");
			desktop.setVisible(true);
		    }	// run()
		};   // Runnable

		try {
		    EventQueue.invokeAndWait(task);
		} catch (InterruptedException e) {
		    if (tracing)
			e.printStackTrace();
		} catch (java.lang.reflect.InvocationTargetException e) {
		    if (tracing)
			e.printStackTrace();
		}
	    }
	}
    }

    private CommandManager[] commandManagers;
    private HelpManager helpManager;
    private DesktopManager desktopManager;

    private static void preloadUsefulClasses() {
	//System.err.println("\n\n\n>>>>> preloading classes\n\n\n");
	new javax.swing.text.html.HTMLEditorKit().createDefaultDocument();
	com.sun.interview.Interview i = new com.sun.interview.Interview("dummy") {
		com.sun.interview.Question qEnd = new com.sun.interview.FinalQuestion(this);
		{ setFirstQuestion(qEnd); }
	    };
	new com.sun.interview.wizard.WizPane(i);
	//System.err.println("\n\n\n>>>>> preloading classes done\n\n\n");
    }

    private static void trace(String msg) {
	long now = System.currentTimeMillis();
	traceOut.println(MessageFormat.format("{0,number,[##0.0]} {1}",
				       new Object[] {
					   new Float((now - traceStartTime)/1000f),
					   msg }));
	traceOut.flush();
    }


    /**
     * Call System.exit, taking care to get permission from the
     * JavaTestSecurityManager, if it is installed.
     * @param exitCode an exit code to be passed to System.exit
     */
    private static final void exit(int exitCode) {
	// If our security manager is installed, it won't allow a call of
	// System.exit unless we ask it nicely, pretty please, thank you.
	SecurityManager sc = System.getSecurityManager();
	if (sc instanceof JavaTestSecurityManager)
	    ((JavaTestSecurityManager) sc).setAllowExit(true);
	System.exit(exitCode);
	throw new JavaTestError(i18n, "main.cannotExit.err");
    }

    private static boolean tracing;
    private static long traceStartTime;
    private static PrintWriter traceOut;
    private static boolean initialized = false;
    private static final String CMDMGRLIST = "JavaTest.cmdMgrs.lst";

    private static I18NResourceBundle i18n = I18NResourceBundle.getBundleForClass(Main.class);

    private static final int RC_GUI_ACTIVE = -1;
    private static final int RC_OK = 0;
    private static final int RC_BATCH_TESTS_FAILED = 1;
    private static final int RC_BATCH_TESTS_ERROR = 2;
    private static final int RC_USER_ERROR = 3;
    private static final int RC_INTERNAL_ERROR = 4;
}
