/*
 *  Copyright (C) 2007-2010  Anders Gavare.  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright  
 *     notice, this list of conditions and the following disclaimer in the 
 *     documentation and/or other materials provided with the distribution.
 *  3. The name of the author may not be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 *  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE   
 *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 *  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 *  OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 *  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 *  OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 *  SUCH DAMAGE.
 */

#include "assert.h"
#include <iostream>

#include "GXemul.h"
#include "CommandInterpreter.h"

// Built-in commands (autogenerated list by the configure script):
#include "../../commands_h.h"


CommandInterpreter::CommandInterpreter(GXemul* owner)
	: m_GXemul(owner)
	, m_currentCommandCursorPosition(0)
	, m_inEscapeSequence(false)
	, m_historyEntryToCopyFrom(0)
	, m_commandHistoryInsertPosition(0)
	, m_commandHistoryMaxSize(100)
{
	m_commandHistory.resize(m_commandHistoryMaxSize, "");

	// It would be bad to run without a working GXemul instance.
	assert(m_GXemul != NULL);

	// Add the default built-in commands:
	// (This list is autogenerated by the configure script.)
#include "../../commands.h"
}


void CommandInterpreter::AddCommand(refcount_ptr<Command> command)
{
	m_commands[command->GetCommandName()] = command;
}


const Commands& CommandInterpreter::GetCommands() const
{
	return m_commands;
}


int CommandInterpreter::AddLineToCommandHistory(const string& command)
{
	if (command == "")
		return m_commandHistoryInsertPosition;

	size_t lastInsertedPosition =
	    (m_commandHistoryInsertPosition - 1 + m_commandHistoryMaxSize)
	    % m_commandHistoryMaxSize;

	if (m_commandHistory[lastInsertedPosition] == command)
		return m_commandHistoryInsertPosition;

	m_commandHistory[m_commandHistoryInsertPosition ++] = command;
	m_commandHistoryInsertPosition %= m_commandHistoryMaxSize;

	return m_commandHistoryInsertPosition;
}


string CommandInterpreter::GetHistoryLine(int nStepsBack) const
{
	if (nStepsBack == 0)
		return "";

	int index = (m_commandHistoryInsertPosition - nStepsBack +
	    m_commandHistoryMaxSize) % m_commandHistoryMaxSize;

	return m_commandHistory[index];
}


bool CommandInterpreter::TabComplete(string& commandString,
	size_t& cursorPosition, bool visibleShowAvailable)
{
	string wordToComplete;
	bool firstWordOnLine = true;

	size_t pos = cursorPosition;
	while (pos > 0) {
		pos --;
		if (commandString[pos] == ' ')
			break;
		wordToComplete = commandString[pos] + wordToComplete;
	}

	while (pos > 0) {
		pos --;
		if (commandString[pos] != ' ') {
			firstWordOnLine = false;
			break;
		}
	}

	bool completeCommands = firstWordOnLine;

	if (wordToComplete == "") {
		if (!visibleShowAvailable)
			return false;

		// Show all available words:

		if (completeCommands) {
			// All available commands:
			vector<string> allCommands;
			for (Commands::const_iterator it = m_commands.begin();
			    it != m_commands.end(); ++it)
				allCommands.push_back(it->first);
		
			ShowAvailableWords(allCommands);
		}

		ShowAvailableWords(m_GXemul->GetRootComponent()->
		    FindPathByPartialMatch(""));

		return false;
	}

	vector<string> matches;

	matches = m_GXemul->GetRootComponent()->
	    FindPathByPartialMatch(wordToComplete, true);

	if (completeCommands) {
		Commands::const_iterator it = m_commands.begin();
		for (; it != m_commands.end(); ++it) {
			const string& commandName = it->first;
			if (commandName.substr(0, wordToComplete.length())
			    == wordToComplete) {
				matches.push_back(commandName);
			}
		}
	}

	if (matches.size() == 0)
		return false;

	string completedWord;

	// Single match, or multiple matches?
	if (matches.size() == 1) {
		// Insert the rest of the command name into the input line:
		completedWord = matches[0];
	} else {
		// Figure out the longest possible match, and add that:
		size_t i, n = matches.size();
		for (size_t pos2 = 0; ; pos2 ++) {
			if (pos2 >= matches[0].length())
				break;
			stringchar ch = matches[0][pos2];
			for (i=1; i<n; i++) {
				if (matches[i][pos2] != ch)
					break;
			}
			if (i == n)
				completedWord += ch;
			else
				break;
		}
		
		// Show available words, so the user knows what there
		// is to choose from.
		if (visibleShowAvailable)
			ShowAvailableWords(matches);
	}

	// Erase the old (incomplete) word, and insert the completed word:
	if (!completedWord.empty()) {
		cursorPosition -= wordToComplete.length();
		commandString.erase(cursorPosition, wordToComplete.length());	
		commandString.insert(cursorPosition, completedWord);
		cursorPosition += completedWord.length();
	}

	// Special case: If there was a single match, and we are at the end
	// of the line, and this was a command, then add a space (" "). This
	// behaviour feels better, and this is how other tab completors seems
	// to work.
	//
	// NOTE: Don't add a space after component paths. Usually the user
	// will want to type e.g. "cpu" + TAB, and get
	// "root.machine0.mainbus0.cpu0" with no space, and then be able to
	// add ".unassemble" or so manually.
	if (matches.size() == 1 && cursorPosition == commandString.length()) {
		bool isCommand = false;
		Commands::const_iterator it = m_commands.begin();
		for (; it != m_commands.end(); ++it) {
			const string& commandName = it->first;
			if (commandName == matches[0]) {
				isCommand = true;
				break;
			}
		}

		if (isCommand) {
			commandString += " ";
			cursorPosition ++;
		}
	}

	return matches.size() == 1;
}


bool CommandInterpreter::TabCompleteWithSubname(string& commandString,
	size_t& cursorPosition, bool visibleShowAvailable)
{
	if (cursorPosition == 0)
		return false;

	int nStepsBack = 1;
	size_t pos = cursorPosition - 1;

	while (pos > 0 && commandString[pos] != '.') {
		pos --;
		nStepsBack ++;
	}

	if (pos == 0)
		return false;

	// Here, pos is the position of the dot:
	//
	//	cpu.u
	//	   ^

	bool success = TabComplete(commandString, pos, visibleShowAvailable);
	if (!success)
		return false;

	// pos is now the new position of the dot:
	//
	//	root.machine0.mainbus0.cpu.u
	//	                          ^

	// Look up the component:
	int startOfComponentName = pos;
	while (startOfComponentName >= 0 &&
	    commandString[startOfComponentName] != ' ')
		-- startOfComponentName;

	if (startOfComponentName < 0)
		startOfComponentName = 0;

	string componentName = commandString.substr(startOfComponentName,
	    pos - startOfComponentName);

	// std::cerr << "[" << componentName << "]\n";
	refcount_ptr<Component> component = m_GXemul->GetRootComponent()->
	    LookupPath(componentName);

	cursorPosition = pos + nStepsBack;

	if (component.IsNULL())
		return false;

	// Figure out the method or state name to expand:
	size_t startOfMethodName = pos + 1;
	size_t methodNameLen = 0;
	while (startOfMethodName + methodNameLen < cursorPosition &&
	    commandString[startOfMethodName+methodNameLen] != ' ')
		methodNameLen ++;

	string methodName = commandString.substr(startOfMethodName,
	    methodNameLen);

	// std::cerr << "{" << methodName << "}\n";

	vector<string> names;
	vector<string> matchingNames;
	component->GetMethodNames(names);
	component->GetVariableNames(names);
	int nrOfMatches = 0;
	if (methodName.length() != 0) {
		for (size_t i=0; i<names.size(); ++i) {
			if (names[i].substr(0, methodName.length()) ==
			    methodName) {
				++ nrOfMatches;
				matchingNames.push_back(names[i]);
			}
		}
	} else {
		matchingNames = names;
	}

	if (matchingNames.size() == 0)
		return false;

	// Replace the short name with a match as long as possible, e.g.
	// "memo" will be replaced by "memoryMapped", if names
	// "memoryMappedAddr" and "memoryMappedSize" are available.
	string longestPossibleMatch = "";
	size_t i, n = matchingNames.size();
	for (size_t pos2 = 0; ; pos2 ++) {
		if (pos2 >= matchingNames[0].length())
			break;
		stringchar ch = matchingNames[0][pos2];
		for (i=1; i<n; i++) {
			if (matchingNames[i][pos2] != ch)
				break;
		}
		if (i == n)
			longestPossibleMatch += ch;
		else
			break;
	}

	commandString.replace(startOfMethodName, methodNameLen,
	    longestPossibleMatch);
	cursorPosition += longestPossibleMatch.length() - methodNameLen;

	// A single match? Then we succeeded.
	if (nrOfMatches == 1)
		return true;

	// Show available methods and variable names:		
	if (visibleShowAvailable) {
		vector<string> allNames;
		vector<string> matchingNames2;
		
		component->GetMethodNames(allNames);
		for (size_t j=0; j<allNames.size(); ++j) {
			if (methodName.length() == 0 ||
			    allNames[j].substr(0, methodName.length()) == methodName)
				matchingNames2.push_back(allNames[j]);
		}

		if (matchingNames2.size() > 0) {
			m_GXemul->GetUI()->ShowDebugMessage("\nMethods:");
			ShowAvailableWords(matchingNames2);
		}
	}
	if (visibleShowAvailable) {
		vector<string> allNames;
		vector<string> matchingNames2;
		
		component->GetVariableNames(allNames);
		for (size_t j=0; j<allNames.size(); ++j) {
			if (methodName.length() == 0 ||
			    allNames[j].substr(0, methodName.length()) == methodName)
				matchingNames2.push_back(allNames[j]);
		}

		if (matchingNames2.size() > 0) {
			m_GXemul->GetUI()->ShowDebugMessage("\nVariables:");
			ShowAvailableWords(matchingNames2);
		}
	}

	return false;
}


