/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#include "glk/quest/geas_runner.h"
#include "glk/quest/read_file.h"
#include "glk/quest/geas_state.h"
#include "glk/quest/geas_util.h"
#include "glk/quest/reserved_words.h"
#include "glk/quest/geas_impl.h"
#include "glk/quest/quest.h"
#include "glk/quest/streams.h"
#include "glk/quest/string.h"

namespace Glk {
namespace Quest {

class GeasInterface;

static const char *dir_names[] = {"north", "south", "east", "west", "northeast", "northwest", "southeast", "southwest", "up", "down", "out"};
static const char *short_dir_names[] = {"n", "s", "e", "w", "ne", "nw", "se", "sw", "u", "d", "out"};

const ObjectRecord *get_obj_record(const Common::Array<ObjectRecord> &v, const String &name) {
	for (uint i = 0; i < v.size(); i ++)
		if (ci_equal(v[i].name, name))
			return &v[i];
	return nullptr;
}



GeasRunner *GeasRunner::get_runner(GeasInterface *gi) {
	return new geas_implementation(gi);
}

bool geas_implementation::find_ivar(String name, uint &rv) const {
	for (uint n = 0; n < state.ivars.size(); n ++)
		if (ci_equal(state.ivars[n].name, name)) {
			rv = n;
			return true;
		}
	return false;
}

bool geas_implementation::find_svar(String name, uint &rv) const {
	//name = lcase (name);
	for (uint n = 0; n < state.svars.size(); n ++)
		if (ci_equal(state.svars[n].name, name)) {
			rv = n;
			return true;
		}
	return false;
}

void geas_implementation::set_svar(String varname, String varval) {
	cerr << "set_svar (" << varname << ", " << varval << ")\n";
	int i1 = varname.find('[');
	if (i1 == -1)
		return set_svar(varname, 0, varval);
	if (varname[varname.length() - 1] != ']') {
		gi->debug_print("set_svar: Badly formatted name " + varname);
		return;
	}
	String arrayname = varname.substr(0, i1);
	String indextext = varname.substr(i1 + 1, varname.length() - i1 - 2);
	cerr << "set_svar(" << varname << ") --> set_svar (" << arrayname << ", " << indextext << ")\n";
	for (uint c3 = 0; c3 < indextext.size(); c3 ++)
		if (indextext[c3] < '0' || indextext[c3] > '9') {
			set_svar(arrayname, get_ivar(indextext), varval);
			return;
		}
	set_svar(arrayname, parse_int(indextext), varval);
	return;
}

void geas_implementation::set_svar(String varname, uint index, String varval) {
	uint n, m;
	if (!find_svar(varname, n)) {
		if (find_ivar(varname, m)) {
			gi->debug_print("Defining " + varname + " as String variable when there is already a numeric variable of that name.");
			return;
		}
		SVarRecord svr;
		svr.name = varname;
		n = state.svars.size();
		state.svars.push_back(svr);
	}
	state.svars[n].set(index, varval);
	if (index == 0) {
		for (uint varn = 0; varn < gf.size("variable"); varn ++) {
			const GeasBlock &go(gf.block("variable", varn));
			if (ci_equal(go.name, varname)) {
				String script = "";
				uint c1, c2;
				for (uint j = 0; j < go.data.size(); j ++)
					// SENSITIVE ?
					if (first_token(go.data[j], c1, c2) == "onchange")
						script = trim(go.data[j].substr(c2 + 1));
				if (script != "")
					run_script(script);
			}
		}
	}
}

String geas_implementation::get_svar(String varname) const {
	int i1 = varname.find('[');
	if (i1 == -1)
		return get_svar(varname, 0);
	if (varname[varname.length() - 1] != ']') {
		gi->debug_print("get_svar: badly formatted name " + varname);
		return "";
	}
	String arrayname = varname.substr(0, i1);
	String indextext = varname.substr(i1 + 1, varname.length() - i1 - 2);
	cerr << "get_svar(" << varname << ") --> get_svar (" << arrayname << ", " << indextext << ")\n";
	for (uint c3 = 0; c3 < indextext.size(); c3 ++)
		if (indextext[c3] < '0' || indextext[c3] > '9')
			return get_svar(arrayname, get_ivar(indextext));
	return get_svar(arrayname, parse_int(indextext));
}
String geas_implementation::get_svar(String varname, uint index) const {
	for (uint i = 0; i < state.svars.size(); i ++) {
		if (ci_equal(state.svars[i].name, varname))
			return state.svars[i].get(index);
	}

	gi->debug_print("get_svar (" + varname + ", " + string_int(index) + "): No such variable defined.");
	return "";
}

int geas_implementation::get_ivar(String varname) const {
	int i1 = varname.find('[');
	if (i1 == -1)
		return get_ivar(varname, 0);
	if (varname[varname.length() - 1] != ']') {
		gi->debug_print("get_ivar: Badly formatted name " + varname);
		return -32767;
	}
	String arrayname = varname.substr(0, i1);
	String indextext = varname.substr(i1 + 1, varname.length() - i1 - 2);
	cerr << "get_ivar(" << varname << ") --> get_ivar (" << arrayname << ", " << indextext << ")\n";
	for (uint c3 = 0; c3 < indextext.size(); c3 ++)
		if (indextext[c3] < '0' || indextext[c3] > '9')
			return get_ivar(arrayname, get_ivar(indextext));
	return get_ivar(arrayname, parse_int(indextext));
}
int geas_implementation::get_ivar(String varname, uint index) const {
	for (uint i = 0; i < state.ivars.size(); i ++)
		if (ci_equal(state.ivars[i].name, varname))
			return state.ivars[i].get(index);
	gi->debug_print("get_ivar: Tried to read undefined int '" + varname +
	                "' [" + string_int(index) + "]");
	return -32767;
}
void geas_implementation::set_ivar(String varname, int varval) {
	int i1 = varname.find('[');
	if (i1 == -1)
		return set_ivar(varname, 0, varval);
	if (varname[varname.length() - 1] != ']') {
		gi->debug_print("set_ivar: Badly formatted name " + varname);
		return;
	}
	String arrayname = varname.substr(0, i1);
	String indextext = varname.substr(i1 + 1, varname.length() - i1 - 2);
	cerr << "set_svar(" << varname << ") --> set_svar (" << arrayname << ", " << indextext << ")\n";
	for (uint c3 = 0; c3 < indextext.size(); c3 ++)
		if (indextext[c3] < '0' || indextext[c3] > '9') {
			set_ivar(arrayname, get_ivar(indextext), varval);
			return;
		}
	set_ivar(arrayname, parse_int(indextext), varval);
}

void geas_implementation::set_ivar(String varname, uint index, int varval) {
	uint n, m;
	if (!find_ivar(varname, n)) {
		if (find_svar(varname, m)) {
			gi->debug_print("Defining " + varname + " as numeric variable when there is already a String variable of that name.");
			return;
		}
		IVarRecord ivr;
		ivr.name = varname;
		n = state.ivars.size();
		state.ivars.push_back(ivr);
	}
	state.ivars[n].set(index, varval);
	if (index == 0) {
		for (uint varn = 0; varn < gf.size("variable"); varn ++) {
			const GeasBlock &go(gf.block("variable", varn));
			//if (go.lname == varname)
			if (ci_equal(go.name, varname)) {
				String script = "";
				uint c1, c2;
				for (uint j = 0; j < go.data.size(); j ++)
					// SENSITIVE?
					if (first_token(go.data[j], c1, c2) == "onchange")
						script = trim(go.data[j].substr(c2 + 1));
				if (script != "")
					run_script(script);
			}
		}
	}
}



Common::WriteStream &operator<<(Common::WriteStream &o, const match_binding &mb) {
	o << "MB['" << mb.var_name << "' == '" << mb.var_text << "' @ "
	  << mb.start << " to " << mb.end << "]";
	return o;
}

String match_binding::tostring() {
	ostringstream oss;
	oss << *this;
	return oss.str();
}

Common::WriteStream &operator<<(Common::WriteStream &o, const Set<String> &s) {
	o << "{ ";
	for (Set<String>::const_iterator i = s.begin(); i != s.end(); i ++) {
		if (i != s.begin())
			o << ", ";
		o << (*i);
	}
	o << " }";
	return o;
}

bool geas_implementation::has_obj_action(String obj, String prop) const {
	String tmp;
	return get_obj_action(obj, prop, tmp);
}


bool geas_implementation::get_obj_action(String objname, String actname,
        String &rv) const {
	//String backup_object = this_object;
	//this_object = objname;

	cerr << "get_obj_action (" << objname << ", " << actname << ")\n";
	String tok;
	uint c1, c2;
	for (uint i = state.props.size() - 1; i + 1 > 0; i --)
		if (state.props[i].name == objname) {
			String line = state.props[i].data;
			// SENSITIVE?
			if (first_token(line, c1, c2) != "action")
				continue;
			tok = next_token(line, c1, c2);
			if (!is_param(tok) || ci_equal(param_contents(tok), actname))
				continue;
			rv = trim(line.substr(c2));
			cerr << "  g_o_a: returning true, \"" << rv << "\".";
			return true;
		}
	return gf.get_obj_action(objname, actname, rv);
	//bool bool_rv = gf.get_obj_action (objname, actname, rv);
	//this_object = backup_object;
	//return bool_rv;
}

bool geas_implementation::has_obj_property(String obj, String prop) const {
	String tmp;
	return get_obj_property(obj, prop, tmp);
}

bool geas_implementation::get_obj_property(String obj, String prop,
        String &string_rv) const {
	String is_prop = "properties " + prop;
	String not_prop = "properties not " + prop;
	for (uint i = state.props.size() - 1; i + 1 > 0; i --)
		if (ci_equal(state.props[i].name, obj)) {
			String dat = state.props[i].data;
			//cerr << "In looking for " << obj << ":" << prop << ", got line "
			//     << dat << endl;
			if (ci_equal(dat, not_prop)) {
				//cerr << "   not_prop, returning false\n";
				string_rv = "!";
				return false;
			}
			if (ci_equal(dat, is_prop)) {
				//cerr << "   is_prop, returning true\n";
				string_rv = "";
				return true;
			}
			int index = dat.find('=');
			if (index != -1 && ci_equal(dat.substr(0, index), is_prop)) {
				string_rv = dat.substr(index + 1);
				return true;
			}
		}
	return gf.get_obj_property(obj, prop, string_rv);
}

void geas_implementation::set_obj_property(String obj, String prop) {
	state.props.push_back(PropertyRecord(obj, "properties " + prop));
	if (ci_equal(prop, "hidden") || ci_equal(prop, "not hidden") ||
	        ci_equal(prop, "invisible") || ci_equal(prop, "not invisible")) {
		gi->update_sidebars();
		regen_var_objects();
	}
}

void geas_implementation::set_obj_action(String obj, String act) {
	state.props.push_back(PropertyRecord(obj, "action " + act));
}

void geas_implementation::move(String obj, String dest) {
	for (uint i = 0; i < state.objs.size(); i ++)
		if (ci_equal(state.objs[i].name, obj)) {
			state.objs[i].parent = dest;
			gi->update_sidebars();
			regen_var_objects();
			return;
		}
	gi->debug_print("Tried to move nonexistent object '" + obj +
	                "' to '" + dest + "'.");
}

String geas_implementation::get_obj_parent(String obj) {
	//obj = lcase (obj);
	for (uint i = 0; i < state.objs.size(); i ++)
		if (ci_equal(state.objs[i].name, obj))
			return state.objs[i].parent;
	gi->debug_print("Tried to find parent of nonexistent object " + obj);
	return "";
}

void geas_implementation::goto_room(String room) {
	state.location = room;
	regen_var_room();
	regen_var_dirs();
	regen_var_look();
	regen_var_objects();
	String scr;
	if (get_obj_action(room, "script", scr))
		run_script_as(room, scr);
	//run_script (scr);
	look();
}

void geas_implementation::display_error(String errorname, String obj) {
	cerr << "display_error (" << errorname << ", " << obj << ")\n";
	if (obj != "") {
		String tmp;
		if (!get_obj_property(obj, "gender", tmp))
			tmp = "it";
		set_svar("quest.error.gender", tmp);

		if (!get_obj_property(obj, "article", tmp))
			tmp = "it";
		set_svar("quest.error.article", tmp);

		cerr << "In erroring " << errorname << " / " << obj << ", qeg == "
		     << get_svar("quest.error.gender") << ", qea == "
		     << get_svar("quest.error.article") << endl;
		// TODO quest.error.charactername
	}

	const GeasBlock *game = gf.find_by_name("game", "game");
	assert(game != NULL);
	String tok;
	uint c1, c2;
	for (uint i = 0; i < game->data.size(); i ++) {
		String line = game->data[i];
		tok = first_token(line, c1, c2);
		// SENSITIVE?
		if (tok == "error") {
			tok = next_token(line, c1, c2);
			if (is_param(tok)) {
				String text = param_contents(tok);
				int index = text.find(';');
				String errortype = trim(text.substr(0, index));
				// SENSITIVE?
				if (errortype == errorname) {
					print_eval_p(trim(text.substr(index + 1)));
					return;
				}
			} else
				gi->debug_print("Bad error line: " + line);
		}
	}
	//print_formatted ("Default error " + errorname);

	// ARE THESE SENSITIVE?
	if (errorname == "badcommand")
		print_eval("I don't understand your command. Type HELP for a list of valid commands.");
	else if (errorname == "badgo")
		print_eval("I don't understand your use of 'GO' - you must either GO in some direction, or GO TO a place.");
	else if (errorname == "badgive")
		print_eval("You didn't say who you wanted to give that to.");
	else if (errorname == "badcharacter")
		print_eval("I can't see anybody of that name here.");
	else if (errorname == "noitem")
		print_eval("You don't have that.");
	else if (errorname == "itemunwanted")
		print_eval_p("#quest.error.gender# doesn't want #quest.error.article#.");
	else if (errorname == "badlook")
		print_eval("You didn't say what you wanted to look at.");
	else if (errorname == "badthing")
		print_eval("I can't see that here.");
	else if (errorname == "defaultlook")
		print_eval("Nothing out of the ordinary.");
	else if (errorname == "defaultspeak")
		print_eval_p("#quest.error.gender# says nothing.");
	else if (errorname == "baditem")
		print_eval("I can't see that anywhere.");
	else if (errorname == "defaulttake")
		print_eval("You pick #quest.error.article# up.");
	else if (errorname == "baduse")
		print_eval("You didn't say what you wanted to use that on.");
	else if (errorname == "defaultuse")
		print_eval("You can't use that here.");
	else if (errorname == "defaultout")
		print_eval("There's nowhere you can go out to around here.");
	else if (errorname == "badplace")
		print_eval("You can't go there.");
	else if (errorname == "defaultexamine")
		print_eval("Nothing out of the ordinary.");
	else if (errorname == "badtake")
		print_eval("You can't take #quest.error.article#.");
	else if (errorname == "cantdrop")
		print_eval("You can't drop that here.");
	else if (errorname == "defaultdrop")
		print_eval("You drop #quest.error.article#.");
	else if (errorname == "baddrop")
		print_eval("You are not carrying such a thing.");
	else if (errorname == "badpronoun")
		print_eval("I don't know what '#quest.error.pronoun#' you are referring to.");
	else if (errorname == "badexamine")
		print_eval("You didn't say what you wanted to examine.");
	else
		gi->debug_print("Bad error name " + errorname);
}

String geas_implementation::displayed_name(String obj) const {
	String rv = obj, tmp;

	if (get_obj_property(obj, "alias", tmp))
		rv = tmp;
	else {
		for (uint i = 0; i < gf.blocks.size(); i ++)
			if (ci_equal(gf.blocks[i].name, obj)) {
				rv = gf.blocks[i].name;
				break;
			}
	}
	return rv;
}

/* For each destination, give:
 * - printed name
 * - accepted name, with prefix
 * - accepted name, without prefix
 * - destination, internal format
 * - script (optional)
 */
Common::Array<Common::Array<String> > geas_implementation::get_places(String room) {
	Common::Array<Common::Array<String> > rv;

	const GeasBlock *gb = gf.find_by_name("room", room);
	if (gb == NULL)
		return rv;

	String line, tok;
	uint c1, c2;
	for (uint i = 0; i < gb->data.size(); i ++) {
		line = gb->data[i];
		tok = first_token(line, c1, c2);
		if (tok == "place") {
			tok = next_token(line, c1, c2);
			if (!is_param(tok)) {
				gi->debug_print("Expected parameter after 'place' in " + line);
				continue;
			}
			String dest_param = eval_param(tok);
			if (dest_param == "") {
				gi->debug_print("Parameter empty in " + line);
				continue;
			}

			int j = dest_param.find(';');
			String dest, prefix = "";
			if (j == -1)
				dest = trim(dest_param);
			else {
				dest = trim(dest_param.substr(j + 1));
				prefix = trim(dest_param.substr(0, j));
			}
			String displayed = displayed_name(dest);
			String printed_dest = (prefix != "" ? prefix + " " : "") +
			                      "|b" + displayed + "|xb";

			Common::Array<String> tmp;
			tmp.push_back(printed_dest);
			tmp.push_back(prefix + " " + displayed);
			tmp.push_back(displayed);
			tmp.push_back(dest);
			String rest = trim(line.substr(c2));
			if (rest != "")
				tmp.push_back(rest);
			rv.push_back(tmp);
		}
	}

	for (uint i = 0; i < state.exits.size(); i ++) {
		if (state.exits[i].src != room)
			continue;
		line = state.exits[i].dest;
		tok = first_token(line, c1, c2);
		if (tok == "exit") {
			tok = next_token(line, c1, c2);
			if (!is_param(tok))
				continue;
			tok = next_token(line, c1, c2);
			assert(is_param(tok));
			tok = param_contents(tok);
			Common::Array<String> args = split_param(tok);
			if (args.size() != 2) {
				gi->debug_print("Expected two arguments in " + tok);
				continue;
			}
			assert(args[0] == room);
			Common::Array<String> tmp;
			String displayed = displayed_name(args[1]);
			tmp.push_back("|b" + displayed + "|xb");
			tmp.push_back(displayed);
			tmp.push_back(displayed);
			tmp.push_back(args[1]);
			rv.push_back(tmp);
		} else if (tok == "destroy") {
			tok = next_token(line, c1, c2);
			assert(tok == "exit");
			tok = next_token(line, c1, c2);

			for (v2string::iterator j = rv.begin(); j != rv.end(); j ++)
				if ((*j)[3] == tok) {
					rv.erase(j);
					break;
				}
		}


	}

	cerr << "get_places (" << room << ") -> " << rv << "\n";
	return rv;
}

String geas_implementation::exit_dest(String room, String dir, bool *is_script) const {
	uint c1, c2;
	String tok;
	if (is_script != NULL)
		*is_script = false;
	for (uint i = state.exits.size() - 1; i + 1 > 0; i --)
		if (state.exits[i].src == room) {
			String line = state.exits[i].dest;
			cerr << "Processing exit line '" << state.exits[i].dest << "'\n";
			tok = first_token(line, c1, c2);
			cerr << "   first tok is " << tok << " (vs. exit)\n";
			// SENSITIVE?
			if (tok != "exit")
				continue;
			tok = next_token(line, c1, c2);
			cerr << "   second tok is " << tok << " (vs. " << dir << ")\n";
			if (tok != dir)
				continue;
			tok = next_token(line, c1, c2);
			cerr << "   third tok is " << tok << " (expecting parameter)\n";
			assert(is_param(tok));
			Common::Array<String> p = split_param(param_contents(tok));
			assert(p.size() == 2);
			assert(ci_equal(p[0], room));
			return p[1];
		}
	/*
	if (gf.get_obj_action (room, dir, tok))
	  {
	    if (is_script != NULL)
	*is_script = true;
	    return tok;
	  }
	if (gf.get_obj_property (room, dir, tok))
	  return tok;
	else
	  return "";
	*/

	const GeasBlock *gb = gf.find_by_name("room", room);
	if (gb == NULL) {
		gi->debug_print(String("Trying to find exit <") + dir +
		                "> of nonexistent room <" + room + ">.");
		return "";
	}
	// TODO: what's the priority on this?
	for (uint i = 0; i < gb->data.size(); i ++) {
		String line = gb->data[i];
		tok = first_token(line, c1, c2);
		if (tok == dir) {
			uint line_start = c2;
			tok = next_token(line, c1, c2);
			if (is_param(tok))
				return param_contents(tok);
			if (tok != "") {
				if (is_script != NULL)
					*is_script = true;
				return trim(line.substr(line_start + 1));
			}
			return "";
		}
	}
	return "";
}

void geas_implementation::look() {
	String tmp;
	if (get_obj_action(state.location, "description", tmp))
		run_script_as(state.location, tmp);
	//run_script(tmp);
	else if (get_obj_property(state.location, "description", tmp))
		print_formatted(tmp);
	else if (get_obj_action("game", "description", tmp))
		run_script_as("game", tmp);
	//run_script (tmp);
	else if (get_obj_property("game", "description", tmp))
		print_formatted(tmp);
	else {
		String in_desc;
		if (get_obj_property(state.location, "indescription", tmp))
			in_desc = tmp;
		else
			in_desc = "You are in";
		print_formatted(in_desc + " " + get_svar("quest.formatroom"));

		if ((tmp = get_svar("quest.formatobjects")) != "")
			//print_formatted ("There is " + tmp + " here.");
			print_eval("There is #quest.formatobjects# here.");
		if ((tmp = get_svar("quest.doorways.out")) != "")
			print_formatted("You can go out to " + tmp + ".");
		if ((tmp = get_svar("quest.doorways.dirs")) != "")
			//print_formatted ("You can go " + tmp + ".");
			print_eval("You can go #quest.doorways.dirs#.");
		if ((tmp = get_svar("quest.doorways.places")) != "")
			print_formatted("You can go to " + tmp + ".");
		if ((tmp = get_svar("quest.lookdesc")) != "")
			print_formatted(tmp);
	}
}

void geas_implementation::set_game(const String &fname) {
	cerr << "set_game (...)\n";

	gf = read_geas_file(gi, fname);
	if (gf.blocks.size() == 0) {
		is_running_ = false;
		return;
	}
	//print_formatted ("Ready...|n|cbblack|crred|clblue|cggreen|cyyellow|n|uunderlined: |cbblack|crred|clblue|cggreen|cyyellow|xu|n");
	//cerr << "Read game " << gf << endl;
	uint tok_start, tok_end;
	outputting = true;

	state = GeasState(*gi, gf);

	state.running = true;

	for (uint gline = 0; gline < gf.block("game", 0).data.size(); gline ++) {
		String s = gf.block("game", 0).data[gline];
		String tok = first_token(s, tok_start, tok_end);
		// SENSITIVE?
		if (tok == "asl-version") {
			String ver = next_token(s, tok_start, tok_end);
			if (!is_param(ver)) {
				gi->debug_print("Version " + s + " has invalid version " +
					            ver);
				continue;
			}
			int vernum = parse_int(param_contents(ver));
			if (vernum < 311 || vernum > 353)
				gi->debug_print("Warning: Geas only supports ASL "
					            " versions 3.11 to 3.53");
		}
		// SENSITIVE?
		else if (tok == "background") {
			tok = next_token(s, tok_start, tok_end);
			if (!is_param(tok))
				gi->debug_print(nonparam("background color", s));
			else
				gi->set_background(param_contents(tok));
		}
		// SENSITIVE?
		else if (tok == "default") {
			tok = next_token(s, tok_start, tok_end);
			// SENSITIVE?
			if (tok == "fontname") {
				tok = next_token(s, tok_start, tok_end);
				if (!is_param(tok))
					gi->debug_print(nonparam("font name", s));
				else
					gi->set_default_font(param_contents(tok));
			}
			// SENSITIVE?
			else if (tok == "fontsize") {
				tok = next_token(s, tok_start, tok_end);
				if (!is_param(tok))
					gi->debug_print(nonparam("font size", s));
				else
					gi->set_default_font_size(param_contents(tok));
			}
		}
		// SENSITIVE?
		else if (tok == "foreground") {
			tok = next_token(s, tok_start, tok_end);
			if (!is_param(tok))
				gi->debug_print(nonparam("foreground color", s));
			else
				gi->set_foreground(param_contents(tok));
		}
		// SENSITIVE?
		else if (tok == "gametype") {
			tok = next_token(s, tok_start, tok_end);
			// SENSITIVE?
			if (tok == "singleplayer")
				continue;
			// SENSITIVE?
			if (tok == "multiplayer")
				error("Error: geas is single player only.");
			gi->debug_print("Unexpected game type " + s);
		}
		// SENSITIVE?
		else if (tok == "nodebug") {
		}
		// SENSITIVE?
		else if (tok == "start") {
			tok = next_token(s, tok_start, tok_end);
			if (!is_param(tok))
				gi->debug_print(nonparam("start room", s));
			else {
				state.location = param_contents(tok);
			}
		}
	}

	const GeasBlock &game = gf.block("game", 0);
	cerr << gf << endl;
	//print_formatted ("Done loading " + game.name);
	uint c1, c2;
	String tok;

	/* TODO do I run the startscript or print the opening text first? */
	run_script("displaytext <intro>");

	for (uint i = 0; i < game.data.size(); i ++)
		// SENSITIVE?
		if (first_token(game.data[i], c1, c2) == "startscript") {
			run_script_as("game", game.data[i].substr(c2 + 1));
			//run_script (game.data[i].substr (c2 + 1));
			break;
		}

	regen_var_room();
	regen_var_objects();
	regen_var_dirs();
	regen_var_look();
	look();

	cerr << "s_g: done with set_game (...)\n\n";
}

void geas_implementation::regen_var_objects() {
	String tmp;
	Common::Array <String> objs;
	for (uint i = 0; i < state.objs.size(); i ++) {
		//cerr << "r_v_o: Checking '" << state.objs[i].name << "' (" << state.objs[i].parent << "): " << ((state.objs[i].parent == state.location) ? "YES" : "NO") << endl;
		if (ci_equal(state.objs[i].parent, state.location) &&
		        !get_obj_property(state.objs[i].name, "hidden", tmp) &&
		        !get_obj_property(state.objs[i].name, "invisible", tmp))
			//!state.objs[i].hidden &&
			//!state.objs[i].invisible)
			objs.push_back(state.objs[i].name);
	}
	String qobjs = "", qfobjs = "";
	String objname, prefix, main, suffix, propval, print1, print2;
	for (uint i = 0; i < objs.size(); i ++) {
		objname = objs[i];
		if (!get_obj_property(objname, "alias", main))
			main = objname;
		print1 = main;
		print2 = "|b" + main + "|xb";
		if (get_obj_property(objname, "prefix", prefix)) {
			print1 = prefix + " " + print1;
			print2 = prefix + " " + print2;
		}
		if (get_obj_property(objname, "suffix", suffix)) {
			print1 = print1 + " " + suffix;
			print2 = print2 + " " + suffix;
		}
		qobjs = qobjs + print1;
		qfobjs = qfobjs + print2;
		if (i + 2 < objs.size()) {
			qobjs = qobjs + ", ";
			qfobjs = qfobjs + ", ";
		} else if (i + 2 == objs.size()) {
			qobjs = qobjs + " and ";
			qfobjs = qfobjs + " and ";
		}
	}
	set_svar("quest.objects", qobjs);
	set_svar("quest.formatobjects", qfobjs);
}

void geas_implementation::regen_var_room() {
	set_svar("quest.currentroom", state.location);

	String tmp, formatroom;
	if (!get_obj_property(state.location, "alias", formatroom))
		formatroom = state.location;
	formatroom = "|cr" + formatroom + "|cb";
	if (get_obj_property(state.location, "prefix", tmp))
		formatroom = tmp + " " + formatroom;
	if (get_obj_property(state.location, "suffix", tmp))
		formatroom = formatroom + " " + tmp;
	//set_svar ("quest.formatroom", displayed_name (state.location));
	set_svar("quest.formatroom", formatroom);

	// regen_var_objects();
	/*
	String out_dest = exit_dest (state.location, "out");
	if (out_dest == "")
	  {
	    set_svar ("quest.doorways.out", "");
	    set_svar ("quest.doorways.out.display", "");
	  }
	else
	  {
	    cerr << "Updating quest.doorways.out; out_dest == {" << out_dest << "}";
	    uint i = out_dest.find (';');
	    cerr << ", i == " << i;
	    String prefix = "";
	    if (i != -1)
	{
	  prefix = trim (out_dest.substr (0, i-1));
	  out_dest = trim (out_dest.substr (i + 1));
	  cerr << "; prefix == {" << prefix << "}, out_dest == {" << out_dest << "}";
	}
	    cerr << "  quest.doorways.out == {" << out_dest << "}";
	    set_svar ("quest.doorways.out", out_dest);
	    cerr << endl;

	    String tmp = displayed_name (out_dest);

	    cerr << ", tmp == {" << tmp << "}";

	    if (tmp != "")
	tmp = "|b" + tmp + "|xb";
	    else if (prefix != "")
	tmp = prefix + " |b" + out_dest + "|xb";
	    else
	tmp = "|b" + out_dest + "|xb";

	    cerr << ",    final value {" << tmp << "}" << endl;

	    set_svar ("quest.doorways.out.display", tmp);
	  }
	*/
}


void geas_implementation::regen_var_look() {
	String look_tag;
	if (!get_obj_property(state.location, "look", look_tag))
		look_tag = "";
	set_svar("quest.lookdesc", look_tag);
}


void geas_implementation::regen_var_dirs() {
	Common::Array <String> dirs;
	// the -1 is so that it skips 'out'
	for (uint i = 0; i < ARRAYSIZE(dir_names) - 1; i ++)
		if (exit_dest(state.location, dir_names[i]) != "")
			dirs.push_back(dir_names[i]);
	String exits = "";
	if (dirs.size() == 1)
		exits = "|b" + dirs[0] + "|xb";
	else if (dirs.size() > 1) {
		for (uint i = 0; i < dirs.size(); i ++) {
			exits = exits + "|b" + dirs[i] + "|xb";
			if (i < dirs.size() - 2)
				exits = exits + ", ";
			else if (i == dirs.size() - 2)
				exits = exits + " or ";
		}
	}
	set_svar("quest.doorways.dirs", exits);

	/*
	String tmp;
	if ((tmp = exit_dest (state.location, "out")) != "")
	  set_svar ("quest.doorways.out", displayed_name (tmp));
	else
	  set_svar ("quest.doorways.out", "");
	*/

	String out_dest = exit_dest(state.location, "out");
	if (out_dest == "") {
		set_svar("quest.doorways.out", "");
		set_svar("quest.doorways.out.display", "");
	} else {
		cerr << "Updating quest.doorways.out; out_dest == {" << out_dest << "}";
		int i = out_dest.find(';');
		cerr << ", i == " << i;
		String prefix = "";
		if (i != -1) {
			prefix = trim(out_dest.substr(0, i - 1));
			out_dest = trim(out_dest.substr(i + 1));
			cerr << "; prefix == {" << prefix << "}, out_dest == {" << out_dest << "}";
		}
		cerr << "  quest.doorways.out == {" << out_dest << "}";
		set_svar("quest.doorways.out", out_dest);
		cerr << endl;

		String tmp = displayed_name(out_dest);

		cerr << ", tmp == {" << tmp << "}";

		if (tmp != "")
			tmp = "|b" + tmp + "|xb";
		else if (prefix != "")
			tmp = prefix + " |b" + out_dest + "|xb";
		else
			tmp = "|b" + out_dest + "|xb";

		cerr << ",    final value {" << tmp << "}" << endl;

		set_svar("quest.doorways.out.display", tmp);
	}

	/* TODO handle this */
	//set_svar ("quest.doorways.places", "");
	current_places = get_places(state.location);
	String printed_places = "";
	for (uint i = 0; i < current_places.size(); i ++) {
		if (i == 0)
			printed_places = current_places[i][0];
		else if (i < current_places.size() - 1)
			printed_places = printed_places + ", " + current_places[i][0];
		else if (current_places.size() == 2)
			printed_places = printed_places + " or " + current_places[i][0];
		else
			printed_places = printed_places + ", or " + current_places[i][0];
	}
	set_svar("quest.doorways.places", printed_places);
}



// TODO:  SENSITIVE???
String geas_implementation::substitute_synonyms(String s) const {
	String orig = s;
	cerr << "substitute_synonyms (" << s << ")\n";
	const GeasBlock *gb = gf.find_by_name("synonyms", "");
	if (gb != NULL) {
		/* TODO: exactly in what order does it try synonyms?
		 * Does it have to be flanked by whitespace?
		 */
		for (uint i = 0; i < gb->data.size(); i ++) {
			String line = gb->data[i];
			int index = line.find('=');
			if (index == -1)
				continue;
			Common::Array<String> words = split_param(line.substr(0, index));
			String rhs = trim(line.substr(index + 1));
			if (rhs == "")
				continue;
			for (uint j = 0; j < words.size(); j ++) {
				String lhs = words[j];
				if (lhs == "")
					continue;
				int k = 0;
				while ((k = s.find(lhs, k)) != -1) {
					uint end_index = k + lhs.length();
					if ((k == 0 || s[k - 1] == ' ') &&
					        (end_index == s.length() || s[end_index] == ' ')) {
						s = s.substr(0, k) + rhs + s.substr(k + lhs.length());
						k = k + rhs.length();
					} else
						k ++;
				}
			}
		}
	}
	cerr << "substitute_synonyms (" << orig << ") -> '" << s << "'\n";
	return s;
}

bool geas_implementation::is_running() const {
	return is_running_;
}

String geas_implementation::get_banner() {
	String banner;
	const GeasBlock *gb = gf.find_by_name("game", "game");
	if (gb) {
		String line = gb->data[0];
		uint c1, c2;
		String tok = first_token(line, c1, c2);
		tok = next_token(line, c1, c2);
		tok = next_token(line, c1, c2);
		if (is_param(tok)) {
			banner = eval_param(tok);

			for (uint i = 0; i < gb->data.size(); i ++) {
				line = gb->data[i];
				if (first_token(line, c1, c2) == "game" &&
				        next_token(line, c1, c2) == "version" &&
				        is_param(tok = next_token(line, c1, c2))) {
					banner += ", v";
					banner += eval_param(tok);
				}
			}

			for (uint i = 0; i < gb->data.size(); i ++) {
				line = gb->data[i];
				if (first_token(line, c1, c2) == "game" &&
				        next_token(line, c1, c2) == "author" &&
				        is_param(tok = next_token(line, c1, c2))) {
					banner += " | ";
					banner += eval_param(tok);
				}
			}
		}
	}
	return banner;
}

void geas_implementation::run_command(String s) {
	/* if s == "restore" or "restart" or "quit" or "undo" */

	if (!is_running_)
		return;

	print_newline();
	print_normal("> " + s);
	print_newline();

	if (s == "dump status") {
		//cerr << state << endl;
		ostringstream oss;
		oss << state;
		print_normal(oss.str());
		return;
	
	} else if (s == "undo") {
		if (undo_buffer.size() < 2) {
			print_formatted("(No more undo information available!)");
			return;
		}
		undo_buffer.pop();
		state = undo_buffer.peek();
		print_formatted("Undone.");
		return;
	
	} else if (s == "save") {
		if (g_vm->saveGame().getCode() == Common::kNoError)
			print_formatted("Saved.");
		return;
	
	} else if (s == "restore") {
		if (g_vm->loadGame().getCode() == Common::kNoError)
			run_command("look");
		return;
	}

	if (!state.running)
		return;
	// TODO: does this get the original command, or the lowercased version?
	set_svar("quest.originalcommand", s);
	s = substitute_synonyms(lcase(s));
	set_svar("quest.command", s);

	bool overridden = false;
	dont_process = false;

	const GeasBlock *gb = gf.find_by_name("room", state.location);
	if (gb != NULL) {
		String line, tok;
		uint c1, c2;
		for (uint i = 0; i < gb->data.size(); i ++) {
			line = gb->data[i];
			tok = first_token(line, c1, c2);
			// SENSITIVE?
			if (tok == "beforeturn") {
				uint scr_starts = c2;
				tok = next_token(line, c1, c2);
				// SENSITIVE?
				if (tok == "override") {
					overridden = true;
					scr_starts = c2;
				}
				String scr = line.substr(scr_starts);
				run_script(state.location, scr);
				//run_script (scr);
			}
		}
	} else
		gi->debug_print("Unable to find block " + state.location + ".\n");

	if (!overridden) {
		gb = gf.find_by_name("game", "game");
		if (gb != NULL) {
			String line, tok;
			uint c1, c2;
			for (uint i = 0; i < gb->data.size(); i ++) {
				line = gb->data[i];
				tok = first_token(line, c1, c2);
				// SENSITIVE?
				if (tok == "beforeturn") {
					uint scr_starts = c2;
					tok = next_token(line, c1, c2);
					// SENSITIVE?
					if (tok == "override") {
						overridden = true;
						scr_starts = c2;
					}
					String scr = line.substr(scr_starts);
					run_script_as("game", scr);
					//run_script (scr);
				}
			}
		} else
			gi->debug_print("Unable to find block game.\n");
	}

	if (!dont_process) {
		if (try_match(s, false, false)) {
			/* TODO TODO */
			// run after turn events ???
		} else
			display_error("badcommand");
	}

	overridden = false;

	gb = gf.find_by_name("room", state.location);
	if (gb != NULL) {
		String line, tok;
		uint c1, c2;
		for (uint i = 0; i < gb->data.size(); i ++) {
			line = gb->data[i];
			tok = first_token(line, c1, c2);
			// SENSITIVE?
			if (tok == "afterturn") {
				uint scr_starts = c2;
				tok = next_token(line, c1, c2);
				// SENSITIVE?
				if (tok == "override") {
					overridden = true;
					scr_starts = c2;
				}
				String scr = line.substr(scr_starts);
				run_script_as(state.location, scr);
				//run_script (scr);
			}
		}
	}
	if (!overridden) {
		gb = gf.find_by_name("game", "game");
		if (gb != NULL) {
			String line, tok;
			uint c1, c2;
			for (uint i = 0; i < gb->data.size(); i ++) {
				line = gb->data[i];
				tok = first_token(line, c1, c2);
				// SENSITIVE?
				if (tok == "afterturn") {
					uint scr_starts = c2;
					tok = next_token(line, c1, c2);
					// SENSITIVE?
					if (tok == "override") {
						overridden = true;
						scr_starts = c2;
					}
					String scr = line.substr(scr_starts);
					run_script_as("game", scr);
					//run_script (scr);
				}
			}
		}
	}

	if (state.running)
		undo_buffer.push(state);
}

Common::WriteStream &operator<< (Common::WriteStream &o, const match_rv &rv) {
	//o << "match_rv {" << (rv.success ? "TRUE" : "FALSE") << ": " << rv.bindings << "}";
	o << "match_rv {" << (rv.success ? "TRUE" : "FALSE") << ": [";
	//o << rv.bindings.size();
	o << rv.bindings;
	//for (uint i = 0; i < rv.bindings.size(); i ++)
	//  o << rv.bindings[i] << ", ";
	o << "]}";
	return o;
}

match_rv geas_implementation::match_command(String input, String action) const {
	//cerr << "match_command (\"" << input << "\", \"" << action << "\")" << endl;
	match_rv rv = match_command(input, 0, action, 0, match_rv());
	cerr << "match_command (\"" << input << "\", \"" << action << "\") -> " << rv << endl;
	return rv;
	//return match_command (input, 0, action, 0, match_rv ());
}

match_rv geas_implementation::match_command(String input, uint ichar, String action, uint achar, match_rv rv) const {
	//cerr << "match_command (\"" << input << "\", " << ichar << ", \"" << action << "\", " << achar << ", " << rv << ")" << endl;
	for (;;) {
		if (achar == action.length()) {
			//cerr << "End of action, returning " << (ichar == input.length()) << "\n";
			return match_rv(ichar == input.length(), rv);
		}
		if (action[achar] == '#') {

			achar ++;
			String varname;
			while (achar != action.length() && action[achar] != '#') {
				varname += action[achar];
				achar ++;
			}
			if (achar == action.length())
				error("Unpaired hashes in command String %s", action.c_str());
			//rv.bindings.push_back (varname);
			int index = rv.bindings.size();
			rv.bindings.push_back(match_binding(varname, ichar));
			achar ++;
			varname = "";
			//rv.bindings.push_back (varname);
			rv.bindings[index].set(varname, ichar);
			while (ichar < input.length()) {
				match_rv tmp = match_command(input, ichar, action, achar, rv);
				if (tmp.success)
					return tmp;
				varname += input[ichar];
				ichar ++;
				//rv.bindings[index] = varname;
				rv.bindings[index].set(varname, ichar);
			}
			return match_rv(achar == action.length(), rv);
		}
		// SENSITIVE?
		if (ichar == input.length() || !c_equal_i(input[ichar], action[achar]))
			return match_rv();
		//cerr << "Matched " << input[ichar] << " to " << action[achar] << endl;
		++ achar;
		++ ichar;
	}
}

bool match_object_alts(String text, const Common::Array<String> &alts, bool is_internal) {
	for (uint i = 0; i < alts.size(); i ++) {
		cerr << "m_o_a: Checking '" << text << "' v. alt '" << alts[i] << "'.\n";
		if (starts_with(text, alts[i])) {
			uint len = alts[i].length();
			if (text.length() == len)
				return true;
			if (text.length() > len  &&  text[len] == ' '  &&
			        match_object_alts(text.substr(len + 1), alts, is_internal))
				return true;
		}
	}
	return false;
}


bool geas_implementation::match_object(String text, String name, bool is_internal) const {
	cerr << "* * * match_object (" << text << ", " << name << ", "
	     << (is_internal ? "true" : "false") << ")\n";

	String alias, alt_list, prefix, suffix;

	if (is_internal && ci_equal(text, name)) return true;

	if (get_obj_property(name, "prefix", prefix) &&
	        starts_with(text, prefix + " ") &&
	        match_object(text.substr(prefix.length() + 1), name, false))
		return true;

	if (get_obj_property(name, "suffix", suffix) &&
	        ends_with(text, " " + suffix) &&
	        match_object(text.substr(0, text.length() - suffix.length() - 1), name, false))
		return true;

	if (!get_obj_property(name, "alias", alias))
		alias = name;
	if (ci_equal(text, alias))
		return true;

	const GeasBlock *gb = gf.find_by_name("object", name);
	if (gb != NULL) {
		String tok, line;
		uint c1, c2;
		for (uint ln = 0; ln < gb->data.size(); ln ++) {
			line = gb->data[ln];
			tok = first_token(line, c1, c2);
			// SENSITIVE?
			if (tok == "alt") {
				tok = next_token(line, c1, c2);
				if (!is_param(tok))
					gi->debug_print("Expected param after alt in " + line);
				else {
					Common::Array<String> alts = split_param(param_contents(tok));
					cerr << "  m_o: alt == " << alts << "\n";
					return match_object_alts(text, alts, is_internal);
				}
			}
		}
	}

	return false;
}


bool geas_implementation::dereference_vars(Common::Array<match_binding> &bindings, bool is_internal) const {
	/* TODO */
	Common::Array<String> where;
	where.push_back("inventory");
	where.push_back(state.location);
	return dereference_vars(bindings, where, is_internal);
}

bool geas_implementation::dereference_vars(Common::Array<match_binding> &bindings, const Common::Array<String> &where, bool is_internal) const {
	bool rv = true;
	for (uint i = 0; i < bindings.size(); i ++)
		if (bindings[i].var_name[0] == '@') {
			String obj_name = get_obj_name(bindings[i].var_text, where, is_internal);
			if (obj_name == "!") {
				print_formatted("You don't see any " + bindings[i].var_text + ".");
				rv = false;
			} else {
				bindings[i].var_text = obj_name;
				bindings[i].var_name = bindings[i].var_name.substr(1);
			}
		}
	return rv;
}

String geas_implementation::get_obj_name(String name, const Common::Array<String> &where, bool is_internal) const {
	Common::Array<String> objs, printed_objs;
	for (uint objnum = 0; objnum < state.objs.size(); objnum ++) {
		bool is_used = false;
		for (uint j = 0; j < where.size(); j ++) {
			cerr << "Object #" << objnum << ": " << state.objs[objnum].name
			     << "@" << state.objs[objnum].parent << " vs. "
			     << where[j] << endl;
			// SENSITIVE?
			if (where[j] == "game" || state.objs[objnum].parent == where[j])
				is_used = true;
		}
		if (is_used && !has_obj_property(state.objs[objnum].name, "hidden") &&
		        match_object(name, state.objs[objnum].name, is_internal)) {
			String printed_name, tmp, oname = state.objs[objnum].name;
			objs.push_back(oname);
			if (!get_obj_property(oname, "alias", printed_name))
				printed_name = oname;
			if (get_obj_property(oname, "detail", tmp))
				printed_name = tmp;
			printed_objs.push_back(printed_name);
		}
	}
	cerr << "objs == " << objs << ", printed_objs == " << printed_objs << "\n";
	if (objs.size() > 1) {
		//bindings[i].var_name = bindings[i].var_name.substr(1);
		uint num = 0;
		//if (objs.size() > 1)
		num = gi->make_choice("Which " + name + " do you mean?", printed_objs);

		//bindings[i].var_text = objs[num];
		return objs[num];
	}
	if (objs.size() == 1)
		return objs[0];
	return "!";
}


void geas_implementation::set_vars(const Common::Array<match_binding> &v) {
	for (uint i = 0; i < v.size(); i ++)
		set_svar(v[i].var_name, v[i].var_text);
}


bool geas_implementation::run_commands(String cmd, const GeasBlock *room, bool is_internal) {
	uint c1, c2;
	String line, tok;
	match_rv match;

	if (room != NULL) {
		for (uint i = 0; i < room->data.size(); i++) {
			line = room->data[i];
			tok = first_token(line, c1, c2);
			// SENSITIVE?
			if (tok == "command") {
				tok = next_token(line, c1, c2);
				if (is_param(tok)) {
					Common::Array<String> tmp = split_param(param_contents(tok));

					for (uint j = 0; j < tmp.size(); j++)
						if (match = match_command(cmd, tmp[j])) {
							if (!dereference_vars(match.bindings, is_internal))
								return false;
							set_vars(match.bindings);
							run_script_as(state.location, line.substr(c2 + 1));
							//run_script (line.substr (c2+1));
							return true;
						}
					/*
					  if (match = match_command (cmd, param_contents(tok)))
					  {
					  if (!dereference_vars (match.bindings))
					  return false;
					  set_vars (match.bindings);
					  run_script (line.substr (c2+1));
					  return true;
					  }
					*/
				} else {
					gi->debug_print("Bad command line: " + line);
				}
			}
		}
	} else
		gi->debug_print("room is null\n");

	return false;
}

bool geas_implementation::try_match(String cmd, bool is_internal, bool is_normal) {
	//print_formatted ("geas_impl registers " + cmd);

	String line, tok;
	match_rv match;

	if (!is_normal) {
		if (run_commands(cmd, gf.find_by_name("room", state.location)) ||
		        run_commands(cmd, gf.find_by_name("game", "game")))
			return true;
	}

	if ((match = match_command(cmd, "look at #@object#")) ||
	        (match = match_command(cmd, "look #@object#"))) {
		if (!dereference_vars(match.bindings, is_internal))
			return true;

		String object = match.bindings[0].var_text;

		if (get_obj_action(object, "look", tok))
			run_script_as(object, tok);
		//run_script (tok);
		else if (get_obj_property(object, "look", tok))
			print_formatted(tok);
		else
			display_error("defaultlook", object);

		return true;
	}

	if ((match = match_command(cmd, "examine #@object#")) ||
	        (match = match_command(cmd, "x #@object#"))) {
		if (!dereference_vars(match.bindings, is_internal))
			return true;

		String object = match.bindings[0].var_text;
		if (get_obj_action(object, "examine", tok))
			run_script_as(object, tok);
		//run_script (tok);
		else if (get_obj_property(object, "examine", tok))
			print_formatted(tok);
		else if (get_obj_action(object, "look", tok))
			run_script_as(object, tok);
		//run_script (tok);
		else if (get_obj_property(object, "look", tok))
			print_formatted(tok);
		else
			display_error("defaultexamine", object);
		return true;
	}

	if (match = match_command(cmd, "look")) {
		look();
		return true;
	}

	if (match = match_command(cmd, "give #@first# to #@second#")) {
		if (!dereference_vars(match.bindings, is_internal))
			return true;
		String script, first = match.bindings[0].var_text, second = match.bindings[1].var_text;
		if (! ci_equal(get_obj_parent(first), "inventory"))
			display_error("noitem", first);
		else if (get_obj_action(second, "give " + first, script))
			run_script(second, script);
		//run_script (script);
		else if (get_obj_action(first, "give to " + second, script))
			run_script_as(first, script);
		//run_script (script);
		else if (get_obj_action(second, "give anything", script)) {
			set_svar("quest.give.object.name", first);
			run_script_as(second, script);
			//run_script (script);
		} else if (get_obj_action(first, "give to anything", script)) {
			set_svar("quest.give.object.name", second);
			run_script_as(first, script);
			//run_script (script);
		} else {
			String tmp;
			if (!get_obj_property(second, "gender", tmp))
				tmp = "it";
			set_svar("quest.error.gender", tmp);
			if (!get_obj_property(first, "article", tmp))
				tmp = "it";
			set_svar("quest.error.article", tmp);
			display_error("itemunwanted");
		}
		return true;
	}

	if ((match = match_command(cmd, "use #@first# on #@second#")) ||
	        (match = match_command(cmd, "use #@first# with #@second#"))) {
		if (!dereference_vars(match.bindings, is_internal))
			return true;
		String script, first = match.bindings[0].var_text, second = match.bindings[1].var_text;
		if (! ci_equal(get_obj_parent(first), "inventory"))
			display_error("noitem", first);
		else if (get_obj_action(second, "use " + first, script)) {
			//set_svar ("quest.use.object", first);
			run_script_as(second, script);
			//run_script (script);
		} else if (get_obj_action(first, "use on " + second, script)) {
			//set_svar ("quest.use.object", second);
			run_script_as(first, script);
			//run_script (script);
		} else if (get_obj_action(second, "use anything", script)) {
			set_svar("quest.use.object", first);
			run_script(second, script);
			//run_script (script);
		} else if (get_obj_action(first, "use on anything", script)) {
			set_svar("quest.use.object", second);
			run_script_as(first, script);
			//run_script (script);
		} else
			display_error("defaultuse");

		return true;
	}

	if (match = match_command(cmd, "use #@first#")) {
		if (!dereference_vars(match.bindings, is_internal))
			return true;
		String tmp, obj = match.bindings[0].var_text;
		if (!ci_equal(get_obj_parent(obj), "inventory"))
			display_error("noitem", obj);
		else if (get_obj_action(obj, "use", tmp))
			run_script_as(obj, tmp);
		//run_script (tmp);
		else if (get_obj_property(obj, "use", tmp))
			print_formatted(tmp);
		else
			display_error("defaultuse", obj);
		return true;
	}


	if ((match = match_command(cmd, "take #@object#")) ||
	        (match = match_command(cmd, "get #@object#"))) {
		if (!dereference_vars(match.bindings, is_internal))
			return true;

		String object = match.bindings[0].var_text;
		if (get_obj_action(object, "take", tok)) {
			cerr << "Running script '" << tok << "' for take " << object << endl;
			run_script_as(object, tok);
			//run_script (tok);
		} else if (get_obj_property(object, "take", tok)) {
			cerr << "Found property '" << tok << "' for take " << object << endl;
			if (tok != "")
				print_formatted(tok);
			else
				display_error("defaulttake", object);
			String tmp;
			move(object, "inventory");
			if (get_obj_action(object, "gain", tmp))
				run_script(object, tmp);
			//run_script (tmp);
			else if (get_obj_property(object, "gain", tmp))
				print_formatted(tmp);
		} else {
			cerr << "No match found for take " << object << endl;
			// TODO set variable with object name
			display_error("badtake", object);
		}
		return true;
	}


	if (match = match_command(cmd, "drop #@object#")) {
		if (!dereference_vars(match.bindings, is_internal))
			return true;
		String scr, obj = match.bindings[0].var_text;
		if (get_obj_action(obj, "drop", scr)) {
			run_script_as(obj, scr);
			//run_script (scr);
			return true;
		}

		const GeasBlock *gb = gf.find_by_name("object", obj);
		if (gb != NULL) {
			uint c1, c2, script_begins;
			for (uint i = 0; i < gb->data.size(); i ++) {
				line = gb->data[i];
				tok = first_token(line, c1, c2);
				// SENSITIVE?
				if (tok == "drop") {
					script_begins = c2;
					tok = next_token(line, c1, c2);
					// SENSITIVE?
					if (tok == "everywhere") {
						tok = next_token(line, c1, c2);
						move(obj, state.location);
						if (is_param(tok))
							print_eval(tok);
						else
							gi->debug_print("Expected param after drop everywhere in " + line);
						return true;
					}
					// SENSITIVE?
					if (tok == "nowhere") {
						if (is_param(tok))
							print_eval(tok);
						else
							gi->debug_print("Expected param after drop nowhere in " + line);
						return true;
					}
					run_script_as(obj, line.substr(script_begins));
					//run_script (line.substr (script_begins));
					return true;
				}
			}
		}
		move(obj, state.location);
		display_error("defaultdrop", obj);
		return true;
	}

	if ((match = match_command(cmd, "speak to #@object#")) ||
	        (match = match_command(cmd, "speak #@object#")) ||
	        (match = match_command(cmd, "talk to #@object#")) ||
	        (match = match_command(cmd, "talk #@object#"))) {
		//print_formatted ("Talk to <" + String (match.bindings[0]) + ">");
		if (!dereference_vars(match.bindings, is_internal))
			return true;
		String obj = match.bindings[0].var_text;
		String script;
		if (get_obj_action(obj, "speak", script))
			run_script_as(obj, script);
		//run_script (script);
		else
			display_error("defaultspeak", obj);
		//print_formatted ("Talk to <" + String (match.bindings[0]) + ">");
		return true;
	}

	if (cmd == "exit" || cmd == "out" || cmd == "go out") {
		const GeasBlock *gb = gf.find_by_name("room", state.location);
		if (gb == NULL) {
			gi->debug_print("Bad room");
			return true;
		}

		line = "";
		int c1 = -1, c2 = -1;
		uint uc1, uc2;
		// TODO: Use the first matching line or the last?
		for (uint i = 0; i < gb->data.size(); i ++) {
			if (first_token(gb->data[i], uc1, uc2) == "out")
				line = gb->data[i];
			c1 = uc1;
			c2 = uc2;
		}

		//gi->debug_print ("COMMAND " + cmd + ": line == " + line);

		if (line == "")
			display_error("defaultout");
		else {
			c1 = line.find('<');
			if (c1 != -1)
				c2 = line.find('>', c1);

			if (c1 == -1 || c2 == -1)
				gi->debug_print("Bad out line: " + line);
			else {
				String tmp = trim(line.substr(c2 + 1));
				//gi->debug_print ("tmp1 == {" + tmp + "}");
				if (tmp != "")
					run_script_as(state.location, tmp);
				//run_script (tmp);
				else {
					tmp = line.substr(c1, c2 - c1 + 1);
					//gi->debug_print ("tmp2 == {" + tmp + "}");
					assert(is_param(tmp));
					tmp = param_contents(tmp);
					c1 = tmp.find(';');
					if (c1 == -1)
						goto_room(trim(tmp));
					else
						goto_room(trim(tmp.substr(c1 + 1)));
				}
			}
		}
		return true;
	}

	for (uint i = 0; i < ARRAYSIZE(dir_names); i ++)
		if (cmd == dir_names[i] || cmd == (String("go ") + dir_names[i]) ||
		        cmd == short_dir_names[i] || cmd == (String("go ") + short_dir_names[i])) {
			bool is_script = false;
			//print_formatted ("Trying to go " + dir_names[i]);
			if ((tok = exit_dest(state.location, dir_names[i], &is_script)) == "") {
				// TODO Which display_error do I use?
				print_formatted("You can't go that way.");
				return true;
			}
			if (is_script)
				run_script_as(state.location, tok);
			//run_script (tok);
			else {
				int index = tok.find(';');
				if (index == -1)
					goto_room(trim(tok));
				else
					goto_room(trim(tok.substr(index + 1)));
			}
			return true;
		}

	if ((match = match_command(cmd, "go to #@room#")) ||
	        (match = match_command(cmd, "go #@room#"))) {
		assert(match.bindings.size() == 1);
		String destination = match.bindings[0].var_text;
		for (uint i = 0; i < current_places.size(); i ++) {
			if (ci_equal(destination, current_places[i][1]) ||
			        ci_equal(destination, current_places[i][2])) {
				if (current_places[i].size() == 5)
					run_script_as(state.location, current_places[i][4]);
				//run_script (current_places[i][4]);
				else
					goto_room(current_places[i][3]);
				return true;
			}
		}
		display_error("badplace", destination);
		return true;
	}

	if (ci_equal(cmd, "inventory") || ci_equal(cmd, "i")) {
		Common::Array<Common::Array<String> > inv = get_inventory();
		if (inv.size() == 0)
			print_formatted("You are carrying nothing.");
		else
			print_formatted("You are carrying:");
		for (uint i = 0; i < inv.size(); i ++) {
			print_normal(inv[i][0]);
			print_newline();
		}
		return true;
	}

	if (ci_equal(cmd, "help")) {
		print_formatted("|b|cl|s14Quest Quick Help|xb|cb|s00|n|n|cl|bMoving|xb|cb Type |bGO NORTH|xb, |bSOUTH|xb, |bE|xb, etc. |xnTo go into a place, type |bGO TO ...|xb . To leave a place, type |bOUT, EXIT|xb or |bLEAVE|xb, or press the '|crOUT|cb' button.|n|cl|bObjects and Characters|xb|cb Use |bTAKE ...|xb, |bGIVE ... TO ...|xb, |bTALK|xb/|bSPEAK TO ...|xb, |bUSE ... ON|xb/|bWITH ...|xb, |bLOOK AT ...|xb, etc.|n|cl|bExit Quest|xb|cb Type |bQUIT|xb to leave Quest.|n|cl|bMisc|xb|cb Type |bABOUT|xb to get information on the current game.");
		return true;
	}

	if (ci_equal(cmd, "about")) {
		const GeasBlock *gb = gf.find_by_name("game", "game");
		if (gb == NULL)
			return true;
		cerr << *gb << endl;

		uint c1, c2;
		//print_formatted ("Game name: ");
		line = gb->data[0];
		tok = first_token(line, c1, c2);  // game
		tok = next_token(line, c1, c2);  // name
		tok = next_token(line, c1, c2);  // <whatever>
		if (is_param(tok))
			print_formatted("Game name: " + eval_param(tok));


		for (uint i = 0; i < gb->data.size(); i ++) {
			line = gb->data[i];
			// SENSITIVE?
			if (first_token(line, c1, c2) == "game" &&
			        next_token(line, c1, c2) == "version" &&
			        is_param(tok = next_token(line, c1, c2)))
				print_formatted("Version " + eval_param(tok));
		}

		for (uint i = 0; i < gb->data.size(); i ++) {
			line = gb->data[i];
			// SENSITIVE?
			if (first_token(line, c1, c2) == "game" &&
			        next_token(line, c1, c2) == "author" &&
			        is_param(tok = next_token(line, c1, c2)))
				print_formatted("Author: " + eval_param(tok));
		}

		for (uint i = 0; i < gb->data.size(); i ++) {
			line = gb->data[i];
			// SENSITIVE?
			if (first_token(line, c1, c2) == "game" &&
			        next_token(line, c1, c2) == "copyright" &&
			        is_param(tok = next_token(line, c1, c2)))
				print_formatted("Copyright: " + eval_param(tok));
		}

		for (uint i = 0; i < gb->data.size(); i ++) {
			line = gb->data[i];
			// SENSITIVE?
			if (first_token(line, c1, c2) == "game" &&
			        next_token(line, c1, c2) == "info" &&
			        is_param(tok = next_token(line, c1, c2)))
				print_formatted(eval_param(tok));
		}

		return true;
	}

	if (ci_equal(cmd, "quit")) {
		is_running_ = false;
		return true;
	}

	return false;
}

void geas_implementation::run_script_as(String obj, String scr) {
	String backup_object, garbage;
	backup_object = this_object;
	this_object = obj;
	run_script(scr, garbage);
	this_object = backup_object;
}

void geas_implementation::run_script(String s) {
	String garbage;
	run_script(s, garbage);
}

void geas_implementation::run_script(String s, String &rv) {
	//print_formatted ("     Running script " + s + ".");
	cerr << "Script line '" << s << "'\n";
	String tok;
	uint c1, c2;

	tok = first_token(s, c1, c2);

	if (tok == "") return;

	if (tok[0] == '{') {
		uint brace1 = c1 + 1, brace2;
		for (brace2 = s.length() - 1; brace2 >= brace1 && s[brace2] != '}'; brace2 --)
			;
		if (brace2 >= brace1)
			run_script(s.substr(brace1, brace2 - brace1));
		else
			gi->debug_print("Unterminated brace block in " + s);
		return;
	}

	// SENSITIVE?
	if (tok == "action") {
		tok = next_token(s, c1, c2);
		if (!is_param(tok)) {
			gi->debug_print("Expected parameter after action in " + s);
			return;
		}
		tok = eval_param(tok);
		int index = tok.find(';');
		if (index == -1) {
			gi->debug_print("Error: no semicolon in " + s);
			return;
		}
		set_obj_action(trim(tok.substr(0, index)),
		               "<" + trim(tok.substr(index + 1)) + "> " + s.substr(c2 + 1));
		return;
	}
	// SENSITIVE?
	else if (tok == "animate") {
	}
	// SENSITIVE?
	else if (tok == "background") {
		tok = next_token(s, c1, c2);
		if (is_param(tok))
			gi->set_background(eval_param(tok));
		else
			gi->debug_print("Expected parameter after foreground in " + s);
		return;
	}
	// SENSITIVE?
	else if (tok == "choose") {
		tok = next_token(s, c1, c2);
		if (!is_param(tok)) {
			gi->debug_print("Expected parameter after choose in " + s);
			return;
		}
		tok = eval_param(tok);
		const GeasBlock *gb = gf.find_by_name("selection", tok);
		if (gb == NULL) {
			gi->debug_print("No selection called " + tok + " found");
			return;
		}
		String question, line;
		Common::Array<String> choices, actions;
		for (uint ln = 0; ln < gb->data.size(); ln ++) {
			line = gb->data[ln];
			tok = first_token(line, c1, c2);
			// SENSITIVE?
			if (tok == "info") {
				tok = next_token(line, c1, c2);
				if (is_param(tok))
					question = eval_param(tok);
				else
					gi->debug_print("Expected parameter after info in " + line);
			}
			// SENSITIVE?
			else if (tok == "choice") {
				tok = next_token(line, c1, c2);
				if (is_param(tok)) {
					choices.push_back(eval_param(tok));
					actions.push_back(line.substr(c2));
				} else
					gi->debug_print("Expected parameter after choice in " + line);
			} else
				gi->debug_print("Bad line " + line + " in selection");
		}
		if (choices.size() == 0)
			//gi->debug_print ("No choices in selection " + gb->lname);
			gi->debug_print("No choices in selection " + gb->name);
		else
			run_script(actions[gi->make_choice(question, choices)]);
		return;
	}
	// SENSITIVE?
	else if (tok == "clear") {
		gi->clear_screen();
		return;
	}
	// SENSITIVE?
	else if (tok == "clone") {
		/* TODO */
	}
	// SENSITIVE?
	else if (tok == "create") {
		tok = next_token(s, c1, c2);
		// SENSITIVE?
		if (tok == "exit") { // create exit
			String dir = "";

			tok = next_token(s, c1, c2);
			if (!is_param(tok)) {
				dir = tok;
				tok = next_token(s, c1, c2);
			}

			if (!is_param(tok)) {
				gi->debug_print("Expected param after create exit in " + s);
				return;
			}
			tok = eval_param(tok);
			Common::Array<String> args = split_param(tok);
			if (args.size() != 2) {
				gi->debug_print("Expected 2 elements in param in " + s);
				return;
			}
			if (dir != "")
				state.exits.push_back(ExitRecord(args[0],
				                                 "exit " + dir + " <" + tok + ">"));
			else
				state.exits.push_back(ExitRecord(args[0], "exit <" + tok + ">"));
			//gi->debug_print ("Not yet able to create place type exits");
			regen_var_dirs();
			return;
		}
		// SENSITIVE?
		else if (tok == "object") { // create object
			/* TODO */
		}
		// SENSITIVE?
		else if (tok == "room") { // create room
			/* TODO */
		} else
			gi->debug_print("Bad create line " + s);
		return;
	}
	// SENSITIVE?
	else if (tok == "debug") {
		tok = next_token(s, c1, c2);
		if (is_param(tok))
			gi->debug_print(eval_param(tok));
		else
			gi->debug_print("Expected param after debug in " + s);
		return;
	}
	// SENSITIVE?
	else if (tok == "destroy") {
		tok = next_token(s, c1, c2);
		if (tok != "exit") {
			gi->debug_print("expected 'exit' after 'destroy' in " + s);
			return;
		}
		tok = next_token(s, c1, c2);
		if (!is_param(tok)) {
			gi->debug_print("Expected param after 'destroy exit' in " + s);
			return;
		}
		tok = eval_param(tok);
		Common::Array<String> args = split_param(tok);
		if (args.size() != 2) {
			gi->debug_print("Expected two arguments in " + s);
			return;
		}
		//state.exits.push_back (ExitRecord (args[0], "destroy exit <" + tok + ">"));
		state.exits.push_back(ExitRecord(args[0], "destroy exit " + args[1]));
		regen_var_dirs();
		return;
	}
	// SENSITIVE?
	else if (tok == "disconnect") {
		/* QNSO */
	}
	// SENSITIVE?
	else if (tok == "displaytext") {
		tok = next_token(s, c1, c2);
		if (!is_param(tok)) {
			gi->debug_print("Expected parameter after displaytext in " + s);
			return;
		}
		const GeasBlock *gb = gf.find_by_name("text", param_contents(tok));
		if (gb != NULL) {
			for (uint i = 0; i < gb->data.size(); i ++) {
				print_formatted(gb->data[i]);
				print_newline();
			}
		} else
			gi->debug_print("No such text block " + tok);
		return;
	}
	// SENSITIVE?
	else if (tok == "do") {
		tok = next_token(s, c1, c2);
		if (!is_param(tok)) {
			gi->debug_print("Expected parameter after do in " + s);
			return;
		}
		String fname = eval_param(tok);
		int index = fname.find('(');
		if (index != -1) {
			int index2 = fname.find(')');
			run_procedure(trim(fname.substr(0, index)),
			              split_f_args(fname.substr(index + 1, index2 - index - 1)));
		} else
			run_procedure(fname);

		//run_procedure (fname);
		return;
	}
	// SENSITIVE?
	else if (tok == "doaction") {
		tok = next_token(s, c1, c2);
		if (!is_param(tok)) {
			gi->debug_print("Expected parameter after doaction in " + s);
			return;
		}
		String line = eval_param(tok);
		int index = line.find(';');
		String obj = trim(line.substr(0, index));
		String act = trim(line.substr(index + 1));
		String old_object = this_object;
		this_object = obj;
		if (get_obj_action(obj, act, tok))
			run_script_as(obj, tok);
		//run_script (tok);
		else
			gi->debug_print("No action defined for " + obj + " // " + act);
		this_object = old_object;
		return;
	}
	// SENSITIVE?
	else if (tok == "dontprocess") {
		dont_process = true;
		return;
	}
	// SENSITIVE?
	else if (tok == "enter") {
		tok = next_token(s, c1, c2);
		if (!is_param(tok)) {
			gi->debug_print("Expected parameter after enter in " + s);
			return;
		}
		tok = eval_param(tok);
		set_svar(tok, gi->get_string());
		return;
	}
	// SENSITIVE?
	else if (tok == "exec") {
		tok = next_token(s, c1, c2);
		if (!is_param(tok)) {
			gi->debug_print("Expected parameter after exec in " + s);
			return;
		}
		tok = eval_param(tok);
		int index = tok.find(';');
		if (index != -1) {
			String tmp = trim(tok.substr(index + 1));
			// SENSITIVE?
			if (tmp == "normal") {
				//run_command (trim (tok.substr (0, index)), true, true);
				try_match(trim(tok.substr(0, index)), true, true);
			} else {
				gi->debug_print("Bad " + tmp + " in exec in " + s);
				//run_command (trim (tok.substr (0, index)), true, false);
				try_match(trim(tok.substr(0, index)), true, false);
			}
		} else {
			//run_command (trim (tok.substr (0, index)), true, false);
			try_match(trim(tok.substr(0, index)), true, false);
		}
		return;
	}
	// SENSITIVE?
	else if (tok == "flag") {
		tok = next_token(s, c1, c2);
		bool is_on;
		// SENSITIVE?
		if (tok == "on")
			is_on = true;
		// SENSITIVE?
		else if (tok == "off")
			is_on = false;
		else {
			gi->debug_print("Expected 'on' or 'off' after flag in " + s);
			return;
		}
		String onoff = tok;

		tok = next_token(s, c1, c2);
		if (is_param(tok))
			set_obj_property("game", (is_on ? "" : "not ") + eval_param(tok));
		else
			gi->debug_print("Expected param after flag " + onoff + " in " + s);
		return;
	}
	// SENSITIVE?
	else if (tok == "font") {
		/* TODO */
	}
	// SENSITIVE?
	else if (tok == "for") {
		tok = next_token(s, c1, c2);
		// SENSITIVE?
		if (tok == "each") {
			// SENSITIVE?
			if (next_token(s, c1, c2) == "object" &&
			        next_token(s, c1, c2) == "in") {
				tok = next_token(s, c1, c2);
				// SENSITIVE?
				if (tok == "game") {
					/* TODO: This will run over the game, rooms, and objects */
					/* It should just do the objects. */
					String script = s.substr(c2);
					// Start at 1 to skip game
					for (uint i = 1; i < state.objs.size(); i ++) {
						cerr << "  quest.thing -> " + state.objs[i].name + "\n";
						set_svar("quest.thing", state.objs[i].name);
						run_script(script);
					}
					return;
				} else if (is_param(tok)) {
					tok = trim(eval_param(tok));
					String script = s.substr(c2);
					for (uint i = 0; i < state.objs.size(); i ++)
						if (state.objs[i].parent == tok) {
							set_svar("quest.thing", state.objs[i].name);
							run_script(script);
						}
					return;
				}
			}
		} else if (is_param(tok)) {
			Common::Array<String> args = split_param(eval_param(tok));
			String varname = args[0];
			String script = s.substr(c2);
			int startindex = parse_int(args[1]);
			int endindex = parse_int(args[2]);
			int step = 1;
			if (args.size() > 3)
				step = parse_int(args[3]);
			for (set_ivar(varname, startindex); get_ivar(varname) < endindex;
			        set_ivar(varname, get_ivar(varname) + step))
				run_script(script);
			return;
		}

	}
	// SENSITIVE?
	else if (tok == "foreground") {
		tok = next_token(s, c1, c2);
		if (is_param(tok))
			gi->set_foreground(eval_param(tok));
		else
			gi->debug_print("Expected parameter after foreground in " + s);
		return;
	}
	// SENSITIVE?
	else if (tok == "give") {
		tok = next_token(s, c1, c2);
		if (!is_param(tok)) {
			gi->debug_print("Expected parameter after give in " + s);
			return;
		}
		tok = eval_param(tok);
		move(tok, "inventory");
		String tmp;
		if (get_obj_action(tok, "gain", tmp))
			run_script_as(tok, tmp);
		//run_script (tmp);
		else if (get_obj_property(tok, "gain", tmp))
			print_formatted(tmp);
		return;
	}
	// SENSITIVE?
	else if (tok == "goto") {
		tok = next_token(s, c1, c2);
		if (is_param(tok))
			goto_room(trim(eval_param(tok)));
		else
			gi->debug_print("Expected parameter after goto in " + s);
		return;
	}
	// SENSITIVE?
	else if (tok == "helpclear") {
	}
	// SENSITIVE?
	else if (tok == "helpclose") {
	}
	// SENSITIVE?
	else if (tok == "helpdisplaytext") {
	}
	// SENSITIVE?
	else if (tok == "helpmsg") {
	}
	// SENSITIVE?
	else if (tok == "hide") {
		tok = next_token(s, c1, c2);
		if (is_param(tok))
			set_obj_property(eval_param(tok), "hidden");
		else
			gi->debug_print("Expected param after conceal in " + s);
		return;
	}
	// SENSITIVE?
	else if (tok == "show") {
		tok = next_token(s, c1, c2);
		if (is_param(tok))
			set_obj_property(eval_param(tok), "not hidden");
		else
			gi->debug_print("Expected param after conceal in " + s);
		return;
	}
	// SENSITIVE?
	else if (tok == "if") {
		/* TODO TODO */
		uint begin_cond = c2 + 1, end_cond, begin_then, end_then;

		do {
			tok = next_token(s, c1, c2);
			// SENSITIVE?
		} while (tok != "then" && tok != "");

		if (tok == "") {
			gi->debug_print("Expected then in if: " + s);
			return;
		}
		end_cond = c1;
		String cond_str = s.substr(begin_cond, end_cond - begin_cond);

		begin_then = c2 + 1;
		int brace_count = 0;
		do {
			tok = next_token(s, c1, c2);
			for (uint i = 0; i < tok.length(); i ++)
				if (tok[i] == '{')
					brace_count ++;
				else if (tok[i] == '}')
					brace_count --;
			// SENSITIVE?
		} while (tok != "" && !(brace_count == 0 && tok == "else"));
		end_then = c1;


		if (eval_conds(cond_str))
			run_script(s.substr(begin_then, end_then - begin_then), rv);
		else if (c2 < s.length())
			run_script(s.substr(c2), rv);
		return;
	}
	// SENSITIVE?
	else if (tok == "inc" || tok == "dec") {
		// SENSITIVE?
		bool is_dec = (tok == "dec");
		tok = next_token(s, c1, c2);
		if (!is_param(tok)) {
			gi->debug_print("Expected parameter after inc in " + s);
			return;
		}
		tok = eval_param(tok);
		int diff;
		int index = tok.find(';');
		String varname;
		if (index == -1) {
			varname = trim(tok);
			diff = 1;
		} else {
			varname = trim(tok.substr(0, index));
			diff = eval_int(tok.substr(index + 1));
		}
		if (is_dec)
			set_ivar(varname, get_ivar(varname) - diff);
		else
			set_ivar(varname, get_ivar(varname) + diff);
		return;
	}
	// SENSITIVE?
	else if (tok == "lose") {
		/* TODO TODO */
		tok = next_token(s, c1, c2);
		if (!is_param(tok)) {
			gi->debug_print("Expected parameter after lose in " + s);
			return;
		}
		tok = eval_param(tok);

		/* TODO: is the object always moved to location, or only
		 * when it had been in the inventory ?
		 */
		bool was_lost = (ci_equal(get_obj_parent(tok), "inventory"));
		if (was_lost) {
			move(tok, state.location);
			String tmp;
			if (get_obj_action(tok, "lose", tmp))
				run_script_as(tok, tmp);
			//run_script (tmp);
			else if (get_obj_property(tok, "lose", tmp))
				print_formatted(tmp);
		}
		return;
	}
	// SENSITIVE?
	else if (tok == "mailto") {
	}
	// SENSITIVE?
	else if (tok == "modvolume") {
	}
	// SENSITIVE?
	else if (tok == "move") {
		tok = next_token(s, c1, c2);
		if (!is_param(tok)) {
			gi->debug_print("Expected parameter after move in " + s);
			return;
		}
		tok = eval_param(tok);
		int index = tok.find(';');
		if (index == -1) {
			gi->debug_print("No semi in " + tok + " in " + s);
			return;
		}
		move(trim(tok.substr(0, index)), trim(tok.substr(index + 1)));
		return;
	}
	// SENSITIVE?
	else if (tok == "msg") {
		tok = next_token(s, c1, c2);
		if (is_param(tok))
			print_eval(param_contents(tok));
		else
			gi->debug_print("Expected parameter after msg in " + s);
		return;
	}
	// SENSITIVE?
	else if (tok == "msgto") {
		/* QNSO */
	}
	// SENSITIVE?
	else if (tok == "outputoff") {
		//print_formatted ("<<");
		outputting = false;
		return;
	}
	// SENSITIVE?
	else if (tok == "outputon") {
		outputting = true;
		//print_formatted (">>");
		return;
	}
	// SENSITIVE?
	else if (tok == "panes") {
		/* TODO */
	}
	// SENSITIVE?
	else if (tok == "pause") {
		tok = next_token(s, c1, c2);
		if (!is_param(tok)) {
			gi->debug_print("Expected parameter after pause in " + s);;
			return;
		}
		int i = eval_int(param_contents(tok));
		gi->pause(i);
		return;
	}
	// SENSITIVE?
	else if (tok == "picture") {
	}
	// SENSITIVE?
	else if (tok == "playerlose") {
		run_script("displaytext <lose>");
		state.running = false;
		return;
	}
	// SENSITIVE?
	else if (tok == "playerwin") {
		run_script("displaytext <win>");
		state.running = false;
		return;
	}
	// SENSITIVE?
	else if (tok == "playmidi") {
	}
	// SENSITIVE?
	else if (tok == "playmod") {
	}
	// SENSITIVE?
	else if (tok == "playwav") {
	}
	// SENSITIVE?
	else if (tok == "property") {
		tok = next_token(s, c1, c2);
		if (!is_param(tok)) {
			gi->debug_print("Expected parameter in '" + s + "'");
			return;
		}
		Common::Array<String> args = split_param(eval_param(tok));
		for (uint i = 1; i < args.size(); i ++) {
			String val = args[i];
			/*
			if (val[0] == '[' && val[val.length() - 1] == ']')
			  val = val.substr (1, val.length() - 2);
			//state.props.push_back (PropertyRecord (args[0], val));
			*/
			val = trim_braces(val);
			set_obj_property(args[0], val);
		}
		return;
	}
	// SENSITIVE?
	else if (tok == "repeat") {
		/* TODO TODO: assumes script is a "do ..." */
		tok = next_token(s, c1, c2);
		// SENSITIVE?
		if (tok != "while" && tok != "until") {
			gi->debug_print("Expected while or until after repeat in " + s);
			return;
		}
		bool is_while = (tok == "while");
		int start_cond = c2, end_cond = -1;
		while ((tok = next_token(s, c1, c2)) != "") {
			// SENSITIVE?
			if (tok == "do") {
				end_cond = c1;
				break; // TODO: Do I break here?
			}
		}
		if (end_cond == -1) {
			gi->debug_print("No script found after condition in " + s);
			return;
		}
		String cond = trim(s.substr(start_cond, end_cond - start_cond));
		String script = trim(s.substr(end_cond));
		cerr << "Interpreting '" << s << "' as ("
		     << (is_while ? "WHILE" : "UNTIL") << ") ("
		     << cond << ") {" << script << "}\n";
		while (eval_conds(cond) == is_while)
			run_script(script);
		return;
	}
	// SENSITIVE?
	else if (tok == "return") {
		tok = next_token(s, c1, c2);
		if (is_param(tok))
			rv = eval_param(tok);
		else
			gi->debug_print("Expected parameter after return in " + s);
		return;
	}
	// SENSITIVE?
	else if (tok == "reveal") {
		tok = next_token(s, c1, c2);
		if (is_param(tok))
			set_obj_property(eval_param(tok), "not invisible");
		else
			gi->debug_print("Expected param after reveal in " + s);
		return;
	}
	// SENSITIVE?
	else if (tok == "conceal") {
		tok = next_token(s, c1, c2);
		if (is_param(tok))
			set_obj_property(eval_param(tok), "invisible");
		else
			gi->debug_print("Expected param after conceal in " + s);
		return;
	}
	// SENSITIVE?
	else if (tok == "say") {
		tok = next_token(s, c1, c2);
		if (is_param(tok)) {
			tok = eval_param(tok);
			print_formatted("\"" + tok + "\"");
		} else
			gi->debug_print("Expected param after say in " + s);
		return;
	}
	// SENSITIVE?
	else if (tok == "set") {
		String vartype = "";
		tok = next_token(s, c1, c2);
		// SENSITIVE?
		if (tok == "interval") {
			tok = next_token(s, c1, c2);
			if (!is_param(tok)) {
				gi->debug_print("Expected param after set interval in " + s);
				return;
			}
			tok = eval_param(tok);
			int index = tok.find(';');
			if (index == -1) {
				gi->debug_print("No semicolon in param in " + s);
				return;
			}
			//String timer_name = lcase (trim (tok.substr (0, index)));
			String timer_name = trim(tok.substr(0, index));
			uint time_val = parse_int(trim(tok.substr(index + 1)));

			for (uint i = 0; i < state.timers.size(); i ++)
				if (state.timers[i].name == timer_name) {
					state.timers[i].interval = time_val;
					return;
				}
			gi->debug_print("no interval named " + timer_name + " found!");
			return;
		}
		// SENSITIVE?
		if (tok == "String" || tok == "numeric") {
			vartype = tok;
			tok = next_token(s, c1, c2);
		}
		if (!is_param(tok)) {
			gi->debug_print("Expected parameter in " + s);
			return;
		}
		if (tok.find(';') == -1) {
			gi->debug_print("Only one expression in set in " + s);
			return;
		}
		tok = eval_param(tok);
		int index = tok.find(';');
		//String varname = lcase (trim (tok.substr (0, index)));
		String varname = trim(tok.substr(0, index));
		if (vartype == "") {
			for (uint varn = 0; varn < state.ivars.size(); varn ++)
				if (state.ivars[varn].name == varname) {
					vartype = "numeric";
					break;
				}
			if (vartype == "")
				for (uint varn = 0; varn < state.svars.size(); varn ++)
					if (state.svars[varn].name == varname) {
						vartype = "String";
						break;
					}
		}
		if (vartype == "") {
			gi->debug_print("Undefined variable " + varname + " in " + s);
			return;
		}
		if (vartype == "String")
			set_svar(varname, trim_braces(trim(tok.substr(index + 1))));
		else
			set_ivar(varname, eval_int(tok.substr(index + 1)));
		return;
	}
	// SENSITIVE?
	else if (tok == "setstring") {
		tok = next_token(s, c1, c2);
		if (!is_param(tok)) {
			gi->debug_print("Expected parameter in " + s);
			return;
		}
		if (tok.find(';') == -1) {
			gi->debug_print("Only one expression in set in " + s);
			return;
		}
		tok = eval_param(tok);
		int index = tok.find(';');
		String varname = trim(tok.substr(0, index));
		set_svar(varname, trim_braces(trim(tok.substr(index + 1))));
		return;
	}
	// SENSITIVE?
	else if (tok == "setvar") {
		tok = next_token(s, c1, c2);
		if (!is_param(tok)) {
			gi->debug_print("Expected parameter in " + s);
			return;
		}
		if (tok.find(';') == -1) {
			gi->debug_print("Only one expression in set in " + s);
			return;
		}
		tok = eval_param(tok);
		int index = tok.find(';');
		String varname = trim(tok.substr(0, index));
		set_ivar(varname, eval_int(tok.substr(index + 1)));
		return;
	}
	// SENSITIVE?
	else if (tok == "shell") {

	}
	// SENSITIVE?
	else if (tok == "shellexe") {
	}
	// SENSITIVE?
	else if (tok == "speak") {
		tok = next_token(s, c1, c2);
		if (is_param(tok))
			gi->speak(eval_param(tok));
		else
			gi->debug_print("Expected param after speak in " + s);
		return;
	}
	// SENSITIVE?
	else if (tok == "stop") {
		state.running = false;
		return;
	}
	// SENSITIVE?
	else if (tok == "timeron" || tok == "timeroff") {
		// SENSITIVE?
		bool running = (tok == "timeron");
		//tok = lcase (next_token (s, c1, c2));
		tok = next_token(s, c1, c2);
		if (is_param(tok)) {
			tok = eval_param(tok);
			for (uint i = 0; i < state.timers.size(); i ++)
				if (state.timers[i].name == tok) {
					if (running)
						state.timers[i].timeleft = state.timers[i].interval;
					state.timers[i].is_running = running;
					return;
				}
			gi->debug_print("No timer " + tok + " found");
			return;
		}
		gi->debug_print(String("Expected parameter after timer") +
		                (running ? "on" : "off") + " in " + s);
		return;
	}
	// SENSITIVE?
	else if (tok == "type") {
		/* TODO */
	}
	// SENSITIVE?
	else if (tok == "wait") {
		tok = next_token(s, c1, c2);
		if (tok != "") {
			if (!is_param(tok)) {
				gi->debug_print("Expected parameter after wait in " + s);
				return;
			}
			tok = eval_param(tok);
		}
		gi->wait_keypress(tok);
		return;
	}
	// SENSITIVE?
	else if (tok == "with") {
		// QNSO
	}
	gi->debug_print("Unrecognized script " + s);
}

bool geas_implementation::eval_conds(String s) {
	cerr << "if (" + s + ")" << endl;

	uint c1, c2;
	String tok = first_token(s, c1, c2);

	if (tok == "") return true;

	bool rv = eval_cond(s);

	while (tok != "" && tok != "and")
		tok = next_token(s, c1, c2);

	if (tok == "and")
		rv = rv && eval_conds(s.substr(c2));
	else {
		tok = first_token(s, c1, c2);
		while (tok != "" && tok != "or")
			tok = next_token(s, c1, c2);
		if (tok == "or")
			rv = rv || eval_conds(s.substr(c2));
	}

	cerr << "if (" << s << ") --> " << (rv ? "true" : "false") << endl;
	return rv;
}

bool geas_implementation::eval_cond(String s) {
	uint c1, c2;
	String tok = first_token(s, c1, c2);
	// SENSITIVE?
	if (tok == "not")
		return !eval_cond(s.substr(c2));
	// SENSITIVE?
	else if (tok == "action") {
		tok = next_token(s, c1, c2);
		if (!is_param(tok)) {
			gi->debug_print("expected parameter after property in " + s);
			return false;
		}
		tok = eval_param(tok);
		int index = tok.find(';');
		if (index == -1) {
			gi->debug_print("Only one argument to property in " + s);
			return false;
		}
		String obj = trim(tok.substr(0, index));
		String act = trim(tok.substr(index + 1));
		return has_obj_action(obj, act);
	}
	// SENSITIVE?
	else if (tok == "ask") {
		tok = next_token(s, c1, c2);
		if (!is_param(tok)) {
			gi->debug_print("expected parameter after ask in " + s);
			return false;
		}
		tok = eval_param(tok);
		return gi->choose_yes_no(tok);
	}
	// SENSITIVE?
	else if (tok == "exists") {
		tok = next_token(s, c1, c2);
		if (!is_param(tok)) {
			gi->debug_print("expected parameter after exists in " + s);
			return false;
		}
		Common::Array<String> args = split_param(eval_param(tok));
		bool do_report = false;
		for (uint i = 1; i < args.size(); i ++)
			// SENSITIVE?
			if (args[i] == "report")
				do_report = true;
			else
				gi->debug_print("Got modifier " + args[i] + " after exists");
		//args[0] = lcase (args[0]);
		for (uint i = 0; i < state.objs.size(); i ++)
			if (ci_equal(state.objs[i].name, args[0]))
				return state.objs[i].parent != "";
		if (do_report)
			gi->debug_print("exists " + args[0] + " failed due to nonexistence");
		return false;
	}
	// SENSITIVE?
	else if (tok == "flag") {
		tok = next_token(s, c1, c2);
		if (!is_param(tok)) {
			gi->debug_print("expected parameter after flag in " + s);
			return false;
		}
		tok = trim(eval_param(tok));
		return has_obj_property("game", tok);
	}
	// SENSITIVE?
	else if (tok == "got") {
		tok = next_token(s, c1, c2);
		if (!is_param(tok)) {
			gi->debug_print("expected parameter after got in " + s);
			return false;
		}
		//tok = lcase (trim (eval_param (tok)));
		tok = trim(eval_param(tok));
		for (uint i = 0; i < state.objs.size(); i ++)
			if (ci_equal(state.objs[i].name, tok))
				return ci_equal(state.objs[i].parent, "inventory");
		gi->debug_print("No object " + tok + " found while evaling " + s);
		return false;
	}
	// SENSITIVE?
	else if (tok == "here") {
		tok = next_token(s, c1, c2);
		if (!is_param(tok)) {
			gi->debug_print("expected parameter after here in " + s);
			return false;
		}
		//tok = lcase (trim (eval_param (tok)));
		tok = trim(eval_param(tok));
		for (uint i = 0; i < state.objs.size(); i ++)
			if (ci_equal(state.objs[i].name, tok)) {
				//return (ci_equal (state.objs[i].parent, state.location) &&
				//    !has_obj_property (tok, "invisible"));
				return (ci_equal(state.objs[i].parent, state.location));
			}
		/* TODO: is it invisible or hidden? */
		gi->debug_print("No object " + tok + " found while evaling " + s);
		return false;
	}
	// SENSITIVE?
	else if (tok == "is") {
		tok = next_token(s, c1, c2);
		if (!is_param(tok)) {
			gi->debug_print("expected parameter after is in " + s);
			return false;
		}
		tok = eval_param(tok);
		int index;
		// SENSITIVE?
		if ((index = tok.find("!=;")) != -1) {
			uint index1 = index;
			do {
				-- index1;
			} while (index1 > 0 && tok[index1] != ';');

			cerr << "Comparing <" << trim_braces(trim(tok.substr(0, index1)))
			     << "> != <" << trim_braces(trim(tok.substr(index + 3)))
			     << ">\n";
			return ci_notequal(trim_braces(trim(tok.substr(0, index - 1))),
			                   trim_braces(trim(tok.substr(index + 3))));
		}
		if ((index = tok.find("lt=;")) != -1) {
			cerr << "Comparing <" << trim_braces(trim(tok.substr(0, index)))
			     << "> < <" << trim_braces(trim(tok.substr(index + 4)))
			     << ">\n";
			return eval_int(tok.substr(0, index - 1))
			       <= eval_int(tok.substr(index + 4));
		}
		if ((index = tok.find("gt=;")) != -1)
			return eval_int(tok.substr(0, index))
			       >= eval_int(tok.substr(index + 4));
		if ((index = tok.find("lt;")) != -1)
			return eval_int(tok.substr(0, index))
			       < eval_int(tok.substr(index + 3));
		if ((index = tok.find("gt;")) != -1)
			return eval_int(tok.substr(0, index))
			       > eval_int(tok.substr(index + 3));
		if ((index = tok.find(";")) != -1) {
			cerr << "Comparing <" << trim_braces(trim(tok.substr(0, index)))
			     << "> == <" << trim_braces(trim(tok.substr(index + 1)))
			     << ">\n";
			return ci_equal(trim_braces(trim(tok.substr(0, index))),
			                trim_braces(trim(tok.substr(index + 1))));
		}
		gi->debug_print("Bad is condition " + tok + " in " + s);
		return false;
	}
	// SENSITIVE?
	else if (tok == "property") {
		tok = next_token(s, c1, c2);
		if (!is_param(tok)) {
			gi->debug_print("expected parameter after property in " + s);
			return false;
		}
		tok = eval_param(tok);
		int index = tok.find(';');
		if (index == -1) {
			gi->debug_print("Only one argument to property in " + s);
			return false;
		}
		String obj = trim(tok.substr(0, index));
		String prop = trim(tok.substr(index + 1));
		return has_obj_property(obj, prop);
	}
	// SENSITIVE?
	else if (tok == "real") {
		tok = next_token(s, c1, c2);
		if (!is_param(tok)) {
			gi->debug_print("expected parameter after real in " + s);
			return false;
		}
		Common::Array<String> args = split_param(eval_param(tok));
		bool do_report = false;
		for (uint i = 1; i < args.size(); i ++)
			// SENSITIVE?
			if (args[i] == "report")
				do_report = true;
			else
				gi->debug_print("Got modifier " + args[i] + " after exists");
		//args[0] = lcase (args[0]);
		for (uint i = 0; i < state.objs.size(); i ++)
			if (ci_equal(state.objs[i].name, args[0]))
				return true;
		if (do_report)
			gi->debug_print("real " + args[0] + " failed due to nonexistence");
		return false;
	}
	// SENSITIVE?
	else if (tok == "type") {
		tok = next_token(s, c1, c2);
		if (!is_param(tok)) {
			gi->debug_print("Expected parameter after type in " + s);
			return false;
		}
		Common::Array<String> args = split_param(eval_param(tok));
		if (args.size() != 2) {
			gi->debug_print("Expected two parameters to type in " + s);
			return false;
		}
		return gf.obj_of_type(args[0], args[1]);
	}

	gi->debug_print("Bad condition " + s);
	return false;
}

void geas_implementation::run_procedure(String pname, Common::Array<String> args) {
	cerr << "run_procedure " << pname << " (" << args << ")\n";
	Common::Array<String> backup = function_args;
	function_args = args;
	run_procedure(pname);
	function_args = backup;
}

void geas_implementation::run_procedure(String pname) {
	for (uint i = 0; i < gf.size("procedure"); i ++)
		if (ci_equal(gf.block("procedure", i).name, pname)) {
			const GeasBlock &proc = gf.block("procedure", i);
			//cerr << "Running procedure " << proc << endl;
			for (uint j = 0; j < proc.data.size(); j ++) {
				//cerr << "  Running line #" << j << ": " << proc.data[j] << endl;
				run_script(proc.data[j]);
			}
			return;
		}
	gi->debug_print("No procedure " + pname + " found.");
}

String geas_implementation::run_function(String pname, Common::Array<String> args) {
	cerr << "run_function (w/ args) " << pname << " (" << args << ")\n";
	/* Parameter is handled specially because it can't change the stack */
	// SENSITIVE?
	if (pname == "parameter") {
		if (args.size() != 1) {
			gi->debug_print("parameter called with " + string_int(args.size())
			                + " args");
			return "";
		}
		uint num = parse_int(args[0]);
		if (0 < num && num <= function_args.size()) {
			cerr << "   --> " << function_args[num - 1] << "\n";
			return function_args[num - 1];
		}
		cerr << "   --> too many arguments\n";
		return "";
	}
	Common::Array<String> backup = function_args;
	function_args = args;
	for (uint i = 0; i < args.size(); i ++)
		set_svar("quest.function.parameter." + string_int(i + 1), args[i]);
	String rv = run_function(pname);
	function_args = backup;
	return rv;
}

String geas_implementation::bad_arg_count(String fname) {
	gi->debug_print("Called " + fname + " with " +
	                string_int(function_args.size()) + " arguments.");
	return "";
}

String geas_implementation::run_function(String pname) {
	cerr << "geas_implementation::run_function (" << pname << ", " << function_args << ")\n";
	//pname = lcase (pname);
	// SENSITIVE?
	if (pname == "getobjectname") {
		if (function_args.size() == 0)
			return bad_arg_count(pname);
		//return get_obj_name (function_args);
		Common::Array<String> where;
		for (uint i = 1; i < function_args.size(); i ++)
			where.push_back(function_args[i]);
		if (where.size() == 0) {
			where.push_back(state.location);
			where.push_back("inventory");
		}
		bool is_internal = false;
		return get_obj_name(function_args[0], where, is_internal);
	}
	// SENSITIVE?
	else if (pname == "loadmethod") {
		/* TODO TODO */
		return "normal";
		// "loaded" on restore from qsg
	}
	// SENSITIVE?
	else if (pname == "locationof") {
		if (function_args.size() != 1)
			return bad_arg_count(pname);

		return get_obj_parent(function_args[0]);
	}
	// SENSITIVE?
	else if (pname == "objectproperty") {
		if (function_args.size() != 2)
			return bad_arg_count(pname);

		String rv;
		get_obj_property(function_args[0], function_args[1], rv);
		return rv;
	}
	// SENSITIVE?
	else if (pname == "timerstate") {
		if (function_args.size() != 1)
			return bad_arg_count(pname);

		//String timername = lcase (function_args[0]);
		String timername = function_args[0];
		for (uint i = 0; i < state.timers.size(); i ++)
			if (state.timers[i].name == timername)
				return state.timers[i].is_running ? "1" : "0";
		return "!";
	}
	// SENSITIVE?
	else if (pname == "displayname") {
		if (function_args.size() != 1)
			return bad_arg_count(pname);

		return displayed_name(function_args[0]);
	}
	// SENSITIVE?
	else if (pname == "capfirst") {
		if (function_args.size() != 1)
			return bad_arg_count(pname);

		return pcase(function_args[0]);
	}
	// SENSITIVE?
	else if (pname == "instr") {
		/* TODO What if it's not present? */
		if (function_args.size() != 2 && function_args.size() != 3)
			return bad_arg_count(pname);

		int rv;
		if (function_args.size() == 2)
			rv = function_args[0].find(function_args[1]);
		else
			rv = function_args[1].find(function_args[2],
			                           parse_int(function_args[0]));

		if (rv == -1)
			return string_int(rv); // TODO What goes here?
		else
			return string_int(rv);
	}
	// SENSITIVE?
	else if (pname == "lcase") {
		if (function_args.size() != 1)
			return bad_arg_count(pname);

		String rv = function_args[0];
		for (uint i = 0; i < rv.size(); i ++)
			rv[i] = tolower(rv[i]);
		return rv;
	}
	// SENSITIVE?
	else if (pname == "left") {
		if (function_args.size() != 2)
			return bad_arg_count(pname);

		uint i = parse_int(function_args[1]);
		if (i > function_args[0].length())
			return function_args[0];
		else
			return function_args[0].substr(0, i);
	}
	// SENSITIVE?
	else if (pname == "lengthof") {
		if (function_args.size() != 1)
			return bad_arg_count(pname);

		return string_int(function_args[0].length());
	}
	// SENSITIVE?
	else if (pname == "mid") {
		if (function_args.size() != 3)
			return bad_arg_count(pname);

		uint start = parse_int(function_args[1]),
		     len = parse_int(function_args[2]);
		if (start > function_args[0].length())
			return "";
		if (start + len > function_args[0].length())
			return function_args[0].substr(start);
		return function_args[0].substr(start, len);
	}
	// SENSITIVE?
	else if (pname == "right") {
		if (function_args.size() != 2)
			return bad_arg_count(pname);

		uint size = parse_int(function_args[1]);
		if (size > function_args[0].length())
			return function_args[0];
		return function_args[0].substr(function_args[0].length() - size);
	} else if (pname == "ubound") {
		if (function_args.size() != 1)
			return bad_arg_count(pname);

		/* TODO TODO */
		return "";
	}
	// SENSITIVE?
	else if (pname == "ucase") {
		if (function_args.size() != 1)
			return bad_arg_count(pname);

		String rv = function_args[0];
		for (uint i = 0; i < rv.length(); i ++)
			rv[i] = toupper(rv[i]);
		return rv;
	}
	// SENSITIVE?
	else if (pname == "rand") {
		if (function_args.size() != 2)
			return bad_arg_count(pname);
		uint lower = parse_int(function_args[0]),
		     upper = parse_int(function_args[1]);

		// TODO: change this to use the high order bits of the random # instead
		return string_int(lower + (g_vm->getRandomNumber(0x7fffff) % (upper + 1 - lower)));
	}
	// SENSITIVE?
	else if (pname == "speechenabled") {
		if (function_args.size() != 0)
			return bad_arg_count(pname);

		return "0";
		/* TODO: return 1 if speech is enabled */
	}
	// SENSITIVE?
	else if (pname == "symbol") {
		if (function_args.size() != 1)
			return bad_arg_count(pname);

		// SENSITIVE?
		if (function_args[0] == "gt")
			return ">";
		// SENSITIVE?
		else if (function_args[0] == "lt")
			return "<";
		gi->debug_print("Bad symbol argument: " + function_args[0]);
		return "";
	}
	// SENSITIVE?
	else if (pname == "numberparameters")
		return string_int(function_args.size());
	// SENSITIVE?
	else if (pname == "thisobject")
		return this_object;

	/* disconnectedby, id, name, removeformatting */

	String rv = "";

	for (uint i = 0; i < gf.size("function"); i ++)
		if (ci_equal(gf.block("function", i).name, pname)) {
			const GeasBlock &proc = gf.block("function", i);
			cerr << "Running function " << proc << endl;
			for (uint j = 0; j < proc.data.size(); j ++) {
				cerr << "  Running line #" << j << ": " << proc.data[j] << endl;
				run_script(proc.data[j], rv);
			}
			return rv;
		}
	gi->debug_print("No function " + pname + " found.");
	return "";
}



v2string geas_implementation::get_inventory() {
	return get_room_contents("inventory");
}

v2string geas_implementation::get_room_contents() {
	return get_room_contents(state.location);
}

v2string geas_implementation::get_room_contents(String room) {
	v2string rv;
	String objname;
	for (uint i = 0; i < state.objs.size(); i ++)
		if (state.objs[i].parent == room) {
			objname = state.objs[i].name;
			if (!has_obj_property(objname, "invisible") &&
			        !has_obj_property(objname, "hidden")) {
				vstring tmp;

				String print_name, temp_str;
				if (!get_obj_property(objname, "alias", print_name))
					print_name = objname;
				/*
				if (get_obj_property (objname, "prefix", temp_str))
				  print_name = temp_str + " " + print_name;
				if (get_obj_property (objname, "suffix", temp_str))
				  print_name = print_name + " " + temp_str;
				*/
				tmp.push_back(print_name);

				String otype;
				if (!get_obj_property(objname, "displaytype", otype))
					otype = "object";
				tmp.push_back(otype);
				rv.push_back(tmp);
			}
		}
	return rv;
}

vstring geas_implementation::get_status_vars() {
	vstring rv;

	String tok, line;
	uint c1, c2;

	for (uint i = 0; i < gf.size("variable"); i ++) {
		const GeasBlock &gb = gf.block("variable", i);

		bool nozero = false;
		String disp;
		bool is_numeric = true;

		cerr << "g_s_v: " << gb << endl;

		for (uint j = 0; j < gb.data.size(); j ++) {
			line = gb.data[j];
			cerr << "  g_s_v:  " << line << endl;
			tok = first_token(line, c1, c2);
			// SENSITIVE?
			if (tok == "display") {
				tok = next_token(line, c1, c2);

				// SENSITIVE?
				if (tok == "nozero") {
					nozero = true;
					tok = next_token(line, c1, c2);
				}
				if (!is_param(tok))
					gi->debug_print("Expected param after display: " + line);
				else
					disp = tok;
			}
			// SENSITIVE?
			else if (tok == "type") {
				tok = next_token(line, c1, c2);
				// SENSITIVE?
				if (tok == "String")
					is_numeric = false;
			}
		}

		cerr << "  g_s_v, block 2, tok == '" << tok << "'" << endl;
		if (!(is_numeric && nozero && get_ivar(gb.name) == 0) && disp != "") {
			disp = param_contents(disp);
			String outval = "";
			for (uint j = 0; j < disp.length(); j ++)
				if (disp[j] == '!') {
					if (is_numeric)
						outval = outval + string_int(get_ivar(gb.name));
					//outval = outval + string_int (get_ivar (gb.lname));
					else
						outval = outval + get_svar(gb.name);
					//outval = outval + get_svar (gb.lname);
				} else if (disp[j] == '*') {
					uint k;
					for (k = j + 1; k < disp.length() && disp[k] != '*'; k ++)
						;
					//if (!is_numeric || get_ivar (gb.lname) != 1)
					if (!is_numeric || get_ivar(gb.name) != 1)
						outval = outval + disp.substr(j + 1, k - j - 1);
					j = k;
				} else
					outval = outval + disp[j];
			rv.push_back(eval_string(outval));
		}
	}
	return rv;
}

Common::Array<bool> geas_implementation::get_valid_exits() {
	Common::Array<bool> rv;
	cerr << "Getting valid exits\n";
	rv.push_back(exit_dest(state.location, "northwest") != "");
	rv.push_back(exit_dest(state.location, "north") != "");
	rv.push_back(exit_dest(state.location, "northeast") != "");
	rv.push_back(exit_dest(state.location, "west") != "");
	rv.push_back(exit_dest(state.location, "out") != "");
	rv.push_back(exit_dest(state.location, "east") != "");
	rv.push_back(exit_dest(state.location, "southwest") != "");
	rv.push_back(exit_dest(state.location, "south") != "");
	rv.push_back(exit_dest(state.location, "southeast") != "");
	rv.push_back(exit_dest(state.location, "up") != "");
	rv.push_back(exit_dest(state.location, "down") != "");
	cerr << "Done getting valid exits\n";

	return rv;
}

void geas_implementation::print_eval_p(String s) {
	print_formatted(pcase(eval_string(s)));
}

void geas_implementation::print_eval(String s) {
	print_formatted(eval_string(s));
}

String geas_implementation::eval_string(String s) {
	String rv;
	uint i, j;
	bool do_print = (s.find('$') != -1);
	if (do_print) cerr << "eval_string (" << s << ")\n";
	for (i = 0; i < s.length(); i ++) {
		//if (do_print) cerr << "e_s: i == " << i << ", s[i] == '" << s[i] << "'\n";
		if (i + 1 < s.length() && s[i] == '#' && s[i + 1] == '@') {
			for (j = i + 1; j < s.length() && s[j] != '#'; j ++)
				;
			if (j == s.length()) {
				gi->debug_print("eval_string: Unmatched hash in " + s);
				break;
			}
			//cerr << "dereferencing " + s.substr (i+2, j-i-2) << endl;
			rv = rv + displayed_name(get_svar(s.substr(i + 2, j - i - 2)));
			i = j;
		} else if (s[i] == '#') {
			for (j = i + 1; j < s.length() && s[j] != '#'; j ++)
				;
			if (j == s.length()) {
				gi->debug_print("eval_string: Unmatched hash in " + s);
				break;
			}
			uint k;
			for (k = i + 1; k < j && s[k] != ':'; k ++)
				;
			if (k == j && j == i + 1)
				rv += "#";
			else if (k == j)
				rv += get_svar(s.substr(i + 1, j - i - 1));
			else {
				String propname = s.substr(k + 1, j - k - 1);
				if (s[i + 1] == '(') {
					if (s[k - 1] != ')') {
						gi->debug_print("e_s: Missing paren in '" +
						                s.substr(i, j - i) + "' of '" + s + "'");
						break;
					}
					String objvar = s.substr(i + 2, k - i - 3);
					String objname = get_svar(objvar);
					/*
					cerr << "e_s: Getting prop [(" << objvar << ")] --> ["
					     << objname
					     << "]:[" << propname << "] for " << s << endl;
					*/
					String tmp;
					if (get_obj_property(objname, propname, tmp))
						rv += tmp;
					else
						gi->debug_print("e_s: Evaluating nonexistent object prop "
						                "{" + objname + "}:{" + propname + "}");
				} else {
					String objname = s.substr(i + 1, k - i - 1);
					/*
					cerr << "e_s: Getting prop [" << objname << "]:["
					     << propname << "] for " << s << endl;
					*/
					String tmp;
					if (get_obj_property(objname, propname, tmp))
						rv += tmp;
					else
						gi->debug_print("e_s: Evaluating nonexistent var " + objname);
				}
			}
			i = j;
		} else if (s[i] == '%') {
			for (j = i + 1; j < s.length() && s[j] != '%'; j ++)
				;
			if (j == s.length()) {
				gi->debug_print("e_s: Unmatched %s in " + s);
				break;
			}
			//cerr << "e_s: Getting ivar [" << s.substr (i+1, j-i-1) << "] for " << s << endl;
			if (j == i + 1)
				rv += "%";
			else
				rv += string_int(get_ivar(s.substr(i + 1, j - i - 1)));
			i = j;
		} else if (s[i] == '$') {
			j = s.find('$', i + 1);
			/*
			for (j = i + 1; j < s.length() && s[j] != '$'; j ++)
			  {
			    cerr << "  In searching for partner, j == " << j
			   << ", s[j] == '" << s[j] << "', (s[j] == '$') == "
			   << ((s[j] == '$') ? "true" : "false") << "\n";
			  }
			*/
			//if (j == rv.size())
			if (j == (uint)-1) {
				gi->debug_print("Unmatched $s in " + s);
				return rv + s.substr(i);
			}
			String tmp1 = s.substr(i + 1, j - i - 1);
			cerr << "e_s: first substr was '" << tmp1 << "'\n";
			String tmp = eval_string(tmp1);
			//String tmp = eval_string (s.substr (i + 1, j - i - 2));
			//cerr << "Taking substring of '" + s + "': '" + tmp + "'\n";
			cerr << "e_s: eval substr " + s + "': '" + tmp + "'\n";

			String func_eval;

			int paren_open, paren_close;
			if ((paren_open = tmp.find('(')) == -1)
				func_eval = run_function(tmp);
			else {
				paren_close = tmp.find(')', paren_open);
				if (paren_close == -1)
					gi->debug_print("No matching right paren in " + tmp);
				else {
					String f_name = tmp.substr(0, paren_open);
					String f_args = tmp.substr(paren_open + 1,
					                           paren_close - paren_open - 1);
					func_eval = run_function(f_name, split_f_args(f_args));
				}
			}
			rv = rv + func_eval;
			i = j;
		} else
			rv += s[i];
	}


	//cerr << "eval_string (" << s << ") -> <" << rv << ">\n";
	return rv;
}

void geas_implementation::tick_timers() {
	if (!state.running)
		return;
	//cerr << "tick_timers()\n";
	for (uint i = 0; i < state.timers.size(); i ++) {
		TimerRecord &tr = state.timers[i];
		//cerr << "  Examining " << tr << "\n";
		if (tr.is_running) {
			//cerr << "    Advancing " << tr.name << ": " << tr.timeleft
			//     << " / " << tr.interval << "\n";
			if (tr.timeleft != 0)
				tr.timeleft --;
			else {
				tr.is_running = false;
				tr.timeleft = tr.interval;
				const GeasBlock *gb = gf.find_by_name("timer", tr.name);
				if (gb != NULL) {
					//cout << "Running it!\n";
					String tok, line;
					uint c1, c2;
					for (uint j = 0; j < gb->data.size(); j ++) {
						line = gb->data[j];
						tok = first_token(line, c1, c2);
						// SENSITIVE?
						if (tok == "action") {
							run_script(line.substr(c2));
							break;
						}
					}
				}
			}
		}
	}
}

/***********************************
 *                                 *
 *                                 *
 *                                 *
 * GeasInterface related functions *
 *                                 *
 *                                 *
 *                                 *
 ***********************************/


GeasResult GeasInterface::print_formatted(String s, bool with_newline) {
	unsigned int i, j;

	cerr << "print_formatted (" << s << ", " << with_newline << ")" << endl;

	for (i = 0; i < s.length(); i ++) {
		//std::cerr << "i == " << i << std::endl;
		if (s[i] == '|') {
			// changed indicated whether cur_style has been changed
			// and update_style should be called at the end.
			// it is true unless cleared (by |n or |w).
			bool changed = true;
			j = i;
			i ++;
			if (i == s.length())
				continue;

			// Are the | codes case-sensitive?
			switch (s[i]) {
			case 'u':
				cur_style.is_underlined = true;
				break;
			case 'i':
				cur_style.is_italic     = true;
				break;
			case 'b':
				cur_style.is_bold       = true;
				break;
			case 'c':
				i ++;

				if (i == s.length()) {
					clear_screen();
					break;
				}

				switch (s[i]) {
				case 'y':
					cur_style.color = "#ffff00";
					break;
				case 'g':
					cur_style.color = "#00ff00";
					break;
				case 'l':
					cur_style.color = "#0000ff";
					break;
				case 'r':
					cur_style.color = "#ff0000";
					break;
				case 'b':
					cur_style.color = "";
					break;

				default:
					clear_screen();
					--i;
				}
				break;

			case 's': {
				i ++;
				if (i == s.length() || !(s[i] >= '0' && s[i] <= '9'))
					continue;
				i ++;
				if (i == s.length() || !(s[i] >= '0' && s[i] <= '9'))
					continue;

				int newsize = parse_int(s.substr(j, i - j));
				if (newsize > 0)
					cur_style.size = newsize;
				else
					cur_style.size = default_size;
			}
			break;

			case 'j':
				i ++;

				if (i == s.length() ||
				        !(s[i] == 'l' || s[i] == 'c' || s[i] == 'r'))
					continue;
				if (s[i] == 'l') cur_style.justify = JUSTIFY_LEFT;
				else if (s[i] == 'r') cur_style.justify = JUSTIFY_RIGHT;
				else if (s[i] == 'c') cur_style.justify = JUSTIFY_CENTER;
				break;

			case 'n':
				print_newline();
				changed = false;
				break;

			case 'w':
				wait_keypress("");
				changed = false;
				break;

			case 'x':
				i ++;

				if (s[i] == 'b')
					cur_style.is_bold = false;
				else if (s[i] == 'u')
					cur_style.is_underlined = false;
				else if (s[i] == 'i')
					cur_style.is_italic = false;
				else if (s[i] == 'n' && i + 1 == s.length())
					changed = with_newline = false;
				break;

			default:
				cerr << "p_f: Fallthrough " << s[i] << endl;
				changed = false;
			}
			if (changed)
				update_style();
		} else {
			for (j = i; i != s.length() && s[i] != '|'; i ++)
				;
			print_normal(s.substr(j, i - j));
			if (i != s.length() && s[i] == '|')
				-- i;
		}
	}
	if (with_newline)
		print_newline();
	return r_success;
}

void GeasInterface::set_default_font_size(String size) {
	default_size = parse_int(size);
}

void GeasInterface::set_default_font(String font) {
	default_font = font;
}

bool GeasInterface::choose_yes_no(String question) {
	Common::Array<String> v;
	v.push_back("Yes");
	v.push_back("No");
	return (make_choice(question, v) == 0);
}

} // End of namespace Quest
} // End of namespace Glk