/*
 * synergy -- mouse and keyboard sharing utility
 * Copyright (C) 2012-2016 Symless Ltd.
 * Copyright (C) 2002 Chris Schoeneman
 * 
 * This package is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * found in the file LICENSE that should have accompanied this file.
 * 
 * This package 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 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "server/Config.h"

#include "server/Server.h"
#include "synergy/KeyMap.h"
#include "synergy/key_types.h"
#include "net/XSocket.h"
#include "base/IEventQueue.h"
#include "common/stdistream.h"
#include "common/stdostream.h"

#include <cstdlib>

using namespace synergy::string;

//
// Config
//

Config::Config(IEventQueue* events) :
	m_inputFilter(events),
	m_hasLockToScreenAction(false),
	m_events(events)
{
	// do nothing
}

Config::~Config()
{
	// do nothing
}

bool
Config::addScreen(const String& name)
{
	// alias name must not exist
	if (m_nameToCanonicalName.find(name) != m_nameToCanonicalName.end()) {
		return false;
	}

	// add cell
	m_map.insert(std::make_pair(name, Cell()));

	// add name
	m_nameToCanonicalName.insert(std::make_pair(name, name));

	return true;
}

bool
Config::renameScreen(const String& oldName,
							const String& newName)
{
	// get canonical name and find cell
	String oldCanonical = getCanonicalName(oldName);
	CellMap::iterator index = m_map.find(oldCanonical);
	if (index == m_map.end()) {
		return false;
	}

	// accept if names are equal but replace with new name to maintain
	// case.  otherwise, the new name must not exist.
	if (!CaselessCmp::equal(oldName, newName) &&
		m_nameToCanonicalName.find(newName) != m_nameToCanonicalName.end()) {
		return false;
	}

	// update cell
	Cell tmpCell = index->second;
	m_map.erase(index);
	m_map.insert(std::make_pair(newName, tmpCell));

	// update name
	m_nameToCanonicalName.erase(oldCanonical);
	m_nameToCanonicalName.insert(std::make_pair(newName, newName));

	// update connections
	Name oldNameObj(this, oldName);
	for (index = m_map.begin(); index != m_map.end(); ++index) {
		index->second.rename(oldNameObj, newName);
	}

	// update alias targets
	if (CaselessCmp::equal(oldName, oldCanonical)) {
		for (NameMap::iterator iter = m_nameToCanonicalName.begin();
							iter != m_nameToCanonicalName.end(); ++iter) {
			if (CaselessCmp::equal(
							iter->second, oldCanonical)) {
				iter->second = newName;
			}
		}
	}

	return true;
}

void
Config::removeScreen(const String& name)
{
	// get canonical name and find cell
	String canonical = getCanonicalName(name);
	CellMap::iterator index = m_map.find(canonical);
	if (index == m_map.end()) {
		return;
	}

	// remove from map
	m_map.erase(index);

	// disconnect
	Name nameObj(this, name);
	for (index = m_map.begin(); index != m_map.end(); ++index) {
		index->second.remove(nameObj);
	}

	// remove aliases (and canonical name)
	for (NameMap::iterator iter = m_nameToCanonicalName.begin();
								iter != m_nameToCanonicalName.end(); ) {
		if (iter->second == canonical) {
			m_nameToCanonicalName.erase(iter++);
		}
		else {
            ++iter;
		}
    }
}

void
Config::removeAllScreens()
{
	m_map.clear();
	m_nameToCanonicalName.clear();
}

bool
Config::addAlias(const String& canonical, const String& alias)
{
	// alias name must not exist
	if (m_nameToCanonicalName.find(alias) != m_nameToCanonicalName.end()) {
		return false;
	}

	// canonical name must be known
	if (m_map.find(canonical) == m_map.end()) {
		return false;
	}

	// insert alias
	m_nameToCanonicalName.insert(std::make_pair(alias, canonical));

	return true;
}

bool
Config::removeAlias(const String& alias)
{
	// must not be a canonical name
	if (m_map.find(alias) != m_map.end()) {
		return false;
	}

	// find alias
	NameMap::iterator index = m_nameToCanonicalName.find(alias);
	if (index == m_nameToCanonicalName.end()) {
		return false;
	}

	// remove alias
	m_nameToCanonicalName.erase(index);

	return true;
}

bool
Config::removeAliases(const String& canonical)
{
	// must be a canonical name
	if (m_map.find(canonical) == m_map.end()) {
		return false;
	}

	// find and removing matching aliases
	for (NameMap::iterator index = m_nameToCanonicalName.begin();
							index != m_nameToCanonicalName.end(); ) {
		if (index->second == canonical && index->first != canonical) {
			m_nameToCanonicalName.erase(index++);
		}
		else {
			++index;
		}
	}

	return true;
}

void
Config::removeAllAliases()
{
	// remove all names
	m_nameToCanonicalName.clear();

	// put the canonical names back in
	for (CellMap::iterator index = m_map.begin();
								index != m_map.end(); ++index) {
		m_nameToCanonicalName.insert(
								std::make_pair(index->first, index->first));
	}
}

bool
Config::connect(const String& srcName,
				EDirection srcSide,
				float srcStart, float srcEnd,
				const String& dstName,
				float dstStart, float dstEnd)
{
	assert(srcSide >= kFirstDirection && srcSide <= kLastDirection);

	// find source cell
	CellMap::iterator index = m_map.find(getCanonicalName(srcName));
	if (index == m_map.end()) {
		return false;
	}

	// add link
	CellEdge srcEdge(srcSide, Interval(srcStart, srcEnd));
	CellEdge dstEdge(dstName, srcSide, Interval(dstStart, dstEnd));
	return index->second.add(srcEdge, dstEdge);
}

bool
Config::disconnect(const String& srcName, EDirection srcSide)
{
	assert(srcSide >= kFirstDirection && srcSide <= kLastDirection);

	// find source cell
	CellMap::iterator index = m_map.find(srcName);
	if (index == m_map.end()) {
		return false;
	}

	// disconnect side
	index->second.remove(srcSide);

	return true;
}

bool
Config::disconnect(const String& srcName, EDirection srcSide, float position)
{
	assert(srcSide >= kFirstDirection && srcSide <= kLastDirection);

	// find source cell
	CellMap::iterator index = m_map.find(srcName);
	if (index == m_map.end()) {
		return false;
	}

	// disconnect side
	index->second.remove(srcSide, position);

	return true;
}

void
Config::setSynergyAddress(const NetworkAddress& addr)
{
	m_synergyAddress = addr;
}

bool
Config::addOption(const String& name, OptionID option, OptionValue value)
{
	// find options
	ScreenOptions* options = NULL;
	if (name.empty()) {
		options = &m_globalOptions;
	}
	else {
		CellMap::iterator index = m_map.find(name);
		if (index != m_map.end()) {
			options = &index->second.m_options;
		}
	}
	if (options == NULL) {
		return false;
	}

	// add option
	options->insert(std::make_pair(option, value));
	return true;
}

bool
Config::removeOption(const String& name, OptionID option)
{
	// find options
	ScreenOptions* options = NULL;
	if (name.empty()) {
		options = &m_globalOptions;
	}
	else {
		CellMap::iterator index = m_map.find(name);
		if (index != m_map.end()) {
			options = &index->second.m_options;
		}
	}
	if (options == NULL) {
		return false;
	}

	// remove option
	options->erase(option);
	return true;
}

bool
Config::removeOptions(const String& name)
{
	// find options
	ScreenOptions* options = NULL;
	if (name.empty()) {
		options = &m_globalOptions;
	}
	else {
		CellMap::iterator index = m_map.find(name);
		if (index != m_map.end()) {
			options = &index->second.m_options;
		}
	}
	if (options == NULL) {
		return false;
	}

	// remove options
	options->clear();
	return true;
}

bool
Config::isValidScreenName(const String& name) const
{
	// name is valid if matches validname
	//  name      ::= [_A-Za-z0-9] | [_A-Za-z0-9][-_A-Za-z0-9]*[_A-Za-z0-9]
	//  domain    ::= . name
	//  validname ::= name domain*
	// we also accept names ending in . because many OS X users have
	// so misconfigured their systems.

	// empty name is invalid
	if (name.empty()) {
		return false;
	}

	// check each dot separated part
	String::size_type b = 0;
	for (;;) {
		// accept trailing .
		if (b == name.size()) {
			break;
		}

		// find end of part
		String::size_type e = name.find('.', b);
		if (e == String::npos) {
			e = name.size();
		}

		// part may not be empty
		if (e - b < 1) {
			return false;
		}

		// check first and last characters
		if (!(isalnum(name[b]) || name[b] == '_') ||
			!(isalnum(name[e - 1]) || name[e - 1] == '_')) {
			return false;
		}

		// check interior characters
		for (String::size_type i = b; i < e; ++i) {
			if (!isalnum(name[i]) && name[i] != '_' && name[i] != '-') {
				return false;
			}
		}

		// next part
		if (e == name.size()) {
			// no more parts
			break;
		}
		b = e + 1;
	}

	return true;
}

Config::const_iterator
Config::begin() const
{
	return const_iterator(m_map.begin());
}

Config::const_iterator
Config::end() const
{
	return const_iterator(m_map.end());
}

Config::all_const_iterator
Config::beginAll() const
{
	return m_nameToCanonicalName.begin();
}

Config::all_const_iterator
Config::endAll() const
{
	return m_nameToCanonicalName.end();
}

bool
Config::isScreen(const String& name) const
{
	return (m_nameToCanonicalName.count(name) > 0);
}

bool
Config::isCanonicalName(const String& name) const
{
	return (!name.empty() &&
			CaselessCmp::equal(getCanonicalName(name), name));
}

String
Config::getCanonicalName(const String& name) const
{
	NameMap::const_iterator index = m_nameToCanonicalName.find(name);
	if (index == m_nameToCanonicalName.end()) {
		return String();
	}
	else {
		return index->second;
	}
}

String
Config::getNeighbor(const String& srcName, EDirection srcSide,
				float position, float* positionOut) const
{
	assert(srcSide >= kFirstDirection && srcSide <= kLastDirection);

	// find source cell
	CellMap::const_iterator index = m_map.find(getCanonicalName(srcName));
	if (index == m_map.end()) {
		return String();
	}

	// find edge
	const CellEdge* srcEdge, *dstEdge;
	if (!index->second.getLink(srcSide, position, srcEdge, dstEdge)) {
		// no neighbor
		return "";
	}
	else {
		// compute position on neighbor
		if (positionOut != NULL) {
			*positionOut =
				dstEdge->inverseTransform(srcEdge->transform(position));
		}

		// return neighbor's name
		return getCanonicalName(dstEdge->getName());
	}
}

bool
Config::hasNeighbor(const String& srcName, EDirection srcSide) const
{
	return hasNeighbor(srcName, srcSide, 0.0f, 1.0f);
}

bool
Config::hasNeighbor(const String& srcName, EDirection srcSide,
							float start, float end) const
{
	assert(srcSide >= kFirstDirection && srcSide <= kLastDirection);

	// find source cell
	CellMap::const_iterator index = m_map.find(getCanonicalName(srcName));
	if (index == m_map.end()) {
		return false;
	}

	return index->second.overlaps(CellEdge(srcSide, Interval(start, end)));
}

Config::link_const_iterator
Config::beginNeighbor(const String& srcName) const
{
	CellMap::const_iterator index = m_map.find(getCanonicalName(srcName));
	assert(index != m_map.end());
	return index->second.begin();
}

Config::link_const_iterator
Config::endNeighbor(const String& srcName) const
{
	CellMap::const_iterator index = m_map.find(getCanonicalName(srcName));
	assert(index != m_map.end());
	return index->second.end();
}

const NetworkAddress&
Config::getSynergyAddress() const
{
	return m_synergyAddress;
}

const Config::ScreenOptions*
Config::getOptions(const String& name) const
{
	// find options
	const ScreenOptions* options = NULL;
	if (name.empty()) {
		options = &m_globalOptions;
	}
	else {
		CellMap::const_iterator index = m_map.find(name);
		if (index != m_map.end()) {
			options = &index->second.m_options;
		}
	}

	// return options
	return options;
}

bool
Config::hasLockToScreenAction() const
{
	return m_hasLockToScreenAction;
}

bool
Config::operator==(const Config& x) const
{
	if (m_synergyAddress != x.m_synergyAddress) {
		return false;
	}
	if (m_map.size() != x.m_map.size()) {
		return false;
	}
	if (m_nameToCanonicalName.size() != x.m_nameToCanonicalName.size()) {
		return false;
	}

	// compare global options
	if (m_globalOptions != x.m_globalOptions) {
		return false;
	}

	auto index2map = x.m_map.cbegin();
	for (auto const &index1: m_map) {
		// compare names
		if (!CaselessCmp::equal(index1.first, index2map->first)) {
			return false;
		}

		// compare cells
		if (index1.second != index2map->second) {
			return false;
		}
		++index2map;
	}

	auto index2 = x.m_nameToCanonicalName.cbegin();
	for (auto const &index1: m_nameToCanonicalName) {
		if (index2 == x.m_nameToCanonicalName.cend()) {
			return false; // second source ended
		}
		if (!CaselessCmp::equal(index1.first,  index2->first) ||
			!CaselessCmp::equal(index1.second, index2->second)) {
			return false;
		}
		++index2;
	}

	// compare input filters
	if (m_inputFilter != x.m_inputFilter) {
		return false;
	}

	return true;
}

bool
Config::operator!=(const Config& x) const
{
	return !operator==(x);
}

void
Config::read(ConfigReadContext& context)
{
	Config tmp(m_events);
	while (context.getStream()) {
		tmp.readSection(context);
	}
	*this = tmp;
}

const char*
Config::dirName(EDirection dir)
{
	static const char* s_name[] = { "left", "right", "up", "down" };

	assert(dir >= kFirstDirection && dir <= kLastDirection);

	return s_name[dir - kFirstDirection];
}

InputFilter*
Config::getInputFilter()
{
	return &m_inputFilter;
}

String
Config::formatInterval(const Interval& x)
{
	if (x.first == 0.0f && x.second == 1.0f) {
		return "";
	}
	return synergy::string::sprintf("(%d,%d)", (int)(x.first * 100.0f + 0.5f),
										(int)(x.second * 100.0f + 0.5f));
}

String Config::getClientAddress() const
{
	return m_clientAddress;
}

bool Config::isClientMode() const
{
	return (!m_clientAddress.empty());
}

void
Config::readSection(ConfigReadContext& s)
{
	static const char s_section[] = "section:";
	static const char s_options[] = "options";
	static const char s_screens[] = "screens";
	static const char s_links[]   = "links";
	static const char s_aliases[] = "aliases";

	String line;
	if (!s.readLine(line)) {
		// no more sections
		return;
	}

	// should be a section header
	if (line.find(s_section) != 0) {
		throw XConfigRead(s, "found data outside section");
	}

	// get section name
	String::size_type i = line.find_first_not_of(" \t", sizeof(s_section) - 1);
	if (i == String::npos) {
		throw XConfigRead(s, "section name is missing");
	}
	String name = line.substr(i);
	i = name.find_first_of(" \t");
	if (i != String::npos) {
		throw XConfigRead(s, "unexpected data after section name");
	}

	// read section
	if (name == s_options) {
		readSectionOptions(s);
	}
	else if (name == s_screens) {
		readSectionScreens(s);
	}
	else if (name == s_links) {
		readSectionLinks(s);
	}
	else if (name == s_aliases) {
		readSectionAliases(s);
	}
	else {
		throw XConfigRead(s, "unknown section name \"%{1}\"", name);
	}
}

void
Config::readSectionOptions(ConfigReadContext& s)
{
	String line;
	while (s.readLine(line)) {
		// check for end of section
		if (line == "end") {
			return;
		}

		// parse argument:  `nameAndArgs = [values][;[values]]'
		//   nameAndArgs  := <name>[(arg[,...])]
		//   values       := valueAndArgs[,valueAndArgs]...
		//   valueAndArgs := <value>[(arg[,...])]
		String::size_type i = 0;
		String name, value;
		ConfigReadContext::ArgList nameArgs, valueArgs;
		s.parseNameWithArgs("name", line, "=", i, name, nameArgs);
		++i;
		s.parseNameWithArgs("value", line, ",;\n", i, value, valueArgs);

		bool handled = true;
		if (name == "address") {
			try {
				m_synergyAddress = NetworkAddress(value, kDefaultPort);
				m_synergyAddress.resolve();
			}
			catch (XSocketAddress& e) {
				throw XConfigRead(s,
							String("invalid address argument ") + e.what());
			}
		}
		else if (name == "heartbeat") {
			addOption("", kOptionHeartbeat, s.parseInt(value));
		}
		else if (name == "switchCorners") {
			addOption("", kOptionScreenSwitchCorners, s.parseCorners(value));
		}
		else if (name == "switchCornerSize") {
			addOption("", kOptionScreenSwitchCornerSize, s.parseInt(value));
		}
		else if (name == "switchDelay") {
			addOption("", kOptionScreenSwitchDelay, s.parseInt(value));
		}
		else if (name == "switchDoubleTap") {
			addOption("", kOptionScreenSwitchTwoTap, s.parseInt(value));
		}
		else if (name == "switchNeedsShift") {
			addOption("", kOptionScreenSwitchNeedsShift, s.parseBoolean(value));
		}
		else if (name == "switchNeedsControl") {
			addOption("", kOptionScreenSwitchNeedsControl, s.parseBoolean(value));
		}
		else if (name == "switchNeedsAlt") {
			addOption("", kOptionScreenSwitchNeedsAlt, s.parseBoolean(value));
		}
		else if (name == "relativeMouseMoves") {
			addOption("", kOptionRelativeMouseMoves, s.parseBoolean(value));
		}
		else if (name == "win32KeepForeground") {
			addOption("", kOptionWin32KeepForeground, s.parseBoolean(value));
		}
		else if (name == "disableLockToScreen") {
			addOption("", kOptionDisableLockToScreen, s.parseBoolean(value));
		}
		else if (name == "clipboardSharing") {
			addOption("", kOptionClipboardSharing, s.parseBoolean(value));
		}
		else if (name == "clipboardSharingSize") {
			addOption("", kOptionClipboardSharingSize, s.parseInt(value));
		}
		else if (name == "clientAddress") {
			m_clientAddress = value;
		}
		else {
			handled = false;
		}

		if (handled) {
			// make sure handled options aren't followed by more values
			if (i < line.size() && (line[i] == ',' || line[i] == ';')) {
				throw XConfigRead(s, "to many arguments to %s", name.c_str());
			}
		}
		else {
			// make filter rule
			InputFilter::Rule rule(parseCondition(s, name, nameArgs));

			// save first action (if any)
			if (!value.empty() || line[i] != ';') {
				parseAction(s, value, valueArgs, rule, true);
			}

			// get remaining activate actions
			while (i < line.length() && line[i] != ';') {
				++i;
				s.parseNameWithArgs("value", line, ",;\n", i, value, valueArgs);
				parseAction(s, value, valueArgs, rule, true);
			}

			// get deactivate actions
			if (i < line.length() && line[i] == ';') {
				// allow trailing ';'
				i = line.find_first_not_of(" \t", i + 1);
				if (i == String::npos) {
					i = line.length();
				}
				else {
					--i;
				}

				// get actions
				while (i < line.length()) {
					++i;
					s.parseNameWithArgs("value", line, ",\n",
								i, value, valueArgs);
					parseAction(s, value, valueArgs, rule, false);
				}
			}

			// add rule
			m_inputFilter.addFilterRule(rule);
		}
	}
	throw XConfigRead(s, "unexpected end of options section");
}

void
Config::readSectionScreens(ConfigReadContext& s)
{
	String line;
	String screen;
	while (s.readLine(line)) {
		// check for end of section
		if (line == "end") {
			return;
		}

		// see if it's the next screen
		if (line[line.size() - 1] == ':') {
			// strip :
			screen = line.substr(0, line.size() - 1);

			// verify validity of screen name
			if (!isValidScreenName(screen)) {
				throw XConfigRead(s, "invalid screen name \"%{1}\"", screen);
			}

			// add the screen to the configuration
			if (!addScreen(screen)) {
				throw XConfigRead(s, "duplicate screen name \"%{1}\"", screen);
			}
		}
		else if (screen.empty()) {
			throw XConfigRead(s, "argument before first screen");
		}
		else {
			// parse argument:  `<name>=<value>'
			String::size_type i = line.find_first_of(" \t=");
			if (i == 0) {
				throw XConfigRead(s, "missing argument name");
			}
			if (i == String::npos) {
				throw XConfigRead(s, "missing =");
			}
			String name = line.substr(0, i);
			i = line.find_first_not_of(" \t", i);
			if (i == String::npos || line[i] != '=') {
				throw XConfigRead(s, "missing =");
			}
			i = line.find_first_not_of(" \t", i + 1);
			String value;
			if (i != String::npos) {
				value = line.substr(i);
			}

			// handle argument
			if (name == "halfDuplexCapsLock") {
				addOption(screen, kOptionHalfDuplexCapsLock,
					s.parseBoolean(value));
			}
			else if (name == "halfDuplexNumLock") {
				addOption(screen, kOptionHalfDuplexNumLock,
					s.parseBoolean(value));
			}
			else if (name == "halfDuplexScrollLock") {
				addOption(screen, kOptionHalfDuplexScrollLock,
					s.parseBoolean(value));
			}
			else if (name == "shift") {
				addOption(screen, kOptionModifierMapForShift,
					s.parseModifierKey(value));
			}
			else if (name == "ctrl") {
				addOption(screen, kOptionModifierMapForControl,
					s.parseModifierKey(value));
			}
			else if (name == "alt") {
				addOption(screen, kOptionModifierMapForAlt,
					s.parseModifierKey(value));
			}
			else if (name == "altgr") {
				addOption(screen, kOptionModifierMapForAltGr,
					s.parseModifierKey(value));
			}
			else if (name == "meta") {
				addOption(screen, kOptionModifierMapForMeta,
					s.parseModifierKey(value));
			}
			else if (name == "super") {
				addOption(screen, kOptionModifierMapForSuper,
					s.parseModifierKey(value));
			}
			else if (name == "xtestIsXineramaUnaware") {
				addOption(screen, kOptionXTestXineramaUnaware,
					s.parseBoolean(value));
			}
			else if (name == "switchCorners") {
				addOption(screen, kOptionScreenSwitchCorners,
					s.parseCorners(value));
			}
			else if (name == "switchCornerSize") {
				addOption(screen, kOptionScreenSwitchCornerSize,
					s.parseInt(value));
			}
			else if (name == "preserveFocus") {
				addOption(screen, kOptionScreenPreserveFocus,
					s.parseBoolean(value));
			}
			else {
				// unknown argument
				throw XConfigRead(s, "unknown argument \"%{1}\"", name);
			}
		}
	}
	throw XConfigRead(s, "unexpected end of screens section");
}

void
Config::readSectionLinks(ConfigReadContext& s)
{
	String line;
	String screen;
	while (s.readLine(line)) {
		// check for end of section
		if (line == "end") {
			return;
		}

		// see if it's the next screen
		if (line[line.size() - 1] == ':') {
			// strip :
			screen = line.substr(0, line.size() - 1);

			// verify we know about the screen
			if (!isScreen(screen)) {
				throw XConfigRead(s, "unknown screen name \"%{1}\"", screen);
			}
			if (!isCanonicalName(screen)) {
				throw XConfigRead(s, "cannot use screen name alias here");
			}
		}
		else if (screen.empty()) {
			throw XConfigRead(s, "argument before first screen");
		}
		else {
			// parse argument:  `<name>[(<s0>,<e0>)]=<value>[(<s1>,<e1>)]'
			// the stuff in brackets is optional.  interval values must be
			// in the range [0,100] and start < end.  if not given the
			// interval is taken to be (0,100).
			String::size_type i = 0;
			String side, dstScreen, srcArgString, dstArgString;
			ConfigReadContext::ArgList srcArgs, dstArgs;
			s.parseNameWithArgs("link", line, "=", i, side, srcArgs);
			++i;
			s.parseNameWithArgs("screen", line, "", i, dstScreen, dstArgs);
			Interval srcInterval(s.parseInterval(srcArgs));
			Interval dstInterval(s.parseInterval(dstArgs));

			// handle argument
			EDirection dir;
			if (side == "left") {
				dir = kLeft;
			}
			else if (side == "right") {
				dir = kRight;
			}
			else if (side == "up") {
				dir = kTop;
			}
			else if (side == "down") {
				dir = kBottom;
			}
			else {
				// unknown argument
				throw XConfigRead(s, "unknown side \"%{1}\" in link", side);
			}
			if (!isScreen(dstScreen)) {
				throw XConfigRead(s, "unknown screen name \"%{1}\"", dstScreen);
			}
			if (!connect(screen, dir,
						srcInterval.first, srcInterval.second,
						dstScreen,
						dstInterval.first, dstInterval.second)) {
				throw XConfigRead(s, "overlapping range");
			}
		}
	}
	throw XConfigRead(s, "unexpected end of links section");
}

void
Config::readSectionAliases(ConfigReadContext& s)
{
	String line;
	String screen;
	while (s.readLine(line)) {
		// check for end of section
		if (line == "end") {
			return;
		}

		// see if it's the next screen
		if (line[line.size() - 1] == ':') {
			// strip :
			screen = line.substr(0, line.size() - 1);

			// verify we know about the screen
			if (!isScreen(screen)) {
				throw XConfigRead(s, "unknown screen name \"%{1}\"", screen);
			}
			if (!isCanonicalName(screen)) {
				throw XConfigRead(s, "cannot use screen name alias here");
			}
		}
		else if (screen.empty()) {
			throw XConfigRead(s, "argument before first screen");
		}
		else {
			// verify validity of screen name
			if (!isValidScreenName(line)) {
				throw XConfigRead(s, "invalid screen alias \"%{1}\"", line);
			}

			// add alias
			if (!addAlias(screen, line)) {
				throw XConfigRead(s, "alias \"%{1}\" is already used", line);
			}
		}
	}
	throw XConfigRead(s, "unexpected end of aliases section");
}


InputFilter::Condition*
Config::parseCondition(ConfigReadContext& s,
				const String& name, const std::vector<String>& args)
{
	if (name == "keystroke") {
		if (args.size() != 1) {
			throw XConfigRead(s, "syntax for condition: keystroke(modifiers+key)");
		}

		IPlatformScreen::KeyInfo* keyInfo = s.parseKeystroke(args[0]);

		return new InputFilter::KeystrokeCondition(m_events, keyInfo);
	}

	if (name == "mousebutton") {
		if (args.size() != 1) {
			throw XConfigRead(s, "syntax for condition: mousebutton(modifiers+button)");
		}

		IPlatformScreen::ButtonInfo* mouseInfo = s.parseMouse(args[0]);

		return new InputFilter::MouseButtonCondition(m_events, mouseInfo);
	}

	if (name == "connect") {
		if (args.size() != 1) {
			throw XConfigRead(s, "syntax for condition: connect([screen])");
		}

		String screen = args[0];
		if (isScreen(screen)) {
			screen = getCanonicalName(screen);
		}
		else if (!screen.empty()) {
			throw XConfigRead(s, "unknown screen name \"%{1}\" in connect", screen);
		}

		return new InputFilter::ScreenConnectedCondition(m_events, screen);
	}

	throw XConfigRead(s, "unknown argument \"%{1}\"", name);
}

void
Config::parseAction(ConfigReadContext& s,
				const String& name, const std::vector<String>& args,
				InputFilter::Rule& rule, bool activate)
{
	InputFilter::Action* action;

	if (name == "keystroke" || name == "keyDown" || name == "keyUp") {
		if (args.size() < 1 || args.size() > 2) {
			throw XConfigRead(s, "syntax for action: keystroke(modifiers+key[,screens])");
		}

		IPlatformScreen::KeyInfo* keyInfo;
		if (args.size() == 1) {
			keyInfo = s.parseKeystroke(args[0]);
		}
		else {
			std::set<String> screens;
			parseScreens(s, args[1], screens);
			keyInfo = s.parseKeystroke(args[0], screens);
		}

		if (name == "keystroke") {
			IPlatformScreen::KeyInfo* keyInfo2 =
				IKeyState::KeyInfo::alloc(*keyInfo);
			action = new InputFilter::KeystrokeAction(m_events, keyInfo2, true);
			rule.adoptAction(action, true);
			action   = new InputFilter::KeystrokeAction(m_events, keyInfo, false);
			activate = false;
		}
		else if (name == "keyDown") {
			action = new InputFilter::KeystrokeAction(m_events, keyInfo, true);
		}
		else {
			action = new InputFilter::KeystrokeAction(m_events, keyInfo, false);
		}
	}

	else if (name == "mousebutton" ||
				name == "mouseDown" || name == "mouseUp") {
		if (args.size() != 1) {
			throw XConfigRead(s, "syntax for action: mousebutton(modifiers+button)");
		}

		IPlatformScreen::ButtonInfo* mouseInfo = s.parseMouse(args[0]);

		if (name == "mousebutton") {
			IPlatformScreen::ButtonInfo* mouseInfo2 =
				IPlatformScreen::ButtonInfo::alloc(*mouseInfo);
			action = new InputFilter::MouseButtonAction(m_events, mouseInfo2, true);
			rule.adoptAction(action, true);
			action   = new InputFilter::MouseButtonAction(m_events, mouseInfo, false);
			activate = false;
		}
		else if (name == "mouseDown") {
			action = new InputFilter::MouseButtonAction(m_events, mouseInfo, true);
		}
		else {
			action = new InputFilter::MouseButtonAction(m_events, mouseInfo, false);
		}
	}

/* XXX -- not supported
	else if (name == "modifier") {
		if (args.size() != 1) {
			throw XConfigRead(s, "syntax for action: modifier(modifiers)");
		}

		KeyModifierMask mask = s.parseModifier(args[0]);

		action = new InputFilter::ModifierAction(mask, ~mask);
	}
*/

	else if (name == "switchToScreen") {
		if (args.size() != 1) {
			throw XConfigRead(s, "syntax for action: switchToScreen(name)");
		}

		String screen = args[0];
		if (isScreen(screen)) {
			screen = getCanonicalName(screen);
		}
		else if (!screen.empty()) {
			throw XConfigRead(s, "unknown screen name in switchToScreen");
		}

		action = new InputFilter::SwitchToScreenAction(m_events, screen);
	}

	else if (name == "switchInDirection") {
		if (args.size() != 1) {
			throw XConfigRead(s, "syntax for action: switchInDirection(<left|right|up|down>)");
		}

		EDirection direction;
		if (args[0] == "left") {
			direction = kLeft;
		}
		else if (args[0] == "right") {
			direction = kRight;
		}
		else if (args[0] == "up") {
			direction = kTop;
		}
		else if (args[0] == "down") {
			direction = kBottom;
		}
		else {
			throw XConfigRead(s, "unknown direction \"%{1}\" in switchToScreen", args[0]);
		}

		action = new InputFilter::SwitchInDirectionAction(m_events, direction);
	}

	else if (name == "lockCursorToScreen") {
		if (args.size() > 1) {
			throw XConfigRead(s, "syntax for action: lockCursorToScreen([{off|on|toggle}])");
		}

		InputFilter::LockCursorToScreenAction::Mode mode =
			InputFilter::LockCursorToScreenAction::kToggle;
		if (args.size() == 1) {
			if (args[0] == "off") {
				mode = InputFilter::LockCursorToScreenAction::kOff;
			}
			else if (args[0] == "on") {
				mode = InputFilter::LockCursorToScreenAction::kOn;
			}
			else if (args[0] == "toggle") {
				mode = InputFilter::LockCursorToScreenAction::kToggle;
			}
			else {
				throw XConfigRead(s, "syntax for action: lockCursorToScreen([{off|on|toggle}])");
			}
		}

		if (mode != InputFilter::LockCursorToScreenAction::kOff) {
			m_hasLockToScreenAction = true;
		}

		action = new InputFilter::LockCursorToScreenAction(m_events, mode);
	}

    else if (name == "restartServer") {
        if (args.size() > 1) {
            throw XConfigRead(s, "syntax for action: restartServer([{{restart}}])");
        }

        InputFilter::RestartServer::Mode mode =
                InputFilter::RestartServer::restart;

        if (args.size() == 1) {
            if (args[0] == "restart") {
                mode = InputFilter::RestartServer::restart;
            }
            else {
                throw XConfigRead(s, "syntax for action: restartServer([{restart}])");
            }
        }

        action = new InputFilter::RestartServer(m_events, mode);
    }

	else if (name == "keyboardBroadcast") {
		if (args.size() > 2) {
			throw XConfigRead(s, "syntax for action: keyboardBroadcast([{off|on|toggle}[,screens]])");
		}

		InputFilter::KeyboardBroadcastAction::Mode mode =
			InputFilter::KeyboardBroadcastAction::kToggle;
		if (args.size() >= 1) {
			if (args[0] == "off") {
				mode = InputFilter::KeyboardBroadcastAction::kOff;
			}
			else if (args[0] == "on") {
				mode = InputFilter::KeyboardBroadcastAction::kOn;
			}
			else if (args[0] == "toggle") {
				mode = InputFilter::KeyboardBroadcastAction::kToggle;
			}
			else {
				throw XConfigRead(s, "syntax for action: keyboardBroadcast([{off|on|toggle}[,screens]])");
			}
		}

		std::set<String> screens;
		if (args.size() >= 2) {
			parseScreens(s, args[1], screens);
		}

		action = new InputFilter::KeyboardBroadcastAction(m_events, mode, screens);
	}

	else {
		throw XConfigRead(s, "unknown action argument \"%{1}\"", name);
	}

	rule.adoptAction(action, activate);
}