bool CommandInterpreter::AddKey(stringchar key)
{
	if (m_inEscapeSequence) {
		m_escapeSequence += key;

		// Handle some common escape sequences, and convert
		// them into simpler 1-byte keys/characters:

		if (m_escapeSequence == "[C") {			// right
			m_inEscapeSequence = false;
			AddKey('\6');	// CTRL-F
		} else if (m_escapeSequence == "[D") {		// left
			m_inEscapeSequence = false;
			AddKey('\2');	// CTRL-B
		} else if (m_escapeSequence == "OH") {		// home
			m_inEscapeSequence = false;
			AddKey('\1');	// CTRL-A
		} else if (m_escapeSequence == "[H") {		// home
			m_inEscapeSequence = false;
			AddKey('\1');	// CTRL-A
		} else if (m_escapeSequence == "OF") {		// end
			m_inEscapeSequence = false;
			AddKey('\5');	// CTRL-E
		} else if (m_escapeSequence == "[F") {		// end
			m_inEscapeSequence = false;
			AddKey('\5');	// CTRL-E
		} else if (m_escapeSequence == "[A") {		// up
			m_inEscapeSequence = false;
			AddKey('\20');	// CTRL-P
		} else if (m_escapeSequence == "[B") {		// down
			m_inEscapeSequence = false;
			AddKey('\16');	// CTRL-N
		} else if (m_escapeSequence.length() > 2) {
			// Let's bail out of escape sequence handling...
			//
			// Note: If you trace execution here for some key that
			// you feel _should_ be handled, please send me a mail
			// about it.
			//
			m_inEscapeSequence = false;
			AddKey('?');
		}
		
		return false;
	}

	switch (key) {

	case '\0':
		// Add nothing, just reshow/update the command buffer.
		break;

	case '\1':	// CTRL-A: move to start of line
		m_currentCommandCursorPosition = 0;
		break;

	case '\2':	// CTRL-B: move back (left)
		if (m_currentCommandCursorPosition > 0)
			m_currentCommandCursorPosition --;
		break;

	case '\4':	// CTRL-D: remove the character to the right
		if (m_currentCommandCursorPosition <
		    m_currentCommandString.length())
			m_currentCommandString.erase(
			    m_currentCommandCursorPosition, 1);
		break;

	case '\5':	// CTRL-E: move to end of line
		m_currentCommandCursorPosition =
		    m_currentCommandString.length();
		break;

	case '\6':	// CTRL-F: move forward (right)
		if (m_currentCommandCursorPosition <
		    m_currentCommandString.length())
			m_currentCommandCursorPosition ++;
		break;

	case '\13':	// CTRL-K: kill to end of line
		ClearCurrentInputLineVisually();
		m_currentCommandString.resize(m_currentCommandCursorPosition);
		break;

	case '\16':	// CTRL-N: next in history (down)
		ClearCurrentInputLineVisually();

		m_historyEntryToCopyFrom --;
		if (m_historyEntryToCopyFrom < 0)
			m_historyEntryToCopyFrom = 0;

		m_currentCommandString =
		    GetHistoryLine(m_historyEntryToCopyFrom);
		m_currentCommandCursorPosition =
		    m_currentCommandString.length();
		break;

	case '\20':	// CTRL-P: previous in history (up)
		ClearCurrentInputLineVisually();

		m_historyEntryToCopyFrom ++;
		m_currentCommandString =
		    GetHistoryLine(m_historyEntryToCopyFrom);

		// We went too far? Then back down.
		if (m_currentCommandString == "") {
			m_historyEntryToCopyFrom --;
			m_currentCommandString =
			    GetHistoryLine(m_historyEntryToCopyFrom);
		}
		m_currentCommandCursorPosition =
		    m_currentCommandString.length();
		break;

	case '\24':	// CTRL-T: show status
		m_GXemul->GetUI()->ShowDebugMessage("\n");
		RunCommand("status");
		break;

	case '\27':	// CTRL-W: remove current word (backspacing)
		ClearCurrentInputLineVisually();

		// 1. Remove any spaces left to the cursor.
		while (m_currentCommandCursorPosition > 0) {
			if (m_currentCommandString[
			    m_currentCommandCursorPosition-1] == ' ') {
				m_currentCommandCursorPosition --;
				m_currentCommandString.erase(
				    m_currentCommandCursorPosition, 1);
			} else {
				break;
			}
		}

		// 2. Remove non-spaces left to the cusror, either until
		//	the cursor is at position 0, or until there is a
		//	space again.
		while (m_currentCommandCursorPosition > 0) {
			if (m_currentCommandString[
			    m_currentCommandCursorPosition-1] != ' ') {
				m_currentCommandCursorPosition --;
				m_currentCommandString.erase(
				    m_currentCommandCursorPosition, 1);
			} else {
				break;
			}
		}

		break;

	case '\177':	// ASCII 127 (octal 177) = del
	case '\b':	// backspace
		if (m_currentCommandCursorPosition > 0) {
			m_currentCommandCursorPosition --;
			m_currentCommandString.erase(
			    m_currentCommandCursorPosition, 1);
		}
		break;

	case '\33':
		// Escape key handling:
		m_inEscapeSequence = true;
		m_escapeSequence = "";
		break;

	case '\t':
		// Tab completion, with visible word hints:
		{
			bool success = TabComplete(m_currentCommandString,
			    m_currentCommandCursorPosition, true);

			// Attempt to expand component-name + "." + optional
			// method or variable name, "cpu.u", if the first
			// tab-completion failed.
			if (!success) {
				TabCompleteWithSubname(m_currentCommandString,
				    m_currentCommandCursorPosition, true);
			}
		}
		break;

	case '\n':
	case '\r':
		// Newline executes the command, if it is non-empty:
		m_GXemul->GetUI()->InputLineDone();

		if (!m_currentCommandString.empty()) {
			AddLineToCommandHistory(m_currentCommandString);
			bool ignoredResult;
			RunCommand(m_currentCommandString, &ignoredResult);
			ClearCurrentCommandBuffer();
		} else if (m_mayBeReexecuted != "") {
			RunCommand(m_mayBeReexecuted);
		}
		break;

	default:
		// Most other keys just add/insert a character into the command
		// string:
		if (key >= ' ') {
			m_currentCommandString.insert(
			    m_currentCommandCursorPosition, 1, key);
			m_currentCommandCursorPosition ++;
		}
	}

	if (key != '\n' && key != '\r')
		ReshowCurrentCommandBuffer();

	// Return value is true for newline/cr, false otherwise:
	return key == '\n' || key == '\r';
}


