package de.ugoe.cs.util.console;

import java.util.Collection;
import java.util.LinkedHashSet;

import de.ugoe.cs.util.StringTools;
import de.ugoe.cs.util.console.listener.ICommandListener;
import de.ugoe.cs.util.console.listener.IErrorListener;
import de.ugoe.cs.util.console.listener.IExceptionListener;
import de.ugoe.cs.util.console.listener.IOutputListener;
import de.ugoe.cs.util.console.listener.ITraceListener;

/**
 * <p>
 * This class provides an interface for communication with the user without have
 * to rely on a specific user interface. Thus, it can be used to decouple the
 * programs logic from its user interface.
 * </p>
 * <p>
 * {@link Command} objects can be used to execute behavior.
 * </p>
 * <p>
 * To send output to the user interface, the Observer pattern is used. The
 * Console is an observable, the concrete user interfaces are the observers. The
 * interface for the observers is {@link ConsoleObserver}.
 * </p>
 * 
 * @author Steffen Herbold
 * @version 1.0
 */
public final class Console {

	/**
	 * <p>
	 * Listeners for the output stream.
	 * </p>
	 */
	private Collection<IOutputListener> outputListener;

	/**
	 * <p>
	 * Listeners for the error stream.
	 * </p>
	 */
	private Collection<IErrorListener> errorListener;

	/**
	 * <p>
	 * Listeners for the trace stream.
	 * </p>
	 */
	private Collection<ITraceListener> traceListener;

	/**
	 * <p>
	 * Listeners for the command stream.
	 * </p>
	 */
	private Collection<ICommandListener> commandListener;

	/**
	 * <p>
	 * Listeners for the exception stream.
	 * </p>
	 */
	private Collection<IExceptionListener> exceptionListener;

	/**
	 * <p>
	 * Handle of the Console instance.
	 * </p>
	 */
	private static Console theInstance = new Console();

	/**
	 * <p>
	 * Returns the instance of Console. If no instances exists yet, a new one is
	 * created.
	 * </p>
	 * 
	 * @return instance of this class
	 */
	public static Console getInstance() {
		return theInstance;
	}

	/**
	 * <p>
	 * Resets the Console by creating a new instance that has no registered
	 * observers.
	 * </p>
	 */
	public static void reset() {
		theInstance.init();
	}

	/**
	 * <p>
	 * Creates a new Console. Private to prevent multiple instances (Singleton).
	 * </p>
	 */
	private Console() {
		init();
	}

	/**
	 * <p>
	 * Initializes the console.
	 * </p>
	 */
	private void init() {
		outputListener = new LinkedHashSet<IOutputListener>();
		errorListener = new LinkedHashSet<IErrorListener>();
		traceListener = new LinkedHashSet<ITraceListener>();
		commandListener = new LinkedHashSet<ICommandListener>();
		exceptionListener = new LinkedHashSet<IExceptionListener>();
	}

	/**
	 * <p>
	 * Register a new observer.
	 * </p>
	 * 
	 * @deprecated use registerXYZListener instead
	 * @param observer
	 *            observer to be added
	 */
	public void registerObserver(ConsoleObserver observer) {
		registerOutputListener(observer);
		registerErrorListener(observer);
		registerTraceListener(observer);
		registerCommandListener(observer);
		registerExceptionListener(observer);
	}

	/**
	 * <p>
	 * Registers an output listener.
	 * </p>
	 * 
	 * @param listener
	 *            listener that is registered
	 */
	public void registerOutputListener(IOutputListener listener) {
		outputListener.add(listener);
	}

	/**
	 * <p>
	 * Registers an error listener.
	 * </p>
	 * 
	 * @param listener
	 *            listener that is registered
	 */
	public void registerErrorListener(IErrorListener listener) {
		errorListener.add(listener);
	}

	/**
	 * <p>
	 * Registers a trace listener.
	 * </p>
	 * 
	 * @param listener
	 *            listener that is registered
	 */
	public void registerTraceListener(ITraceListener listener) {
		traceListener.add(listener);
	}

	/**
	 * <p>
	 * Registers a command listener.
	 * </p>
	 * 
	 * @param listener
	 *            listener that is registered
	 */
	public void registerCommandListener(ICommandListener listener) {
		commandListener.add(listener);
	}

	/**
	 * <p>
	 * Registers an exception listener.
	 * </p>
	 * 
	 * @param listener
	 *            listener that is registered
	 */
	public void registerExceptionListener(IExceptionListener listener) {
		exceptionListener.add(listener);
	}

	/**
	 * <p>
	 * Remove an observer. If the observer is not found, nothing is done.
	 * </p>
	 * 
	 * @deprecated use removeXYZListener instead
	 * @param observer
	 *            observer to be removed
	 */
	public void deleteObserver(ConsoleObserver observer) {
		removeOutputListener(observer);
		removeErrorListener(observer);
		removeTraceListener(observer);
		removeCommandListener(observer);
		removeExceptionListener(observer);
	}

	/**
	 * <p>
	 * Removes an output listener.
	 * </p>
	 * 
	 * @param listener
	 *            listener that is removed
	 */
	public void removeOutputListener(IOutputListener listener) {
		outputListener.remove(listener);
	}

	/**
	 * <p>
	 * Removes an error listener.
	 * </p>
	 * 
	 * @param listener
	 *            listener that is removed
	 */
	public void removeErrorListener(IErrorListener listener) {
		errorListener.remove(listener);
	}