void
Config::parseScreens(ConfigReadContext& c,
				const String& s, std::set<String>& screens) const
{
	screens.clear();

	String::size_type i = 0;
	while (i < s.size()) {
		// find end of next screen name
		String::size_type j = s.find(':', i);
		if (j == String::npos) {
			j = s.size();
		}

		// extract name
		String rawName;
		i = s.find_first_not_of(" \t", i);
		if (i < j) {
			rawName = s.substr(i, s.find_last_not_of(" \t", j - 1) - i + 1);
		}

		// add name
		if (rawName == "*") {
			screens.insert("*");
		}
		else if (!rawName.empty()) {
			String name = getCanonicalName(rawName);
			if (name.empty()) {
				throw XConfigRead(c, "unknown screen name \"%{1}\"", rawName);
			}
			screens.insert(name);
		}

		// next
		i = j + 1;
	}
}

const char*
Config::getOptionName(OptionID id)
{
	if (id == kOptionHalfDuplexCapsLock) {
		return "halfDuplexCapsLock";
	}
	if (id == kOptionHalfDuplexNumLock) {
		return "halfDuplexNumLock";
	}
	if (id == kOptionHalfDuplexScrollLock) {
		return "halfDuplexScrollLock";
	}
	if (id == kOptionModifierMapForShift) {
		return "shift";
	}
	if (id == kOptionModifierMapForControl) {
		return "ctrl";
	}
	if (id == kOptionModifierMapForAlt) {
		return "alt";
	}
	if (id == kOptionModifierMapForAltGr) {
		return "altgr";
	}
	if (id == kOptionModifierMapForMeta) {
		return "meta";
	}
	if (id == kOptionModifierMapForSuper) {
		return "super";
	}
	if (id == kOptionHeartbeat) {
		return "heartbeat";
	}
	if (id == kOptionScreenSwitchCorners) {
		return "switchCorners";
	}
	if (id == kOptionScreenSwitchCornerSize) {
		return "switchCornerSize";
	}
	if (id == kOptionScreenSwitchDelay) {
		return "switchDelay";
	}
	if (id == kOptionScreenSwitchTwoTap) {
		return "switchDoubleTap";
	}
	if (id == kOptionScreenSwitchNeedsShift) {
		return "switchNeedsShift";
	}
	if (id == kOptionScreenSwitchNeedsControl) {
		return "switchNeedsControl";
	}
	if (id == kOptionScreenSwitchNeedsAlt) {
		return "switchNeedsAlt";
	}
	if (id == kOptionXTestXineramaUnaware) {
		return "xtestIsXineramaUnaware";
	}
	if (id == kOptionRelativeMouseMoves) {
		return "relativeMouseMoves";
	}
	if (id == kOptionWin32KeepForeground) {
		return "win32KeepForeground";
	}
	if (id == kOptionScreenPreserveFocus) {
		return "preserveFocus";
	}
	if (id == kOptionDisableLockToScreen) {
		return "disableLockToScreen";
	}
	if (id == kOptionClipboardSharing) {
		return "clipboardSharing";
	}
	if (id == kOptionClipboardSharingSize) {
		return "clipboardSharingSize";
	}
	return NULL;
}