void CommandInterpreter::ShowAvailableWords(const vector<string>& words)
{
	m_GXemul->GetUI()->ShowDebugMessage("\n");

	const size_t n = words.size();
	size_t i;

	// Find the longest word first:
	size_t maxLen = 0;
	for (i=0; i<n; ++i) {
		size_t len = words[i].length();
		if (len > maxLen)
			maxLen = len;
	}

	maxLen += 4;

	// Generate msg:
	std::stringstream msg;
	size_t lineLen = 0;
	for (i=0; i<n; ++i) {
		if (lineLen == 0)
			msg << "  ";

		size_t len = words[i].length();
		msg << words[i];
		lineLen += len;

		for (size_t j=len; j<maxLen; j++) {
			msg << " ";
			lineLen ++;
		}

		if (lineLen >= 77 - maxLen || i == n-1) {
			msg << "\n";
			lineLen = 0;
		}
	}
	
	m_GXemul->GetUI()->ShowDebugMessage(msg.str());
}


void CommandInterpreter::ReshowCurrentCommandBuffer()
{
	m_GXemul->GetUI()->RedisplayInputLine(
	    m_currentCommandString, m_currentCommandCursorPosition);
}


void CommandInterpreter::ClearCurrentInputLineVisually()
{
	string clearString = "";
	clearString.insert((size_t)0, m_currentCommandString.length(), ' ');

	m_GXemul->GetUI()->RedisplayInputLine(
	    clearString, m_currentCommandCursorPosition);
}


void CommandInterpreter::ClearCurrentCommandBuffer()
{
	m_currentCommandString = "";
	m_currentCommandCursorPosition = 0;
	m_historyEntryToCopyFrom = 0;
}


static void SplitIntoWords(const string& commandOrig,
	string& commandName, vector<string>& arguments)
{
	string command = commandOrig;

	arguments.clear();
	commandName = "";
	size_t pos = 0;

	// Surround '=' with white spaces, except when inside parentheses...
	// NOTE/TODO: This will not be needed in the future (?), when a real
	// expression evaluator has been [re]implemented.
	int insideParenthesesCount = 0;
	while (pos < command.length()) {
		if (command[pos] == '(')
			insideParenthesesCount ++;
		if (command[pos] == ')')
			insideParenthesesCount --;
		if (command[pos] == '=' && insideParenthesesCount == 0) {
			command.replace(pos, 1, " = ");
			pos ++;
		}

		pos ++;
	}

	// Split command into words, ignoring all whitespace:
	pos = 0;
	while (pos < command.length()) {
		// Skip initial whitespace:
		while (pos < command.length() && command[pos] == ' ')
			pos ++;
		
		if (pos >= command.length())
			break;
		
		// This is a new word. Add all characters, until
		// whitespace or end of string:
		string newWord = "";
		while (pos < command.length() && command[pos] != ' ') {
			newWord += command[pos];
			pos ++;
		}

		if (commandName.empty())
			commandName = newWord;
		else
			arguments.push_back(newWord);
	}
}


void CommandInterpreter::VariableAssignment(const string& componentPath,
	const string& variableName, const string& expression)
{
	refcount_ptr<Component> component = m_GXemul->GetRootComponent()->
	    LookupPath(componentPath);

	StateVariable* var = component->GetVariable(variableName);
	if (var == NULL) {
		m_GXemul->GetUI()->ShowDebugMessage("Unknown variable '" +
		    variableName + "'? (Internal error.)\n");
		throw std::exception();
	}

	const refcount_ptr<Component> lightClone =
	    m_GXemul->GetRootComponent()->LightClone();

	// Attempt to assign the expression to the variable:
	if (!component->SetVariableValue(variableName, expression))
		m_GXemul->GetUI()->ShowDebugMessage("Assignment failed.\n");

	// ... and print all state change (in case a write to a variable had
	// side effects, then this makes sure that the user sees all such side
	// effects):
	stringstream changeMessages;
	m_GXemul->GetRootComponent()->DetectChanges(lightClone, changeMessages);

	string msg = changeMessages.str();
	if (msg == "")
		msg = "(No state change.)\n";

	m_GXemul->GetUI()->ShowDebugMessage(msg);
}


bool CommandInterpreter::RunComponentMethod(
	const string& componentPathAndMethod, const vector<string>& arguments)
{
	// Note: componentPathAndMethod may or may not have a method at
	// the end!

	// Make several "smart" guesses:
	refcount_ptr<Component> component;
	string componentPath;
	string methodName;

	do {
		// 1. Assume that componentPathAndMethod is a full component
		// path:
		component = m_GXemul->GetRootComponent()->
		    LookupPath(componentPathAndMethod);
		if (!component.IsNULL())
			break;

		// 2. Assume that componentPathAndMethod is a component
		// path, but it is not tab-completed yet:
		string tabcompleted = componentPathAndMethod;
		size_t tmpLen = tabcompleted.length();
		if (TabComplete(tabcompleted, tmpLen)) {
			component = m_GXemul->GetRootComponent()->
			    LookupPath(tabcompleted);
			if (!component.IsNULL())
				break;
		}
		
		// If there is no period in the name, we can't continue
		// with the following guesses.
		if (componentPathAndMethod.find(".") == string::npos)
			break;

		size_t pos = componentPathAndMethod.find_last_of('.');

		// 3. Assume full component path + ".method":
		componentPath = componentPathAndMethod.substr(0, pos);
		component = m_GXemul->GetRootComponent()->
		    LookupPath(componentPath);
		if (!component.IsNULL()) {
			methodName = componentPathAndMethod.substr(pos+1);
			break;
		}

		// 4. Assume non-tab-completed component path + ".method":
		tabcompleted = componentPath;
		tmpLen = tabcompleted.length();
		if (TabComplete(tabcompleted, tmpLen)) {
			component = m_GXemul->GetRootComponent()->
			    LookupPath(tabcompleted);
			if (!component.IsNULL()) {
				methodName =
				    componentPathAndMethod.substr(pos+1);
				break;
			}
		}
	} while (false);

	if (component.IsNULL())
		return false;

	// No method given? Then show the component tree, and the component's
	// state variables, and return.
	if (methodName.empty()) {
		m_GXemul->GetUI()->ShowDebugMessage(
		    component->GenerateTreeDump(""));
		
		// Retrieve the names of all the state variables:
		vector<string> variableNames;
		component->GetVariableNames(variableNames);

		stringstream ss;
		ss << "\n";

		size_t maxLen = 0;
		size_t i;
		for (i=0; i<variableNames.size(); i++)
			if (variableNames[i].length() > maxLen)
				maxLen = variableNames[i].length();

		for (i=0; i<variableNames.size(); i++) {
			const string& name = variableNames[i];
			if (name == "name" || name == "template")
				continue;

			ss << "  " << name;
			for (size_t j=name.length(); j<=maxLen; j++)
				ss << " ";

			const StateVariable* var = component->GetVariable(name);
			if (var == NULL)
				ss << "= (unknown?)";
			else
				ss << "= " << var->ToString();

			ss << "\n";
		}

		m_GXemul->GetUI()->ShowDebugMessage(ss.str());
		
		return true;
	}

	// Now, it is possible that methodName is incomplete, so it has to
	// be looked up as well:
	vector<string> names;
	component->GetMethodNames(names);
	int nrOfMatches = 0;
	string fullMatch;
	for (size_t i=0; i<names.size(); ++i) {
		if (names[i].substr(0, methodName.length()) == methodName) {
			++ nrOfMatches;
			fullMatch = names[i];
		}
	}

	if (nrOfMatches == 1) {
		// Execute it!
		component->ExecuteMethod(m_GXemul, fullMatch, arguments);

		if (component->MethodMayBeReexecutedWithoutArgs(fullMatch))
			m_mayBeReexecuted = componentPathAndMethod;

		return true;
	}

	// Try variable names:
	names.clear();
	component->GetVariableNames(names);
	nrOfMatches = 0;
	fullMatch = "";
	for (size_t i=0; i<names.size(); ++i) {
		// Exact match?
		if (names[i] == methodName) {
			nrOfMatches = 1;
			fullMatch = names[i];
			break;
		}

		// Partial match?
		if (names[i].substr(0, methodName.length()) == methodName) {
			++ nrOfMatches;
			fullMatch = names[i];
		}
	}

	stringstream ss;

	if (nrOfMatches == 1) {
		if (arguments.size() > 0) {
			if (arguments.size() == 1 ||
			    arguments[0] != "=") {
				// TODO: Printing expressions, such as
				//	cpu.pc + 4

				m_GXemul->GetUI()->ShowDebugMessage(
				    "Syntax error. Variable assignment syntax"
				    " is:\n  <variable> = <expression>\n");
				return true;
			}

			const StateVariable* var =
			    component->GetVariable(fullMatch);
			if (var == NULL) {
				m_GXemul->GetUI()->ShowDebugMessage(
				    "Unknown variable.\n");
				return true;
			}

			string assignment;
			for (size_t i=1; i<arguments.size(); ++i)
				assignment += arguments[i] + " ";

			VariableAssignment(component->GeneratePath(), fullMatch, assignment);

			return true;
		}

		// Print the variable's name and value:
		ss << fullMatch;

		const StateVariable* var = component->GetVariable(fullMatch);
		if (var == NULL)
			ss << " = (unknown variable?)";
		else
			ss << " = " << var->ToString();

		ss << "\n";

		m_GXemul->GetUI()->ShowDebugMessage(ss.str());

		return true;
	}

	if (nrOfMatches > 1)
		ss << methodName << ": ambiguous method or variable name of "
		    << component->GeneratePath() << ".\n";
	else
		ss << methodName << ": not a method or variable of "
		    << component->GeneratePath() << ".\n";

	m_GXemul->GetUI()->ShowDebugMessage(ss.str());

	return false;
}


