/*
 * $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.net.URL;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.Vector;

import com.sun.javatest.InterviewParameters;
import com.sun.javatest.util.I18NResourceBundle;

/**
 * A class to represent a command to be executed.
 * Commands are typically read from the command line or from command files.
 */
public abstract class Command 
{
    /**
     * This exception is used to report problems with a specific command.
     */
    public class Fault extends Exception
    {
	/**
	 * Create a Fault.
	 * @param i18n A resource bundle in which to find the detail message.
	 * @param s The key for the detail message.
	 */
	public Fault(I18NResourceBundle i18n, String s) {
	    super(i18n.getString(s));
	}

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

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

	/**
	 * Create a Fault, by wrapping a CommandContext Fault.
	 * The message string will be propogated directly; 
	 * the argument fault will be set as the cause for this fault.
	 * @param e A CommandContext.Fault to wrap.
	 */
	public Fault(CommandContext.Fault e) {
	    super(e.getMessage(), e);
	}

	/** 
	 * Get the command that created this fault.
	 * @return the command that created this fault
	 */
	public Command getCommand() {
	    return Command.this;
	}

    }

    /**
     * Create an instance of a command.
     * @param name The name for this command. 
     * The name will be saved as the first entry as the argument array.
     */
    protected Command(String name) {
	args = new Vector();
	args.add(name);
    }

    /**
     * Record another argument in the argument array.
     * @param arg the argument to be added
     */
    protected void addArg(String arg) {
	args.add(arg);
    }

    /**
     * Get another argument from the iterator, and add it to the argument array.
     * @param argIter the iterator from which to get the next argument
     * @return the next argument from the iterator
     */
    protected String nextArg(Iterator argIter) {
	String s = (String) argIter.next();
	addArg(s);
	return s;
    }

    /**
     * Back up the iterator to reject an argument, and remove the corresponding
     * entry from the argument array.
     * @param argIter the iterator from which teh argument was obtained
     */
    protected void putbackArg(ListIterator argIter) {
	argIter.previous();
	args.remove(args.size() - 1);
    }

    /**
     * Get the array of arguments for this command.
     * The first element in the array will be the command name;
     * the subsequent arguments will be the ones added by the addArg method.
     * @return the array of arguments for this command
     */
    public String[] getArgs() {
	String[] a = new String[args.size()];
	args.copyInto(a);
	return a;
    }

    /**
     * Get a printable representation of this command.
     * The string is composed of the entries in the argument array.
     * @return a printable representation of this command
     */
    public String toString() {
	StringBuffer sb = new StringBuffer();
	for (int i = 0; i < args.size(); i++) {
	    if (sb.length() > 0)
		sb.append(' ');
	    String arg = (String) (args.elementAt(i));
	    boolean hasSpace = (arg.indexOf(' ') != -1);
	    boolean hasQuote = (arg.indexOf('"') != -1);
	    boolean hasEscape = (arg.indexOf('\\') != -1);
	    if (hasSpace)
		sb.append('"');
	    if (hasQuote || hasEscape) {
		for (int ci = 0; ci < arg.length(); ci++) {
		    char c = arg.charAt(ci);
		    if (c == '"' || c == '\\')
			sb.append('\\');
		    sb.append(c);
		}
	    }
	    else
		sb.append(arg);
	    if (hasSpace)
		sb.append('"');
	}
	return sb.toString();
    }

    /**
     * Get the desktop mode for this command. Valid responses are one of
     * DEFAULT_DTMODE, DESKTOP_NOT_REQUIRED_DTMODE, DESKTOP_REQUIRED_DTMODE.
     * The default is DESKTOP_NOT_REQUIRED_DTMODE if isActionCommand is true,
     * and DESKTOP_DEFAULT_DTMODE otherwise.
     * @return a value indicating the desktop mode for this command
     * @see #DEFAULT_DTMODE
     * @see #DESKTOP_NOT_REQUIRED_DTMODE
     * @see #DESKTOP_REQUIRED_DTMODE
     */
    public int getDesktopMode() {
	return (isActionCommand() ? DESKTOP_NOT_REQUIRED_DTMODE : DEFAULT_DTMODE);
    }

    /**
     * Get the classpath to load the custom splash screen from.
     * At this location, it is expected that a resource bundle prefixed with
     * "splash" will be available.  The search strategy given in ResourceBundle
     * will be used, with the returned File as the classpath for the class loader.
     * The limited classpath/classloader is used to make this operation as fast
     * as possible, rather than requiring that the command's entire context be
     * loaded.
     *
     * In the resource bundle, there should be a property named
     * <code>startup.icon</code>.
     * @return the location of the splash screen resource bundle
     * @see java.util.ResourceBundle
     * @since 4.0
     */
    public URL getCustomSplash() {
        return null;
    }

    /**
     * A value to indicate that a command accepts the default desktop mode.
     * This means that it neither requires nor discourages the use of a desktop
     * for its use.
     * @see #getDesktopMode
     * @see #DESKTOP_NOT_REQUIRED_DTMODE
     * @see #DESKTOP_REQUIRED_DTMODE
     */
    public static final int DEFAULT_DTMODE = 0;

    /**
     * A value to indicate that a command does not require the use of
     * a desktop to function.
     * @see #getDesktopMode
     * @see #DEFAULT_DTMODE
     * @see #DESKTOP_REQUIRED_DTMODE
     */
    public static final int DESKTOP_NOT_REQUIRED_DTMODE = 1;

    /**
     * A value to indicate that a command requires the use of
     * a desktop to function.
     * @see #getDesktopMode
     * @see #DEFAULT_DTMODE
     * @see #DESKTOP_NOT_REQUIRED_DTMODE
     */
    public static final int DESKTOP_REQUIRED_DTMODE = 2;

    /**
     * Check whether this command is an action command or not. Action commands
     * are those that do work such as running tests, writing a report, etc.
     * The default implementation is to return false.
     * @return true if this command is an action command, and false otherwise
     */
    public boolean isActionCommand() {
	return false;
    }

    /**
     * Execute the work embodied by this command, using the given command context.
     * @param ctx context information that may be set up by preceding commands.
     * @throws Command.Fault if there is an error while executing this command
     */
    public abstract void run(CommandContext ctx) throws Fault;

    /**
     * A convenience method to get the configuration from a command context,
     * and rewrapping any execption that might occur.
     * @param ctx the command context from which to get the configuration
     * @return the current configuration from the command context
     * @throws Command.Fault if there is a problem obtaining or evaluating 
     * the configuration.
     */
    protected InterviewParameters getConfig(CommandContext ctx)
	throws Command.Fault 
    {
	try {
	    return ctx.getConfig();
	}
	catch (CommandContext.Fault e) {
	    throw new Fault(e);
	}
    }

    private Vector args;
}