String
Config::getOptionValue(OptionID id, OptionValue value)
{
	if (id == kOptionHalfDuplexCapsLock ||
		id == kOptionHalfDuplexNumLock ||
		id == kOptionHalfDuplexScrollLock ||
		id == kOptionScreenSwitchNeedsShift ||
		id == kOptionScreenSwitchNeedsControl ||
		id == kOptionScreenSwitchNeedsAlt ||
		id == kOptionXTestXineramaUnaware ||
		id == kOptionRelativeMouseMoves ||
		id == kOptionWin32KeepForeground ||
		id == kOptionScreenPreserveFocus ||
		id == kOptionClipboardSharing ||
		id == kOptionClipboardSharingSize) {
		return (value != 0) ? "true" : "false";
	}
	if (id == kOptionModifierMapForShift ||
		id == kOptionModifierMapForControl ||
		id == kOptionModifierMapForAlt ||
		id == kOptionModifierMapForAltGr ||
		id == kOptionModifierMapForMeta ||
		id == kOptionModifierMapForSuper) {
		switch (value) {
		case kKeyModifierIDShift:
			return "shift";

		case kKeyModifierIDControl:
			return "ctrl";

		case kKeyModifierIDAlt:
			return "alt";

		case kKeyModifierIDAltGr:
			return "altgr";

		case kKeyModifierIDMeta:
			return "meta";

		case kKeyModifierIDSuper:
			return "super";

		default:
			return "none";
		}
	}
	if (id == kOptionHeartbeat ||
		id == kOptionScreenSwitchCornerSize ||
		id == kOptionScreenSwitchDelay ||
		id == kOptionScreenSwitchTwoTap) {
		return synergy::string::sprintf("%d", value);
	}
	if (id == kOptionScreenSwitchCorners) {
		std::string result("none");
		if ((value & kTopLeftMask) != 0) {
			result += " +top-left";
		}
		if ((value & kTopRightMask) != 0) {
			result += " +top-right";
		}
		if ((value & kBottomLeftMask) != 0) {
			result += " +bottom-left";
		}
		if ((value & kBottomRightMask) != 0) {
			result += " +bottom-right";
		}
		return result;
	}

	return "";
}