bool CommandInterpreter::RunCommand(const string& command, bool* pSuccess)
{
	string commandName;
	vector<string> arguments;
	SplitIntoWords(command, commandName, arguments);

	m_mayBeReexecuted = "";

	m_GXemul->GetUI()->ShowCommandMessage(command);

	// Find the command...
	Commands::iterator it = m_commands.find(commandName);
	if (it == m_commands.end()) {
		// Not found? Then try to tab-complete the name...
		string commandTabCompleted = commandName;
		size_t tmpCursorPos = commandTabCompleted.length();
		TabComplete(commandTabCompleted, tmpCursorPos);

		// remove any trailing space(s):
		while (commandTabCompleted.length() > 0 &&
		    commandTabCompleted[commandTabCompleted.length()-1] == ' ')
			commandTabCompleted.erase(
			    commandTabCompleted.length() - 1);

		// ... and try again:
		it = m_commands.find(commandTabCompleted);
		if (it == m_commands.end()) {
			// If this is a component name [with an optional
			// method name], then execute a method on it.
			if (RunComponentMethod(commandName, arguments))
				return true;
			m_GXemul->GetUI()->ShowDebugMessage(commandName +
			    ": unknown command. Type  help  for help.\n");
			return false;
		}
	}

	if (arguments.size() != 0 && (it->second)->GetArgumentFormat() == "") {
		m_GXemul->GetUI()->ShowDebugMessage(commandName +
		    " takes no arguments. Type  help " + commandName +
		    "  for help on the syntax.\n");
		return false;
	}

	// ... and execute it:
	bool success = (it->second)->Execute(*m_GXemul, arguments);
	if (pSuccess != NULL) {
		*pSuccess = success;
	} else {
		if (!success) {
			throw UnitTestFailedException("apa");
		}
	}

	if ((it->second)->MayBeReexecutedWithoutArgs())
		m_mayBeReexecuted = it->first;

	return true;
}


const string& CommandInterpreter::GetCurrentCommandBuffer() const
{
	return m_currentCommandString;
}


/*****************************************************************************/


#ifdef WITHUNITTESTS

static void Test_CommandInterpreter_AddKey_ReturnValue()
{
	GXemul gxemul;
	CommandInterpreter& ci = gxemul.GetCommandInterpreter();

	UnitTest::Assert("addkey of regular char should return false",
	    ci.AddKey('a') == false);

	UnitTest::Assert("addkey of nul char should return false",
	    ci.AddKey('\0') == false);

	UnitTest::Assert("addkey of newline should return true",
	    ci.AddKey('\n') == true);

	UnitTest::Assert("addkey of carriage return should return true too",
	    ci.AddKey('\r') == true);
}

static void Test_CommandInterpreter_KeyBuffer()
{
	GXemul gxemul;
	CommandInterpreter& ci = gxemul.GetCommandInterpreter();

	UnitTest::Assert("buffer should initially be empty",
	    ci.GetCurrentCommandBuffer() == "");

	ci.AddKey('a');		// normal char

	UnitTest::Assert("buffer should contain 'a'",
	    ci.GetCurrentCommandBuffer() == "a");

	ci.AddKey('\0');	// nul char should have no effect

	UnitTest::Assert("buffer should still contain only 'a'",
	    ci.GetCurrentCommandBuffer() == "a");

	ci.AddKey('A');		// multiple chars
	ci.AddKey('B');
	UnitTest::Assert("buffer should contain 'aAB'",
	    ci.GetCurrentCommandBuffer() == "aAB");

	ci.AddKey('\177');	// del

	UnitTest::Assert("buffer should contain 'aA' (del didn't work?)",
	    ci.GetCurrentCommandBuffer() == "aA");

	ci.AddKey('\b');	// backspace

	UnitTest::Assert("buffer should contain 'a' again (BS didn't work)",
	    ci.GetCurrentCommandBuffer() == "a");

	ci.AddKey('\b');

	UnitTest::Assert("buffer should now be empty '' again",
	    ci.GetCurrentCommandBuffer() == "");

	ci.AddKey('\b');	// cannot be emptier than... well... empty :)

	UnitTest::Assert("buffer should still be empty",
	    ci.GetCurrentCommandBuffer() == "");

	ci.AddKey('a');

	UnitTest::Assert("buffer should contain 'a' again",
	    ci.GetCurrentCommandBuffer() == "a");

	ci.AddKey('Q');

	UnitTest::Assert("buffer should contain 'aQ'",
	    ci.GetCurrentCommandBuffer() == "aQ");

	ci.AddKey('\n');	// newline should execute the command

	UnitTest::Assert("buffer should be empty after executing '\\n'",
	    ci.GetCurrentCommandBuffer() == "");

	ci.AddKey('Z');
	ci.AddKey('Q');

	UnitTest::Assert("new command should have been possible",
	    ci.GetCurrentCommandBuffer() == "ZQ");

	ci.AddKey('\r');	// carriage return should work like newline

	UnitTest::Assert("buffer should be empty after executing '\\r'",
	    ci.GetCurrentCommandBuffer() == "");
}

