package org.subethamail.smtp.server;

import java.io.IOException;
import java.net.SocketTimeoutException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.subethamail.smtp.command.AuthCommand;

/**
 * This class manages execution of a SMTP command.
 *
 * @author Jon Stevens
 */
public class CommandHandler
{
	private Map<String, Command> commandMap = new HashMap<String, Command>();
	private static Logger log = LoggerFactory.getLogger(CommandHandler.class);
	
	/**
	 * Populates a default set of commands based on what is in the CommandRegistry.
	 */
	public CommandHandler()
	{
		for(CommandRegistry registry : CommandRegistry.values())
		{
			addCommand(registry.getCommand());
		}
	}

	/**
	 * Pass in a Collection of Command objects.
	 * @param availableCommands
	 */
	public CommandHandler(Collection<Command> availableCommands)
	{
		for(Command command :availableCommands )
		{
			addCommand(command);
		}
	}
	
	/**
	 * Adds a new command to the map.
	 * @param command
	 */
	public void addCommand(Command command)
	{
		if (log.isDebugEnabled())
			log.debug("Added command: " + command.getName());
		this.commandMap.put(command.getName(), command);
	}

	/**
	 * Does the map contain the named command?
	 * @param command
	 * @return true if the command exists
	 */
	public boolean containsCommand(String command)
	{
		return this.commandMap.containsKey(command);
	}

	/**
	 * Calls the execute method on a command.
	 * 
	 * @param context
	 * @param commandString
	 * @throws SocketTimeoutException
	 * @throws IOException
	 */
	public void handleCommand(ConnectionContext context, String commandString)
		throws SocketTimeoutException, IOException
	{
		try
		{	
			Command command = getCommandFromString(commandString);
			handleCommand(context, commandString, command);
		}
		catch (CommandException e)
		{
			context.sendResponse("500 " + e.getMessage());
		}
	}
	
	/**
	 * Calls the execute method on a command.
	 * 
	 * @param context
	 * @param commandString
	 * @throws SocketTimeoutException
	 * @throws IOException
	 */
	public void handleCommand(ConnectionContext context, String commandString, Command command)
		throws SocketTimeoutException, IOException
	{
			command.execute(commandString, context);
	}	
	
	/**
	 * Executes an auth command.
	 * 
	 * @param context
	 * @param commandString
	 * @throws SocketTimeoutException
	 * @throws IOException
	 */
	public void handleAuthChallenge(ConnectionContext context, String commandString)
		throws SocketTimeoutException, IOException
	{
		Command command = this.commandMap.get(AuthCommand.VERB);
		command.execute(commandString, context);
	}	
	
	/**
	 * Given a string, find the Command object.
	 * 
	 * @param commandString
	 * @return The command object.
	 * @throws UnknownCommandException
	 * @throws InvalidCommandNameException
	 */
	protected Command getCommandFromString(String commandString)
		throws UnknownCommandException, InvalidCommandNameException
	{
		Command command = null;
		String key = toKey(commandString);
		if (key != null)
		{
			command = this.commandMap.get(key);
		}
		if (command == null)
		{
			// some commands have a verb longer than 4 letters
			String verb = toVerb(commandString);
			if (verb != null)
			{
				command = this.commandMap.get(verb);
			}
		}
		if (command == null)
		{
			throw new UnknownCommandException("Error: command not implemented");
		}
		return command;
	}
	
	/** */
	private String toKey(String string) throws InvalidCommandNameException
	{
		if (string == null || string.length() < 4)
			throw new InvalidCommandNameException("Error: bad syntax");
		
		return string.substring(0, 4).toUpperCase();
	}
	
	/** */
	private String toVerb(String string) throws InvalidCommandNameException
	{
		StringTokenizer stringTokenizer = new StringTokenizer(string);
		if (!stringTokenizer.hasMoreTokens())
			throw new InvalidCommandNameException("Error: bad syntax");
		
		return stringTokenizer.nextToken().toUpperCase();
	}
}

@@ -641,21 +641,12 @@
 sed 
-on what is in
+with
  the
@@ -661,16 +661,25 @@
 Registry
+ commands
 .%0A%09 */%0A%09
@@ -3064,221 +3064,20 @@
 %0A%09%7B%0A
-%09%09Command command = null;%0A%09%09String key = toKey(commandString);%0A%09%09if (key != null)%0A%09%09%7B%0A%09%09%09command = this.commandMap.get(key);%0A%09%09%7D%0A%09%09if (command == null)%0A%09%09%7B%0A%09%09%09// some commands have a verb longer than 4 letters%0A%09%09%09
+            
 Stri
@@ -3113,38 +3113,26 @@
 g);%0A
-%09%09%09if (verb != null)%0A%09%09%09%7B%0A%09%09%09%09
+%0A%09        Command 
 comm
@@ -3168,17 +3168,8 @@
 b);%0A
-%09%09%09%7D%0A%09%09%7D%0A
 %09%09if
@@ -3189,21 +3189,20 @@
 null)%0A%09%09
-%7B%0A%09%09%09
+    
 throw ne
@@ -3264,17 +3264,16 @@
 ed%22);%0A%09%09
-%7D
 %0A%09%09retur
@@ -3314,19 +3314,20 @@
 tring to
-Key
+Verb
 (String 
@@ -3368,26 +3368,36 @@
 xception%0A%09%7B%0A
-%09%09
+            
 if (string =
@@ -3427,19 +3427,32 @@
 () %3C 4)%0A
-%09%09%09
+                
 throw ne
@@ -3507,149 +3507,21 @@
 %22);%0A
-%09%09%0A%09%09return string.substring(0, 4).toUpperCase();%0A%09%7D%0A%09%0A%09/** */%0A%09private String toVerb(String string) throws InvalidCommandNameException%0A%09%7B%0A%09%09
+%0A            
 Stri
@@ -3579,18 +3579,28 @@
 tring);%0A
-%09%09
+            
 if (!str
@@ -3629,17 +3629,16 @@
 kens())%0A
-%09
 %09%09throw 
@@ -3690,26 +3690,36 @@
 yntax%22);%0A%09%09%0A
-%09%09
+            
 return strin

Fix string to command mapping