//
// Config::Name
//

Config::Name::Name(Config* config, const String& name) :
	m_config(config),
	m_name(config->getCanonicalName(name))
{
	// do nothing
}

bool
Config::Name::operator==(const String& name) const
{
	String canonical = m_config->getCanonicalName(name);
	return CaselessCmp::equal(canonical, m_name);
}


//
// Config::CellEdge
//

Config::CellEdge::CellEdge(EDirection side, float position)
{
	init("", side, Interval(position, position));
}

Config::CellEdge::CellEdge(EDirection side, const Interval& interval)
{
	assert(interval.first >= 0.0f);
	assert(interval.second <= 1.0f);
	assert(interval.first < interval.second);

	init("", side, interval);
}

Config::CellEdge::CellEdge(const String& name,
				EDirection side, const Interval& interval)
{
	assert(interval.first >= 0.0f);
	assert(interval.second <= 1.0f);
	assert(interval.first < interval.second);

	init(name, side, interval);
}

Config::CellEdge::~CellEdge()
{
	// do nothing
}

void
Config::CellEdge::init(const String& name, EDirection side,
				const Interval& interval)
{
	assert(side != kNoDirection);

	m_name     = name;
	m_side     = side;
	m_interval = interval;
}

Config::Interval
Config::CellEdge::getInterval() const
{
	return m_interval;
}

