package de.ugoe.cs.util.console;

import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;

/**
 * <p>
 * Executes commands. The commands have to implement the {@link Command}
 * interface and be in packages registered using addCommandPackage().
 * Additionally, default commands are implemented in the
 * de.ugoe.cs.util.console.defaultcommands package.
 * </p>
 * <p>
 * This class is implemented as a <i>Singleton</i>.
 * </p>
 * 
 * @author Steffen Herbold
 * @version 1.0
 */
public class CommandExecuter {

	/**
	 * <p>
	 * Handle of the CommandExecuter instance.
	 * </p>
	 */
	private final static CommandExecuter theInstance = new CommandExecuter();

	/**
	 * <p>
	 * Prefix of all command classes.
	 * </p>
	 */
	private static final String cmdPrefix = "CMD";

	/**
	 * <p>
	 * Name of the package for default commands.
	 * </p>
	 */
	private static final String defaultPackage = "de.ugoe.cs.util.console.defaultcommands";

	/**
	 * <p>
	 * List of packages in which commands may be defined. The exec methods trys
	 * to load command from these packages in the order they have been added.
	 * </p>
	 * <p>
	 * The de.ugoe.cs.util.console.defaultcommands package has always lowest
	 * priority, unless it is specifically added.
	 * </p>
	 */
	private List<String> commandPackageList;

	/**
	 * <p>
	 * Returns the instance of CommandExecuter. If no instances exists yet, a
	 * new one is created.
	 * </p>
	 * 
	 * @return the instance of CommandExecuter
	 */
	public static synchronized CommandExecuter getInstance() {
		return theInstance;
	}

	/**
	 * <p>
	 * Creates a new CommandExecuter. Private to prevent multiple instances
	 * (Singleton).
	 * </p>
	 */
	private CommandExecuter() {
		commandPackageList = new ArrayList<String>();
	}

	/**
	 * <p>
	 * Adds a package that will be used by {@link #exec(String)} to load command
	 * from.
	 * </p>
	 * 
	 * @param pkg
	 *            package where commands are located
	 * @throws InvalidParameterException
	 *             thrown if the package name is null or empty string
	 */
	public void addCommandPackage(String pkg) {
		if ("".equals(pkg) || pkg == null) {
			throw new InvalidParameterException(
					"package name must not be null or empty string");
		}
		commandPackageList.add(pkg);
	}

	/**
	 * <p>
	 * Executes the command defined by string. A command has the following form
	 * (mix of EBNF and natural language):
	 * </p>
	 * <code>
	 * &lt;command&gt; := &lt;commandname&gt;&lt;whitespace&gt;{&lt;parameter&gt;}<br>
	 * &lt;commandname&gt; := String without whitespaces. Has to be a valid Java class name<br>
	 * &lt;parameter&gt; := &lt;string&gt;|&lt;stringarray&gt;<br>
	 * &lt;string&gt; := &lt;stringwithoutwhitespaces&gt;|&lt;stringwithwhitespaces&gt;
	 * &lt;stringwithoutwhitespaces&gt; := a string without whitespaces<br>
	 * &lt;stringwithoutwhitespaces&gt; := a string, that can have whitespaces, but must be in double quotes<br>
	 * &lt;stringarray&gt; := "["&lt;string&gt;{&lt;whitespace&gt;&lt;string&gt;"]"
	 * </code>
	 * 
	 * @param command
	 *            the command as a string
	 */
	public void exec(String command) {
		Console.commandNotification(command);
		Command cmd = null;
		CommandParser parser = new CommandParser();
		parser.parse(command);
		for (int i = 0; cmd == null && i < commandPackageList.size(); i++) {
			cmd = loadCMD(commandPackageList.get(i) + "." + cmdPrefix
					+ parser.getCommandName());
		}
		if (cmd == null) { // check if command is available as default command
			cmd = loadCMD(defaultPackage + "." + cmdPrefix
					+ parser.getCommandName());
		}
		if (cmd == null) {
			Console.println("Unknown command");
		} else {
			try {
				cmd.run(parser.getParameters());
			} catch (InvalidParameterException e) {
				cmd.help();
			}
		}
	}

	/**
	 * <p>
	 * Helper method that loads a class and tries to cast it to {@link Command}.
	 * </p>
	 * 
	 * @param className
	 *            qualified name of the class (including package name)
	 * @return if class is available and implement {@link Command} and instance
	 *         of the class, null otherwise
	 */
	private Command loadCMD(String className) {
		Command cmd = null;
		try {
			Class<?> cmdClass = Class.forName(className);
			cmd = (Command) cmdClass.newInstance();
		} catch (NoClassDefFoundError e) {
			String[] splitResult = e.getMessage().split("CMD");
			String correctName = splitResult[splitResult.length - 1].replace(
					")", "");
			Console.println("Did you mean " + correctName + "?");
		} catch (ClassNotFoundException e) {
		} catch (IllegalAccessException e) {
		} catch (InstantiationException e) {
		} catch (ClassCastException e) {
			Console.traceln(Level.WARNING, className + "found, but does not implement Command");
		}
		return cmd;
	}
}