static void Test_CommandInterpreter_KeyBuffer_CursorMovement()
{
	GXemul gxemul;
	CommandInterpreter& ci = gxemul.GetCommandInterpreter();

	ci.AddKey('A');
	ci.AddKey('B');
	ci.AddKey('C');
	ci.AddKey('D');
	ci.AddKey('E');
	UnitTest::Assert("buffer should contain 'ABCDE'",
	    ci.GetCurrentCommandBuffer(), "ABCDE");

	ci.AddKey('\2');	// CTRL-B should move back (left)
	ci.AddKey('\2');
	ci.AddKey('\2');
	UnitTest::Assert("buffer should still contain 'ABCDE'",
	    ci.GetCurrentCommandBuffer(), "ABCDE");

	ci.AddKey('\b');
	UnitTest::Assert("buffer should now contain 'ACDE'",
	    ci.GetCurrentCommandBuffer(), "ACDE");

	ci.AddKey('\6');	// CTRL-F should move forward (right)
	ci.AddKey('\6');
	UnitTest::Assert("buffer should still contain 'ACDE'",
	    ci.GetCurrentCommandBuffer(), "ACDE");

	ci.AddKey('\b');
	UnitTest::Assert("buffer should now contain 'ACE'",
	    ci.GetCurrentCommandBuffer(), "ACE");

	ci.AddKey('\1');	// CTRL-A should move to start of line
	UnitTest::Assert("buffer should still contain 'ACE'",
	    ci.GetCurrentCommandBuffer(), "ACE");

	ci.AddKey('1');
	ci.AddKey('2');
	UnitTest::Assert("buffer should now contain '12ACE'",
	    ci.GetCurrentCommandBuffer(), "12ACE");

	ci.AddKey('\5');	// CTRL-E should move to end of line
	UnitTest::Assert("buffer should still contain '12ACE'",
	    ci.GetCurrentCommandBuffer(), "12ACE");

	ci.AddKey('x');
	ci.AddKey('y');
	UnitTest::Assert("buffer should now contain '12ACExy'",
	    ci.GetCurrentCommandBuffer(), "12ACExy");

	ci.AddKey('\1');	// CTRL-A move to start of line again
	ci.AddKey('\6');	// CTRL-F move to the right
	ci.AddKey('\4');	// CTRL-D should remove character to the right
	UnitTest::Assert("buffer should now contain '1ACExy'",
	    ci.GetCurrentCommandBuffer(), "1ACExy");
}

static void Test_CommandInterpreter_KeyBuffer_CtrlK()
{
	GXemul gxemul;
	CommandInterpreter& ci = gxemul.GetCommandInterpreter();

	ci.AddKey('A');
	ci.AddKey('B');
	ci.AddKey('C');
	ci.AddKey('D');
	ci.AddKey('E');
	UnitTest::Assert("buffer should contain 'ABCDE'",
	    ci.GetCurrentCommandBuffer(), "ABCDE");

	ci.AddKey('\2');	// CTRL-B should move back (left)
	ci.AddKey('\2');
	UnitTest::Assert("buffer should still contain 'ABCDE'",
	    ci.GetCurrentCommandBuffer(), "ABCDE");

	ci.AddKey('\13');	// CTRL-K
	UnitTest::Assert("buffer should now contain 'ABC'",
	    ci.GetCurrentCommandBuffer(), "ABC");

	ci.AddKey('X');
	ci.AddKey('\13');	// CTRL-K again, at end of line
	UnitTest::Assert("buffer should now contain 'ABCX'",
	    ci.GetCurrentCommandBuffer(), "ABCX");

	ci.AddKey('\1');	// CTRL-A to move to start of line
	ci.AddKey('\13');	// CTRL-K again, should erase everything
	UnitTest::Assert("buffer should now be empty",
	    ci.GetCurrentCommandBuffer(), "");
}

static void Test_CommandInterpreter_KeyBuffer_CtrlW()
{
	GXemul gxemul;
	CommandInterpreter& ci = gxemul.GetCommandInterpreter();

	UnitTest::Assert("buffer should contain ''",
	    ci.GetCurrentCommandBuffer(), "");
	ci.AddKey('\27');	// CTRL-W
	UnitTest::Assert("buffer should still contain ''",
	    ci.GetCurrentCommandBuffer(), "");

	ci.AddKey('a');
	ci.AddKey('b');
	ci.AddKey('c');
	UnitTest::Assert("buffer should contain abc",
	    ci.GetCurrentCommandBuffer(), "abc");
	ci.AddKey('\27');	// CTRL-W
	UnitTest::Assert("buffer should be empty again",
	    ci.GetCurrentCommandBuffer(), "");

	ci.AddKey(' ');
	ci.AddKey(' ');
	ci.AddKey('a');
	ci.AddKey('b');
	ci.AddKey('c');
	UnitTest::Assert("buffer should contain '  abc'",
	    ci.GetCurrentCommandBuffer(), "  abc");
	ci.AddKey('\27');	// CTRL-W
	UnitTest::Assert("buffer should contain only two spaces",
	    ci.GetCurrentCommandBuffer(), "  ");

	ci.AddKey('a');
	ci.AddKey('b');
	ci.AddKey('c');
	ci.AddKey(' ');
	UnitTest::Assert("buffer should contain '  abc '",
	    ci.GetCurrentCommandBuffer(), "  abc ");
	ci.AddKey('\27');	// CTRL-W
	UnitTest::Assert("buffer should again contain only two spaces",
	    ci.GetCurrentCommandBuffer(), "  ");

	ci.AddKey('a');
	ci.AddKey('b');
	ci.AddKey('c');
	ci.AddKey('d');
	ci.AddKey(' ');
	ci.AddKey('e');
	ci.AddKey('f');
	ci.AddKey('g');
	ci.AddKey('h');
	ci.AddKey('i');
	ci.AddKey(' ');
	ci.AddKey('\2');	// CTRL-B = move left
	ci.AddKey('\2');
	ci.AddKey('\2');
	UnitTest::Assert("buffer should contain '  abcd efghi '",
	    ci.GetCurrentCommandBuffer(), "  abcd efghi ");
	ci.AddKey('\27');	// CTRL-W
	UnitTest::Assert("buffer should now contain '  abcd hi '",
	    ci.GetCurrentCommandBuffer(), "  abcd hi ");
}

static void Test_CommandInterpreter_CommandHistory()
{
	GXemul gxemul;
	CommandInterpreter& ci = gxemul.GetCommandInterpreter();

	UnitTest::Assert("history should still be empty (1)",
	    ci.AddLineToCommandHistory(""), 0);

	UnitTest::Assert("history should still be empty (2)",
	    ci.AddLineToCommandHistory(""), 0);

	UnitTest::Assert("A: history line 0",
	    ci.GetHistoryLine(0), "");
	UnitTest::Assert("A: history line 1 not set yet",
	    ci.GetHistoryLine(1), "");
	UnitTest::Assert("A: history line 2 not set yet",
	    ci.GetHistoryLine(2), "");

	UnitTest::Assert("history should contain one entry",
	    ci.AddLineToCommandHistory("hello"), 1);

	UnitTest::Assert("B: history line 0",
	    ci.GetHistoryLine(0), "");
	UnitTest::Assert("B: history line 1",
	    ci.GetHistoryLine(1), "hello");
	UnitTest::Assert("B: history line 2 not set yet",
	    ci.GetHistoryLine(2), "");

	UnitTest::Assert("history should contain two entries",
	    ci.AddLineToCommandHistory("world"), 2);

	UnitTest::Assert("history should still contain two entries",
	    ci.AddLineToCommandHistory("world"), 2);

	UnitTest::Assert("C: history line 0",
	    ci.GetHistoryLine(0), "");
	UnitTest::Assert("C: history line 1",
	    ci.GetHistoryLine(1), "world");
	UnitTest::Assert("C: history line 2",
	    ci.GetHistoryLine(2), "hello");

	UnitTest::Assert("history should contain three entries",
	    ci.AddLineToCommandHistory("hello"), 3);

	UnitTest::Assert("D: history line 0",
	    ci.GetHistoryLine(0), "");
	UnitTest::Assert("D: history line 1",
	    ci.GetHistoryLine(1), "hello");
	UnitTest::Assert("D: history line 2",
	    ci.GetHistoryLine(2), "world");

	UnitTest::Assert("history should still contain three entries",
	    ci.AddLineToCommandHistory(""), 3);
}

/**
 * \brief A dummy Command, for unit testing purposes
 */