void
Config::CellEdge::setName(const String& newName)
{
	m_name = newName;
}

String
Config::CellEdge::getName() const
{
	return m_name;
}

EDirection
Config::CellEdge::getSide() const
{
	return m_side;
}

bool
Config::CellEdge::overlaps(const CellEdge& edge) const
{
	const Interval& x = m_interval;
	const Interval& y = edge.m_interval;
	if (m_side != edge.m_side) {
		return false;
	}
	return  (x.first  >= y.first && x.first  <  y.second) ||
			(x.second >  y.first && x.second <= y.second) ||
			(y.first  >= x.first && y.first  <  x.second) ||
			(y.second >  x.first && y.second <= x.second);
}

bool
Config::CellEdge::isInside(float x) const
{
	return (x >= m_interval.first && x < m_interval.second);
}

float
Config::CellEdge::transform(float x) const
{
	return (x - m_interval.first) / (m_interval.second - m_interval.first);
}


float
Config::CellEdge::inverseTransform(float x) const
{
	return x * (m_interval.second - m_interval.first) + m_interval.first;
}

bool
Config::CellEdge::operator<(const CellEdge& o) const
{
	if (static_cast<int>(m_side) < static_cast<int>(o.m_side)) {
		return true;
	}
	else if (static_cast<int>(m_side) > static_cast<int>(o.m_side)) {
		return false;
	}

	return (m_interval.first < o.m_interval.first);
}