	/**
	 * <p>
	 * Removes an trace listener.
	 * </p>
	 * 
	 * @param listener
	 *            listener that is removed
	 */
	public void removeTraceListener(ITraceListener listener) {
		traceListener.remove(listener);
	}

	/**
	 * <p>
	 * Removes a command listener.
	 * </p>
	 * 
	 * @param listener
	 *            listener that is removed
	 */
	public void removeCommandListener(ICommandListener listener) {
		commandListener.remove(listener);
	}

	/**
	 * <p>
	 * Removes an exception listener.
	 * </p>
	 * 
	 * @param listener
	 *            listener that is removed
	 */
	public void removeExceptionListener(IExceptionListener listener) {
		exceptionListener.remove(listener);
	}

	/**
	 * <p>
	 * Checks if a listener is registered.
	 * </p>
	 * 
	 * @param listener
	 *            listener that is checked
	 * @return true, is listener is registered; false, otherwise
	 */
	public boolean hasOutputListener(IOutputListener listener) {
		return outputListener.contains(listener);
	}

	/**
	 * <p>
	 * Checks if a listener is registered.
	 * </p>
	 * 
	 * @param listener
	 *            listener that is checked
	 * @return true, is listener is registered; false, otherwise
	 */
	public boolean hasErrorListener(IErrorListener listener) {
		return errorListener.contains(listener);
	}

	/**
	 * <p>
	 * Checks if a listener is registered.
	 * </p>
	 * 
	 * @param listener
	 *            listener that is checked
	 * @return true, is listener is registered; false, otherwise
	 */
	public boolean hasTraceListener(ITraceListener listener) {
		return traceListener.contains(listener);
	}

	/**
	 * <p>
	 * Checks if a listener is registered.
	 * </p>
	 * 
	 * @param listener
	 *            listener that is checked
	 * @return true, is listener is registered; false, otherwise
	 */
	public boolean hasCommandListener(ICommandListener listener) {
		return commandListener.contains(listener);
	}

	/**
	 * <p>
	 * Checks if a listener is registered.
	 * </p>
	 * 
	 * @param listener
	 *            listener that is checked
	 * @return true, is listener is registered; false, otherwise
	 */
	public boolean hasExceptionListener(IExceptionListener listener) {
		return exceptionListener.contains(listener);
	}

	/**
	 * <p>
	 * Sends a message to all observers containing the message that was passed
	 * to this function.
	 * </p>
	 * 
	 * @param msg
	 *            message that is send to the console
	 */
	public static void print(String msg) {
		for (IOutputListener observer : theInstance.outputListener) {
			observer.outputMsg(msg);
		}
	}

	/**
	 * <p>
	 * Sends a message to all observers containing the message that was passed
	 * to this function and adds an endline to the message.
	 * </p>
	 * 
	 * @param msg
	 *            message that is send to the observers
	 */
	public static void println(String msg) {
		for (IOutputListener observer : theInstance.outputListener) {
			observer.outputMsg(msg + StringTools.ENDLINE);
		}
	}

	/**
	 * <p>
	 * Sends an error message to all observers containing the message that was
	 * passed to this function.
	 * </p>
	 * 
	 * @param errMsg
	 *            message that is send to the observers
	 */
	public static void printerr(String errMsg) {
		for (IErrorListener observer : theInstance.errorListener) {
			observer.errorMsg(errMsg);
		}
	}

	/**
	 * <p>
	 * Sends an error message to all observers containing the message that was
	 * passed to this function and adds an endline to the message.
	 * </p>
	 * 
	 * @param errMsg
	 *            message that is send to the observers
	 */
	public static void printerrln(String errMsg) {
		for (IErrorListener observer : theInstance.errorListener) {
			observer.errorMsg(errMsg + StringTools.ENDLINE);
		}
	}

	/**
	 * <p>
	 * Sends an exception to all observers to print its stack trace.
	 * </p>
	 * 
	 * @param e
	 *            exception whose stack trace is to be printed
	 */
	public static void logException(Exception e) {
		for (IExceptionListener observer : theInstance.exceptionListener) {
			observer.logException(e);
		}
	}

	/**
	 * <p>
	 * Sends a debug message to all observers containing the message that was
	 * passed to this function.
	 * </p>
	 * 
	 * @param traceMsg
	 *            message that is send to the observers
	 */
	public static void trace(String traceMsg) {
		for (ITraceListener observer : theInstance.traceListener) {
			observer.traceMsg(traceMsg);
		}
	}

	/**
	 * <p>
	 * Sends a debug message to all observers containing the message that was
	 * passed to this function and adds an {@link StringTools#ENDLINE} to the
	 * message.
	 * </p>
	 * 
	 * @param traceMsg
	 *            message that is send to the observers
	 */
	public static void traceln(String traceMsg) {
		for (ITraceListener observer : theInstance.traceListener) {
			observer.traceMsg(traceMsg + StringTools.ENDLINE);
		}
	}

	/**
	 * <p>
	 * Called by {@link CommandExecuter#exec(String)}.
	 * </p>
	 * 
	 * @param command
	 *            command that is executed
	 */
	static void commandNotification(String command) {
		for (ICommandListener observer : theInstance.commandListener) {
			observer.commandNotification(command);
		}
	}

}