class DummyCommand2
	: public Command
{
public:
	DummyCommand2(int& valueRef)
		: Command("dummycommand", "[args]")
		, m_value(valueRef)
	{
	}

	~DummyCommand2()
	{
	}

	bool Execute(GXemul& gxemul, const vector<string>& arguments)
	{
		m_value ++;
		return true;
	}

	string GetShortDescription() const
	{
		return "A dummy command used for unit testing.";
	}

	string GetLongDescription() const
	{
		return "This is just a dummy command used for unit testing.";
	}

private:
	int&	m_value;
};

/**
 * \brief A dummy Command, for unit testing purposes
 */
class DummyCommand3
	: public Command
{
public:
	DummyCommand3(int& valueRef)
		: Command("dummycmd", "[args]")
		, m_value(valueRef)
	{
	}

	~DummyCommand3()
	{
	}

	bool Execute(GXemul& gxemul, const vector<string>& arguments)
	{
		m_value ++;
		return true;
	}

	string GetShortDescription() const
	{
		return "A dummy command used for unit testing.";
	}

	string GetLongDescription() const
	{
		return "This is just a dummy command used for unit testing.";
	}

private:
	int&	m_value;
};

static void Test_CommandInterpreter_AddCommand()
{
	GXemul gxemul;
	CommandInterpreter& ci = gxemul.GetCommandInterpreter();

	size_t nCommands = ci.GetCommands().size();
	UnitTest::Assert("there should be some commands already",
	    nCommands > 0);

	ci.AddCommand(new VersionCommand);

	UnitTest::Assert("it should not be possible to have multiple commands"
		" with the same name",
	    ci.GetCommands().size() == nCommands);

	int dummyInt = 42;
	ci.AddCommand(new DummyCommand2(dummyInt));

	UnitTest::Assert("it should be possible to add new commands",
	    ci.GetCommands().size() == nCommands + 1);
}

static void Test_CommandInterpreter_TabCompletion_EmptyLine()
{
	GXemul gxemul;
	CommandInterpreter& ci = gxemul.GetCommandInterpreter();

	ci.AddKey('\t');
	UnitTest::Assert("tab completion should not have produced anything",
	    ci.GetCurrentCommandBuffer(), "");
}

static void Test_CommandInterpreter_TabCompletion_FullWord()
{
	GXemul gxemul;
	CommandInterpreter& ci = gxemul.GetCommandInterpreter();

	ci.AddKey('d');
	ci.AddKey('u');
	ci.AddKey('m');
	ci.AddKey('m');
	ci.AddKey('Z');
	ci.AddKey('\2');	// CTRL-B = move left
	UnitTest::Assert("initial buffer contents mismatch",
	    ci.GetCurrentCommandBuffer(), "dummZ");

	ci.AddKey('\t');
	UnitTest::Assert("tab completion should have failed",
	    ci.GetCurrentCommandBuffer(), "dummZ");

	int dummyInt = 42;
	ci.AddCommand(new DummyCommand2(dummyInt));

	ci.AddKey('\t');
	UnitTest::Assert("tab completion should have succeeded",
	    ci.GetCurrentCommandBuffer(), "dummycommandZ");

	ci.AddKey('X');
	UnitTest::Assert("tab completion should have placed cursor at end of"
		" the tab-completed word",
	    ci.GetCurrentCommandBuffer(), "dummycommandXZ");
}

static void Test_CommandInterpreter_TabCompletion_SpacesFirstOnLine()
{
	GXemul gxemul;
	CommandInterpreter& ci = gxemul.GetCommandInterpreter();

	ci.AddKey(' ');
	ci.AddKey(' ');
	ci.AddKey('v');
	ci.AddKey('e');
	ci.AddKey('r');
	ci.AddKey('s');
	UnitTest::Assert("initial buffer contents mismatch",
	    ci.GetCurrentCommandBuffer(), "  vers");

	ci.AddKey('\t');
	UnitTest::Assert("tab completion should have succeeded",
	    ci.GetCurrentCommandBuffer(), "  version ");
}

static void Test_CommandInterpreter_TabCompletion_Partial()
{
	GXemul gxemul;
	CommandInterpreter& ci = gxemul.GetCommandInterpreter();

	ci.AddKey('d');
	ci.AddKey('u');
	ci.AddKey('m');
	ci.AddKey('m');
	ci.AddKey('Z');
	ci.AddKey('\2');	// CTRL-B = move left
	UnitTest::Assert("initial buffer contents mismatch",
	    ci.GetCurrentCommandBuffer(), "dummZ");

	int dummyInt = 42;
	ci.AddCommand(new DummyCommand2(dummyInt));
	ci.AddCommand(new DummyCommand3(dummyInt));

	ci.AddKey('\t');
	UnitTest::Assert("tab completion should have partially succeeded",
	    ci.GetCurrentCommandBuffer(), "dummycZ");
}

static void Test_CommandInterpreter_TabCompletion_C()
{
	GXemul gxemul;
	CommandInterpreter& ci = gxemul.GetCommandInterpreter();

	ci.AddKey('c');
	UnitTest::Assert("initial buffer contents mismatch",
	    ci.GetCurrentCommandBuffer(), "c");

	ci.AddKey('\t');
	UnitTest::Assert("tab completion should not have modified command",
	    ci.GetCurrentCommandBuffer(), "c");

	// ... because there are at least two possible commands on the
	// letter c: close and continue.
}

static void Test_CommandInterpreter_TabCompletion_OnlyCommandAsFirstWord()
{
	GXemul gxemul;
	CommandInterpreter& ci = gxemul.GetCommandInterpreter();

	ci.AddKey('v');
	ci.AddKey('e');
	ci.AddKey('r');
	ci.AddKey('s');
	UnitTest::Assert("initial buffer contents mismatch",
	    ci.GetCurrentCommandBuffer(), "vers");

	ci.AddKey('\t');
	UnitTest::Assert("first tab completion should have succeeded",
	    ci.GetCurrentCommandBuffer(), "version ");

	ci.AddKey('v');
	ci.AddKey('e');
	ci.AddKey('r');
	ci.AddKey('s');
	UnitTest::Assert("buffer contents mismatch",
	    ci.GetCurrentCommandBuffer(), "version vers");

	ci.AddKey('\t');
	UnitTest::Assert("second tab completion should have failed",
	    ci.GetCurrentCommandBuffer(), "version vers");
}

static void Test_CommandInterpreter_TabCompletion_ComponentName()
{
	GXemul gxemul;
	CommandInterpreter& ci = gxemul.GetCommandInterpreter();

	ci.RunCommand("add testm88k");
	UnitTest::Assert("initial buffer should be empty",
	    ci.GetCurrentCommandBuffer(), "");

	ci.AddKey('c');
	ci.AddKey('p');
	ci.AddKey('\t');
	UnitTest::Assert("tab completion should have completed the "
		"component name",
	    ci.GetCurrentCommandBuffer(), "cpu0");

	// Note: No space after component tab completion.
}

static void Test_CommandInterpreter_TabCompletion_ComponentNameAlreadyComplete()
{
	GXemul gxemul;
	CommandInterpreter& ci = gxemul.GetCommandInterpreter();

	ci.RunCommand("add testm88k");
	UnitTest::Assert("initial buffer should be empty",
	    ci.GetCurrentCommandBuffer(), "");

	ci.AddKey('c');
	ci.AddKey('p');
	ci.AddKey('u');
	ci.AddKey('0');
	ci.AddKey('\t');
	UnitTest::Assert("tab completion should not have changed anything",
	    ci.GetCurrentCommandBuffer(), "cpu0");

	// Note: No space after component tab completion.
}

static void Test_CommandInterpreter_TabCompletion_ComponentNameMultiple()
{
	GXemul gxemul;
	CommandInterpreter& ci = gxemul.GetCommandInterpreter();

	ci.RunCommand("add testm88k");
	ci.RunCommand("add m88k_cpu mainbus0");
	UnitTest::Assert("initial buffer should be empty",
	    ci.GetCurrentCommandBuffer(), "");

	ci.AddKey('c');
	ci.AddKey('p');
	ci.AddKey('\t');
	UnitTest::Assert("there are both cpu0 and cpu1, so don't expand all the way",
	    ci.GetCurrentCommandBuffer(), "cpu");

	// Note: No space after component tab completion.
}