bool
Config::CellEdge::operator==(const CellEdge& x) const
{
	return (m_side == x.m_side && m_interval == x.m_interval);
}

bool
Config::CellEdge::operator!=(const CellEdge& x) const
{
	return !operator==(x);
}


//
// Config::Cell
//

bool
Config::Cell::add(const CellEdge& src, const CellEdge& dst)
{
	// cannot add an edge that overlaps other existing edges but we
	// can exactly replace an edge.
	if (!hasEdge(src) && overlaps(src)) {
		return false;
	}

	m_neighbors.erase(src);
	m_neighbors.insert(std::make_pair(src, dst));
	return true;
}

void
Config::Cell::remove(EDirection side)
{
	for (EdgeLinks::iterator j = m_neighbors.begin();
							j != m_neighbors.end(); ) {
		if (j->first.getSide() == side) {
			m_neighbors.erase(j++);
		}
		else {
			++j;
		}
	}
}

void
Config::Cell::remove(EDirection side, float position)
{
	for (EdgeLinks::iterator j = m_neighbors.begin();
							j != m_neighbors.end(); ++j) {
		if (j->first.getSide() == side && j->first.isInside(position)) {
			m_neighbors.erase(j);
			break;
		}
	}
}
void
Config::Cell::remove(const Name& name)
{
	for (EdgeLinks::iterator j = m_neighbors.begin();
							j != m_neighbors.end(); ) {
		if (name == j->second.getName()) {
			m_neighbors.erase(j++);
		}
		else {
			++j;
		}
	}
}

void
Config::Cell::rename(const Name& oldName, const String& newName)
{
	for (EdgeLinks::iterator j = m_neighbors.begin();
							j != m_neighbors.end(); ++j) {
		if (oldName == j->second.getName()) {
			j->second.setName(newName);
		}
	}
}

bool
Config::Cell::hasEdge(const CellEdge& edge) const
{
	EdgeLinks::const_iterator i = m_neighbors.find(edge);
	return (i != m_neighbors.end() && i->first == edge);
}

bool
Config::Cell::overlaps(const CellEdge& edge) const
{
	EdgeLinks::const_iterator i = m_neighbors.upper_bound(edge);
	if (i != m_neighbors.end() && i->first.overlaps(edge)) {
		return true;
	}
	if (i != m_neighbors.begin() && (--i)->first.overlaps(edge)) {
		return true;
	}
	return false;
}

bool
Config::Cell::getLink(EDirection side, float position,
				const CellEdge*& src, const CellEdge*& dst) const
{
	CellEdge edge(side, position);
	EdgeLinks::const_iterator i = m_neighbors.upper_bound(edge);
	if (i == m_neighbors.begin()) {
		return false;
	}
	--i;
	if (i->first.getSide() == side && i->first.isInside(position)) {
		src = &i->first;
		dst = &i->second;
		return true;
	}
	return false;
}

bool
Config::Cell::operator==(const Cell& x) const
{
	// compare options
	if (m_options != x.m_options) {
		return false;
	}

	// compare links
	if (m_neighbors.size() != x.m_neighbors.size()) {
		return false;
	}

	auto index2neighbors = x.m_neighbors.cbegin();
	for (auto const &index1: m_neighbors) {
		if (index1.first != index2neighbors->first) {
			return false;
		}
		if (index1.second != index2neighbors->second) {
			return false;
		}

		// operator== doesn't compare names.  only compare destination
		// names.
		if (!CaselessCmp::equal(index1.second.getName(),
								index2neighbors->second.getName())) {
			return false;
		}
		++index2neighbors;
	}

	return true;
}

bool
Config::Cell::operator!=(const Cell& x) const
{
	return !operator==(x);
}

Config::Cell::const_iterator
Config::Cell::begin() const
{
	return m_neighbors.begin();
}

Config::Cell::const_iterator
Config::Cell::end() const
{
	return m_neighbors.end();
}


//
// Config I/O
//

std::istream&
operator>>(std::istream& s, Config& config)
{
	ConfigReadContext context(s);
	config.read(context);
	return s;
}

std::ostream&
operator<<(std::ostream& s, const Config& config)
{
	// screens section
	s << "section: screens" << std::endl;
	for (Config::const_iterator screen = config.begin();
								screen != config.end(); ++screen) {
		s << "\t" << screen->c_str() << ":" << std::endl;
		const Config::ScreenOptions* options = config.getOptions(*screen);
		if (options != NULL && options->size() > 0) {
			for (Config::ScreenOptions::const_iterator
								option  = options->begin();
								option != options->end(); ++option) {
				const char* name = Config::getOptionName(option->first);
				String value    = Config::getOptionValue(option->first,
															option->second);
				if (name != NULL && !value.empty()) {
					s << "\t\t" << name << " = " << value << std::endl;
				}
			}
		}
	}
	s << "end" << std::endl;

	// links section
	String neighbor;
	s << "section: links" << std::endl;
	for (Config::const_iterator screen = config.begin();
								screen != config.end(); ++screen) {
		s << "\t" << screen->c_str() << ":" << std::endl;

		for (Config::link_const_iterator
				link = config.beginNeighbor(*screen),
				nend = config.endNeighbor(*screen); link != nend; ++link) {			
			s << "\t\t" << Config::dirName(link->first.getSide()) <<
				Config::formatInterval(link->first.getInterval()) <<
				" = " << link->second.getName().c_str() <<
				Config::formatInterval(link->second.getInterval()) <<
				std::endl;
		}
	}
	s << "end" << std::endl;

	// aliases section (if there are any)
	if (config.m_map.size() != config.m_nameToCanonicalName.size()) {
		// map canonical to alias
		typedef std::multimap<String, String,
								CaselessCmp> CMNameMap;
		CMNameMap aliases;
		for (Config::NameMap::const_iterator
								index = config.m_nameToCanonicalName.begin();
								index != config.m_nameToCanonicalName.end();
								++index) {
			if (index->first != index->second) {
				aliases.insert(std::make_pair(index->second, index->first));
			}
		}

		// dump it
		String screen;
		s << "section: aliases" << std::endl;
		for (CMNameMap::const_iterator index = aliases.begin();
								index != aliases.end(); ++index) {
			if (index->first != screen) {
				screen = index->first;
				s << "\t" << screen.c_str() << ":" << std::endl;
			}
			s << "\t\t" << index->second.c_str() << std::endl;
		}
		s << "end" << std::endl;
	}

	// options section
	s << "section: options" << std::endl;
	const Config::ScreenOptions* options = config.getOptions("");
	if (options != NULL && options->size() > 0) {
		for (Config::ScreenOptions::const_iterator
							option  = options->begin();
							option != options->end(); ++option) {
			const char* name = Config::getOptionName(option->first);
			String value    = Config::getOptionValue(option->first,
														option->second);
			if (name != NULL && !value.empty()) {
				s << "\t" << name << " = " << value << std::endl;
			}
		}
	}
	if (config.m_synergyAddress.isValid()) {
		s << "\taddress = " <<
			config.m_synergyAddress.getHostname().c_str() << std::endl;
	}
	s << config.m_inputFilter.format("\t");
	s << "end" << std::endl;

	return s;
}


//
// ConfigReadContext
//

ConfigReadContext::ConfigReadContext(std::istream& s, SInt32 firstLine) :
	m_stream(s),
	m_line(firstLine - 1)
{
	// do nothing
}

ConfigReadContext::~ConfigReadContext()
{
	// do nothing
}

bool
ConfigReadContext::readLine(String& line)
{
	++m_line;
	while (std::getline(m_stream, line)) {
		// strip leading whitespace
		String::size_type i = line.find_first_not_of(" \t");
		if (i != String::npos) {
			line.erase(0, i);
		}

		// strip comments and then trailing whitespace
		i = line.find('#');
		if (i != String::npos) {
			line.erase(i);
		}
		i = line.find_last_not_of(" \r\t");
		if (i != String::npos) {
			line.erase(i + 1);
		}

		// return non empty line
		if (!line.empty()) {
			// make sure there are no invalid characters
			for (i = 0; i < line.length(); ++i) {
				if (!isgraph(line[i]) && line[i] != ' ' && line[i] != '\t') {
					throw XConfigRead(*this,
								"invalid character %{1}",
								synergy::string::sprintf("%#2x", line[i]));
				}
			}

			return true;
		}

		// next line
		++m_line;
	}
	return false;
}

UInt32
ConfigReadContext::getLineNumber() const
{
	return m_line;
}

bool
ConfigReadContext::operator!() const
{
	return !m_stream;
}

OptionValue
ConfigReadContext::parseBoolean(const String& arg) const
{
	if (CaselessCmp::equal(arg, "true")) {
		return static_cast<OptionValue>(true);
	}
	if (CaselessCmp::equal(arg, "false")) {
		return static_cast<OptionValue>(false);
	}
	throw XConfigRead(*this, "invalid boolean argument \"%{1}\"", arg);
}

OptionValue
ConfigReadContext::parseInt(const String& arg) const
{
	const char* s = arg.c_str();
	char* end;
	long tmp      = strtol(s, &end, 10);
	if (*end != '\0') {
		// invalid characters
		throw XConfigRead(*this, "invalid integer argument \"%{1}\"", arg);
	}
	OptionValue value = static_cast<OptionValue>(tmp);
	if (value != tmp) {
		// out of range
		throw XConfigRead(*this, "integer argument \"%{1}\" out of range", arg);
	}
	return value;
}

OptionValue
ConfigReadContext::parseModifierKey(const String& arg) const
{
	if (CaselessCmp::equal(arg, "shift")) {
		return static_cast<OptionValue>(kKeyModifierIDShift);
	}
	if (CaselessCmp::equal(arg, "ctrl")) {
		return static_cast<OptionValue>(kKeyModifierIDControl);
	}
	if (CaselessCmp::equal(arg, "alt")) {
		return static_cast<OptionValue>(kKeyModifierIDAlt);
	}
	if (CaselessCmp::equal(arg, "altgr")) {
		return static_cast<OptionValue>(kKeyModifierIDAltGr);
	}
	if (CaselessCmp::equal(arg, "meta")) {
		return static_cast<OptionValue>(kKeyModifierIDMeta);
	}
	if (CaselessCmp::equal(arg, "super")) {
		return static_cast<OptionValue>(kKeyModifierIDSuper);
	}
	if (CaselessCmp::equal(arg, "none")) {
		return static_cast<OptionValue>(kKeyModifierIDNull);
	}
	throw XConfigRead(*this, "invalid argument \"%{1}\"", arg);
}

OptionValue
ConfigReadContext::parseCorner(const String& arg) const
{
	if (CaselessCmp::equal(arg, "left")) {
		return kTopLeftMask | kBottomLeftMask;
	}
	else if (CaselessCmp::equal(arg, "right")) {
		return kTopRightMask | kBottomRightMask;
	}
	else if (CaselessCmp::equal(arg, "top")) {
		return kTopLeftMask | kTopRightMask;
	}
	else if (CaselessCmp::equal(arg, "bottom")) {
		return kBottomLeftMask | kBottomRightMask;
	}
	else if (CaselessCmp::equal(arg, "top-left")) {
		return kTopLeftMask;
	}
	else if (CaselessCmp::equal(arg, "top-right")) {
		return kTopRightMask;
	}
	else if (CaselessCmp::equal(arg, "bottom-left")) {
		return kBottomLeftMask;
	}
	else if (CaselessCmp::equal(arg, "bottom-right")) {
		return kBottomRightMask;
	}
	else if (CaselessCmp::equal(arg, "none")) {
		return kNoCornerMask;
	}
	else if (CaselessCmp::equal(arg, "all")) {
		return kAllCornersMask;
	}
	throw XConfigRead(*this, "invalid argument \"%{1}\"", arg);
}