static void Test_CommandInterpreter_TabCompletion_ComponentNameMultipleParents()
{
	GXemul gxemul;
	CommandInterpreter& ci = gxemul.GetCommandInterpreter();

	ci.RunCommand("add testm88k");	// root.machine0
	ci.RunCommand("add testm88k");	// root.machine1
	UnitTest::Assert("initial buffer should be empty",
	    ci.GetCurrentCommandBuffer(), "");

	ci.AddKey('c');
	ci.AddKey('p');
	ci.AddKey('\t');
	UnitTest::Assert("there are cpu0 in both root.machine0 and root.machine1",
	    ci.GetCurrentCommandBuffer(), "machine");

	// Note: No space after component tab completion.
}

static void Test_CommandInterpreter_TabCompletion_ComponentNameNonexist()
{
	GXemul gxemul;
	CommandInterpreter& ci = gxemul.GetCommandInterpreter();

	ci.RunCommand("add dummy");
	UnitTest::Assert("initial buffer should be empty",
	    ci.GetCurrentCommandBuffer(), "");

	ci.AddKey('r');
	ci.AddKey('o');
	ci.AddKey('o');
	ci.AddKey('t');
	ci.AddKey('.');
	ci.AddKey('X');
	ci.AddKey('\t');
	UnitTest::Assert("tab completion should not have succeeded",
	    ci.GetCurrentCommandBuffer(), "root.X");
}

static void Test_CommandInterpreter_TabCompletion_ComponentNameAsArgument()
{
	GXemul gxemul;
	CommandInterpreter& ci = gxemul.GetCommandInterpreter();

	ci.RunCommand("add dummy root");
	UnitTest::Assert("initial buffer should be empty",
	    ci.GetCurrentCommandBuffer(), "");

	ci.AddKey('a');
	ci.AddKey('d');
	ci.AddKey('d');
	ci.AddKey(' ');
	ci.AddKey('d');
	ci.AddKey('u');
	ci.AddKey('m');
	ci.AddKey('m');
	ci.AddKey('y');
	ci.AddKey(' ');
	ci.AddKey('d');
	ci.AddKey('u');
	UnitTest::Assert("buffer contents mismatch",
	    ci.GetCurrentCommandBuffer(), "add dummy du");

	ci.AddKey('\t');
	UnitTest::Assert("tab completion should have completed the "
		"component name",
	    ci.GetCurrentCommandBuffer(), "add dummy dummy0");

	// Note: No space after component tab completion.
}

static void Test_CommandInterpreter_TabCompletion_CWithComponents()
{
	GXemul gxemul;
	CommandInterpreter& ci = gxemul.GetCommandInterpreter();

	ci.RunCommand("add testm88k");

	ci.AddKey('c');
	UnitTest::Assert("initial buffer contents mismatch",
	    ci.GetCurrentCommandBuffer(), "c");

	ci.AddKey('\t');
	UnitTest::Assert("tab completion should not have modified command",
	    ci.GetCurrentCommandBuffer(), "c");

	// ... because there are at least two possible commands on the
	// letter c: close and continue.
}

static void Test_CommandInterpreter_TabCompletion_roWithComponents()
{
	GXemul gxemul;
	CommandInterpreter& ci = gxemul.GetCommandInterpreter();

	ci.RunCommand("add testm88k");

	ci.AddKey('r');
	ci.AddKey('o');
	ci.AddKey('\t');
	// there are both "rom0" and "root".
	UnitTest::Assert("tab completion should not have expanded",
	    ci.GetCurrentCommandBuffer(), "ro");

	ci.AddKey('o');
	ci.AddKey('\t');
	UnitTest::Assert("tab completion should have expanded to 'root'",
	    ci.GetCurrentCommandBuffer(), "root");
}

static void Test_CommandInterpreter_TabCompletion_ComponentMethods_Empty()
{
	GXemul gxemul;
	CommandInterpreter& ci = gxemul.GetCommandInterpreter();

	ci.RunCommand("add testm88k");

	ci.AddKey('c');
	ci.AddKey('p');
	ci.AddKey('u');
	ci.AddKey('.');
	ci.AddKey('\t');
	UnitTest::Assert("tab completion should have caused expansion",
	    ci.GetCurrentCommandBuffer(), "cpu0.");
}

static void Test_CommandInterpreter_TabCompletion_ComponentMethods()
{
	GXemul gxemul;
	CommandInterpreter& ci = gxemul.GetCommandInterpreter();

	ci.RunCommand("add testm88k");

	ci.AddKey('c');
	ci.AddKey('p');
	ci.AddKey('u');
	ci.AddKey('.');
	ci.AddKey('u');
	ci.AddKey('\t');
	UnitTest::Assert("tab completion should have caused expansion",
	    ci.GetCurrentCommandBuffer(), "cpu0.unassemble");
}

static void Test_CommandInterpreter_TabCompletion_ComponentMethods_Middle()
{
	GXemul gxemul;
	CommandInterpreter& ci = gxemul.GetCommandInterpreter();

	ci.RunCommand("add testm88k");

	ci.AddKey('c');
	ci.AddKey('p');
	ci.AddKey('u');
	ci.AddKey('.');
	ci.AddKey('u');
	ci.AddKey('n');
	ci.AddKey('a');
	ci.AddKey('b');
	ci.AddKey('c');
	ci.AddKey('d');
	ci.AddKey('\2');
	ci.AddKey('\2');
	ci.AddKey('\2');	// cursor placed after "una"
	ci.AddKey('\t');
	UnitTest::Assert("tab completion should have caused expansion",
	    ci.GetCurrentCommandBuffer(), "cpu0.unassemblebcd");
}

static void Test_CommandInterpreter_TabCompletion_ComponentMethods_Arg()
{
	GXemul gxemul;
	CommandInterpreter& ci = gxemul.GetCommandInterpreter();

	ci.RunCommand("add testm88k");

	ci.AddKey('c');
	ci.AddKey('p');
	ci.AddKey('u');
	ci.AddKey('.');
	ci.AddKey('u');
	ci.AddKey(' ');
	ci.AddKey('a');
	ci.AddKey('d');
	ci.AddKey('d');
	ci.AddKey('r');
	ci.AddKey('\2');
	ci.AddKey('\2');
	ci.AddKey('\2');
	ci.AddKey('\2');
	ci.AddKey('\2');	// cursor placed after "u"
	ci.AddKey('\t');
	UnitTest::Assert("tab completion should have caused expansion",
	    ci.GetCurrentCommandBuffer(), "cpu0.unassemble addr");
}

static void Test_CommandInterpreter_TabCompletion_ComponentVariables()
{
	GXemul gxemul;
	CommandInterpreter& ci = gxemul.GetCommandInterpreter();

	ci.RunCommand("add testm88k");

	ci.AddKey('c');
	ci.AddKey('p');
	ci.AddKey('u');
	ci.AddKey('.');
	ci.AddKey('s');
	ci.AddKey('h');
	ci.AddKey('\t');
	UnitTest::Assert("tab completion should have caused expansion cpu -> cpu0",
	    ci.GetCurrentCommandBuffer(), "cpu0.showFunctionTrace");
}

static void Test_CommandInterpreter_TabCompletion_ComponentVariables_Max()
{
	GXemul gxemul;
	CommandInterpreter& ci = gxemul.GetCommandInterpreter();

	ci.RunCommand("add ram");

	ci.AddKey('r');
	ci.AddKey('a');
	ci.AddKey('m');
	ci.AddKey('.');
	ci.AddKey('m');
	ci.AddKey('e');
	ci.AddKey('m');
	ci.AddKey('o');
	ci.AddKey('\t');
	UnitTest::Assert("tab completion should have caused expansion ram -> ram0",
	    ci.GetCurrentCommandBuffer(), "ram0.memoryMapped");
}

static void Test_CommandInterpreter_TabCompletion_ComponentVariables_Max2()
{
	GXemul gxemul;
	CommandInterpreter& ci = gxemul.GetCommandInterpreter();

	ci.RunCommand("add ram");

	ci.AddKey('r');
	ci.AddKey('a');
	ci.AddKey('m');
	ci.AddKey('.');
	ci.AddKey('m');
	ci.AddKey('e');
	ci.AddKey('m');
	ci.AddKey('o');
	ci.AddKey(' ');
	ci.AddKey('2');
	ci.AddKey('\2');
	ci.AddKey('\2');
	ci.AddKey('\t');
	UnitTest::Assert("tab completion should have caused expansion ram -> ram0",
	    ci.GetCurrentCommandBuffer(), "ram0.memoryMapped 2");

	ci.AddKey('X');
	UnitTest::Assert("cursor position after tab completion was wrong?",
	    ci.GetCurrentCommandBuffer(), "ram0.memoryMappedX 2");
}

static void Test_CommandInterpreter_NonExistingCommand()
{
	GXemul gxemul;
	CommandInterpreter& ci = gxemul.GetCommandInterpreter();

	UnitTest::Assert("nonexisting (nonsense) command should fail",
	    ci.RunCommand("nonexistingcommand") == false);
}

static void Test_CommandInterpreter_SimpleCommand()
{
	GXemul gxemul;
	CommandInterpreter& ci = gxemul.GetCommandInterpreter();

	UnitTest::Assert("simple command should succeed",
	    ci.RunCommand("version") == true);

	UnitTest::Assert("simple command with whitespace should succeed",
	    ci.RunCommand("   version   ") == true);
}

static void Test_CommandInterpreter_SimpleCommand_NoArgsAllowed()
{
	GXemul gxemul;
	CommandInterpreter& ci = gxemul.GetCommandInterpreter();

	UnitTest::Assert("simple command should succeed",
	    ci.RunCommand("version") == true);

	UnitTest::Assert("simple command which does not take arguments should"
		" fail when attempt is made to execute it with arguments",
	    ci.RunCommand("version hello") == false);
}

static void Test_CommandInterpreter_ComponentMethods()
{
	GXemul gxemul;
	CommandInterpreter& ci = gxemul.GetCommandInterpreter();

	UnitTest::Assert("Huh? Could not add testm88k.",
	    ci.RunCommand("add testm88k") == true);

	UnitTest::Assert("component method 1",
	    ci.RunCommand("cpu") == true);
	UnitTest::Assert("component method 2",
	    ci.RunCommand("cpu.u") == true);
	UnitTest::Assert("component method 3",
	    ci.RunCommand("cpu.urk") == false);
	UnitTest::Assert("component method 4",
	    ci.RunCommand("cpu.unassemble") == true);
	UnitTest::Assert("component method 5",
	    ci.RunCommand("root.machine0.mainbus0.cpu") == true);
	UnitTest::Assert("component method 6",
	    ci.RunCommand("root.machine0.mainbus0.cpu0") == true);
	UnitTest::Assert("component method 7",
	    ci.RunCommand("root.machine0.mainbus0.cpu.u") == true);
	UnitTest::Assert("component method 8",
	    ci.RunCommand("root.machine0.mainbus0.cpu0.unassemble") == true);
}

static void Test_CommandInterpreter_ComponentVariables_NoArgs()
{
	GXemul gxemul;
	CommandInterpreter& ci = gxemul.GetCommandInterpreter();

	UnitTest::Assert("Huh? Could not add testm88k.",
	    ci.RunCommand("add testm88k") == true);

	UnitTest::Assert("component variable 1",
	    ci.RunCommand("cpu.nonexistant") == false);
	UnitTest::Assert("component variable 2",
	    ci.RunCommand("cpu.pau") == true);
	UnitTest::Assert("component variable 3",
	    ci.RunCommand("root.machine0.mainbus0.cpu0.pau") == true);
}

static void Test_CommandInterpreter_ComponentVariables_Ambiguous()
{
	GXemul gxemul;
	CommandInterpreter& ci = gxemul.GetCommandInterpreter();

	UnitTest::Assert("Huh? Could not add testmips.",
	    ci.RunCommand("add testm88k") == true);

	UnitTest::Assert("cpu.f should not work, there should be multiple matches",
	    ci.RunCommand("cpu.f") == false);
}

static void Test_CommandInterpreter_ComponentVariables_PartialIsOk()
{
	GXemul gxemul;
	CommandInterpreter& ci = gxemul.GetCommandInterpreter();

	UnitTest::Assert("Huh? Could not add testm88k.",
	    ci.RunCommand("add testm88k") == true);

	// Make sure that r2 works, and doesn't complain about ambiguity.
	UnitTest::Assert("gpr 2",
	    ci.RunCommand("cpu.r2") == true);
	UnitTest::Assert("gpr 29",
	    ci.RunCommand("cpu.r29") == true);
}

UNITTESTS(CommandInterpreter)
{
	// Key and current buffer:
	UNITTEST(Test_CommandInterpreter_AddKey_ReturnValue);
	UNITTEST(Test_CommandInterpreter_KeyBuffer);
	UNITTEST(Test_CommandInterpreter_KeyBuffer_CursorMovement);
	UNITTEST(Test_CommandInterpreter_KeyBuffer_CtrlK);
	UNITTEST(Test_CommandInterpreter_KeyBuffer_CtrlW);

	// Command History:
	UNITTEST(Test_CommandInterpreter_CommandHistory);

	// AddCommand / GetCommands:
	UNITTEST(Test_CommandInterpreter_AddCommand);

	// Tab completion:
	UNITTEST(Test_CommandInterpreter_TabCompletion_EmptyLine);
	UNITTEST(Test_CommandInterpreter_TabCompletion_FullWord);
	UNITTEST(Test_CommandInterpreter_TabCompletion_SpacesFirstOnLine);
	UNITTEST(Test_CommandInterpreter_TabCompletion_Partial);
	UNITTEST(Test_CommandInterpreter_TabCompletion_C);
	UNITTEST(Test_CommandInterpreter_TabCompletion_OnlyCommandAsFirstWord);
	UNITTEST(Test_CommandInterpreter_TabCompletion_ComponentName);
	UNITTEST(Test_CommandInterpreter_TabCompletion_ComponentNameAlreadyComplete);
	UNITTEST(Test_CommandInterpreter_TabCompletion_ComponentNameMultiple);
	UNITTEST(Test_CommandInterpreter_TabCompletion_ComponentNameMultipleParents);
	UNITTEST(Test_CommandInterpreter_TabCompletion_ComponentNameNonexist);
	UNITTEST(Test_CommandInterpreter_TabCompletion_ComponentNameAsArgument);
	UNITTEST(Test_CommandInterpreter_TabCompletion_CWithComponents);
	UNITTEST(Test_CommandInterpreter_TabCompletion_roWithComponents);
	UNITTEST(Test_CommandInterpreter_TabCompletion_ComponentMethods_Empty);
	UNITTEST(Test_CommandInterpreter_TabCompletion_ComponentMethods);
	UNITTEST(Test_CommandInterpreter_TabCompletion_ComponentMethods_Middle);
	UNITTEST(Test_CommandInterpreter_TabCompletion_ComponentMethods_Arg);
	UNITTEST(Test_CommandInterpreter_TabCompletion_ComponentVariables);
	UNITTEST(Test_CommandInterpreter_TabCompletion_ComponentVariables_Max);
	UNITTEST(Test_CommandInterpreter_TabCompletion_ComponentVariables_Max2);

	// RunCommand:
	UNITTEST(Test_CommandInterpreter_NonExistingCommand);
	UNITTEST(Test_CommandInterpreter_SimpleCommand);
	UNITTEST(Test_CommandInterpreter_SimpleCommand_NoArgsAllowed);
	UNITTEST(Test_CommandInterpreter_ComponentMethods);
	UNITTEST(Test_CommandInterpreter_ComponentVariables_NoArgs);
	UNITTEST(Test_CommandInterpreter_ComponentVariables_Ambiguous);
	UNITTEST(Test_CommandInterpreter_ComponentVariables_PartialIsOk);
}

#endif