OptionValue
ConfigReadContext::parseCorners(const String& args) const
{
	// find first token
	String::size_type i = args.find_first_not_of(" \t", 0);
	if (i == String::npos) {
		throw XConfigRead(*this, "missing corner argument");
	}
	String::size_type j = args.find_first_of(" \t", i);

	// parse first corner token
	OptionValue corners = parseCorner(args.substr(i, j - i));

	// get +/-
	i = args.find_first_not_of(" \t", j);
	while (i != String::npos) {
		// parse +/-
		bool add;
		if (args[i] == '-') {
			add = false;
		}
		else if (args[i] == '+') {
			add = true;
		}
		else {
			throw XConfigRead(*this,
							"invalid corner operator \"%{1}\"",
							String(args.c_str() + i, 1));
		}

		// get next corner token
		i = args.find_first_not_of(" \t", i + 1);
		j = args.find_first_of(" \t", i);
		if (i == String::npos) {
			throw XConfigRead(*this, "missing corner argument");
		}

		// parse next corner token
		if (add) {
			corners |= parseCorner(args.substr(i, j - i));
		}
		else {
			corners &= ~parseCorner(args.substr(i, j - i));
		}
		i = args.find_first_not_of(" \t", j);
	}

	return corners;
}

Config::Interval
ConfigReadContext::parseInterval(const ArgList& args) const
{
	if (args.size() == 0) {
		return Config::Interval(0.0f, 1.0f);
	}
	if (args.size() != 2 || args[0].empty() || args[1].empty()) {
		throw XConfigRead(*this, "invalid interval \"%{1}\"", concatArgs(args));
	}

	char* end;
	double startValue = strtod(args[0].c_str(), &end);
	if (end[0] != '\0') {
		throw XConfigRead(*this, "invalid interval \"%{1}\"", concatArgs(args));
	}
	double endValue = strtod(args[1].c_str(), &end);
	if (end[0] != '\0') {
		throw XConfigRead(*this, "invalid interval \"%{1}\"", concatArgs(args));
	}

	if (startValue < 0 || startValue > 100 ||
		endValue   < 0 || endValue   > 100 ||
		startValue >= endValue) {
		throw XConfigRead(*this, "invalid interval \"%{1}\"", concatArgs(args));
	}

	float startInterval = static_cast<float>(startValue / 100.0f);
	float endInterval = static_cast<float>(endValue / 100.0f);
	return Config::Interval(startInterval, endInterval);
}

void
ConfigReadContext::parseNameWithArgs(
				const String& type, const String& line,
				const String& delim, String::size_type& index,
				String& name, ArgList& args) const
{
	// skip leading whitespace
	String::size_type i = line.find_first_not_of(" \t", index);
	if (i == String::npos) {
		throw XConfigRead(*this, String("missing ") + type);
	}

	// find end of name
	String::size_type j = line.find_first_of(" \t(" + delim, i);
	if (j == String::npos) {
		j = line.length();
	}

	// save name
	name = line.substr(i, j - i);
	args.clear();

	// is it okay to not find a delimiter?
	bool needDelim = (!delim.empty() && delim.find('\n') == String::npos);

	// skip whitespace
	i = line.find_first_not_of(" \t", j);
	if (i == String::npos && needDelim) {
		// expected delimiter but didn't find it
		throw XConfigRead(*this, String("missing ") + delim[0]);
	}
	if (i == String::npos) {
		// no arguments
		index = line.length();
		return;
	}
	if (line[i] != '(') {
		// no arguments
		index = i;
		return;
	}

	// eat '('
	++i;

	// parse arguments
	j = line.find_first_of(",)", i);
	while (j != String::npos) {
		// extract arg
		String arg(line.substr(i, j - i));
		i = j;

		// trim whitespace
		j = arg.find_first_not_of(" \t");
		if (j != String::npos) {
			arg.erase(0, j);
		}
		j = arg.find_last_not_of(" \t");
		if (j != String::npos) {
			arg.erase(j + 1);
		}

		// save arg
		args.push_back(arg);

		// exit loop at end of arguments
		if (line[i] == ')') {
			break;
		}

		// eat ','
		++i;

		// next
		j = line.find_first_of(",)", i);
	}

	// verify ')'
	if (j == String::npos) {
		// expected )
		throw XConfigRead(*this, "missing )");
	}

	// eat ')'
	++i;

	// skip whitespace
	j = line.find_first_not_of(" \t", i);
	if (j == String::npos && needDelim) {
		// expected delimiter but didn't find it
		throw XConfigRead(*this, String("missing ") + delim[0]);
	}

	// verify delimiter
	if (needDelim && delim.find(line[j]) == String::npos) {
		throw XConfigRead(*this, String("expected ") + delim[0]);
	}

	if (j == String::npos) {
		j = line.length();
	}

	index = j;
	return;
}

IPlatformScreen::KeyInfo*
ConfigReadContext::parseKeystroke(const String& keystroke) const
{
	return parseKeystroke(keystroke, std::set<String>());
}

IPlatformScreen::KeyInfo*
ConfigReadContext::parseKeystroke(const String& keystroke,
				const std::set<String>& screens) const
{
	String s = keystroke;

	KeyModifierMask mask;
	if (!synergy::KeyMap::parseModifiers(s, mask)) {
		throw XConfigRead(*this, "unable to parse key modifiers");
	}

	KeyID key;
	if (!synergy::KeyMap::parseKey(s, key)) {
		throw XConfigRead(*this, "unable to parse key");
	}

	if (key == kKeyNone && mask == 0) {
		throw XConfigRead(*this, "missing key and/or modifiers in keystroke");
	}

	return IPlatformScreen::KeyInfo::alloc(key, mask, 0, 0, screens);
}

IPlatformScreen::ButtonInfo*
ConfigReadContext::parseMouse(const String& mouse) const
{
	String s = mouse;

	KeyModifierMask mask;
	if (!synergy::KeyMap::parseModifiers(s, mask)) {
		throw XConfigRead(*this, "unable to parse button modifiers");
	}

	char* end;
	ButtonID button = (ButtonID)strtol(s.c_str(), &end, 10);
	if (*end != '\0') {
		throw XConfigRead(*this, "unable to parse button");
	}
	if (s.empty() || button <= 0) {
		throw XConfigRead(*this, "invalid button");
	}

	return IPlatformScreen::ButtonInfo::alloc(button, mask);
}

KeyModifierMask
ConfigReadContext::parseModifier(const String& modifiers) const
{
	String s = modifiers;

	KeyModifierMask mask;
	if (!synergy::KeyMap::parseModifiers(s, mask)) {
		throw XConfigRead(*this, "unable to parse modifiers");
	}

	if (mask == 0) {
		throw XConfigRead(*this, "no modifiers specified");
	}

	return mask;
}

String
ConfigReadContext::concatArgs(const ArgList& args)
{
	String s("(");
	for (size_t i = 0; i < args.size(); ++i) {
		if (i != 0) {
			s += ",";
		}
		s += args[i];
	}
	s += ")";
	return s;
}


//
// Config I/O exceptions
//

XConfigRead::XConfigRead(const ConfigReadContext& context,
				const String& error) :
	m_error(synergy::string::sprintf("line %d: %s",
							context.getLineNumber(), error.c_str()))
{
	// do nothing
}

XConfigRead::XConfigRead(const ConfigReadContext& context,
				const char* errorFmt, const String& arg) :
	m_error(synergy::string::sprintf("line %d: ", context.getLineNumber()) +
							synergy::string::format(errorFmt, arg.c_str()))
{
	// do nothing
}

XConfigRead::~XConfigRead() _NOEXCEPT
{
	// do nothing
}

String
XConfigRead::getWhat() const throw()
{
	return format("XConfigRead", "read error: %{1}", m_error.c_str());
}
