// --------------------------------------------------------------------------
// Copyright (c) 2012 Henry Strickland & Thomas Shanks
// 
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
// --------------------------------------------------------------------------
package terse.vm;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.Reader;
import java.util.Arrays;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import terse.vm.Cls.UsrMeth;
import terse.vm.Expr.Send;
import terse.vm.Ur.Blk;
import terse.vm.Ur.Buf;
import terse.vm.Ur.Dict;
import terse.vm.Ur.Undefined;
import terse.vm.Ur.Num;
import terse.vm.Ur.Obj;
import terse.vm.Ur.Str;
import terse.vm.Ur.Sys;
import terse.vm.Ur.Vec;
import terse.vm.Usr.Tmp;
import terse.vm.Usr.UsrCls;

public abstract class Terp extends Static {

	public static Pattern WORLD_P = Pattern.compile(
			"^[a-z][a-z][a-z][0-9]{0,3}$", Pattern.CASE_INSENSITIVE);

	static Pattern TXTFILE_P = Pattern.compile("^[a-z][a-z0-9_]{1,31}\\.txt$");

	protected static String YAK_WEB_PAGE = "http://wiki.yak.net/1017";
	
	public static Pattern WHITE_PLUS = Pattern.compile("\\s+");

	public String worldName = "";
	public String worldFilename = "";
	boolean loadingWorldFile = false;
	public HashMap<String, Cls> clss = new HashMap<String, Cls>();
	public HashMap<String, Integer> allMethodNames = new HashMap<String, Integer>();

	public long tickCounter = Long.MAX_VALUE;
	public int expectingTerseException = 0;

	public Wrap wrap;
	public Cls tUr;
	public Cls tUrCls;
	public Cls tObj;
	public Cls tObjCls;
	public Cls tCls;
	public Cls tClsCls;
	public Cls tMetacls;
	public Cls tMetaclsCls;
	Cls tSuper;
	Cls tSuperCls;
	Cls tMeth;
	Cls tMethCls;
	Cls tJavaMeth;
	Cls tJavaMethCls;
	Cls tUsrMeth;
	Cls tUsrMethCls;
	Cls tSys;
	Cls tSysCls;
	Cls tNum;
	Cls tNumCls;
	Cls tBuf;
	Cls tBufCls;
	Cls tStr;
	Cls tStrCls;
	Cls tUndefined;
	Cls tUndefinedCls;
	Cls tBlk;
	Cls tBlkCls;
	Cls tVec;
	Cls tVecCls;
	Cls tDict;
	Cls tDictCls;
	Cls tExpr;
	Cls tExprCls;
	public Cls tUsr;
	public Cls tUsrCls;
	Cls tTmp;
	Cls tTmpCls;
	public Num instTrue;
	public Num instFalse;
	public Undefined instNil;
	Ur.Super instSuper;
	
	Exception loadWorldException;

	static boolean tolerateNullClass = false;

	/**
	 * The Interpreter.
	 * 
	 * @throws IOException
	 */
	protected Terp(boolean pleaseLoadPrelude, String worldName)
			throws IOException {
		if (worldName.length() > 0) {
			if (!WORLD_P.matcher(worldName).matches()) {
				toss("Bad world name <%s>", worldName);
			}
			this.worldName = worldName;
			this.worldFilename = "w_" + worldName + ".txt";
		}

		tolerateNullClass = true;
		this.tUrCls = new Cls(null/* Metacls */, this, "UrCls", null/* Cls */);
		this.tUr = new Cls(tUrCls, this, "Ur", null/* Ur has no super */);

		this.tObjCls = new Cls(null/* Metacls */, this, "ObjCls", null/* Cls */);
		this.tObj = new Cls(tObjCls, this, "Obj", tUr);

		this.tClsCls = new Cls(null/* MetaCls */, this, "ClsCls", tObjCls);
		this.tCls = new Cls(tClsCls, this, "Cls", tObj);

		this.tMetaclsCls = new Cls(null/* MetaCls */, this, "MetaclsCls",
				tObjCls);
		this.tMetacls = new Cls(tMetaclsCls, this, "Metacls", tCls);

		// Patch up: All *Cls's are instances of Metacls.
		this.tUrCls.cls = this.tMetacls;
		this.tObjCls.cls = this.tMetacls;
		this.tClsCls.cls = this.tMetacls;
		this.tMetaclsCls.cls = this.tMetacls;

		// Patch up: All *Cls's ultimately derive from Cls.
		this.tUrCls.supercls = tCls;
		this.tObjCls.supercls = tCls;

		tolerateNullClass = false;

		// Super is a very special class, from Ur, not from Obj.
		this.tSuperCls = new Cls(tMetacls, this, "SuperCls", tUrCls);
		this.tSuper = new Cls(tSuperCls, this, "Super", tUr);

		this.tMethCls = new Cls(tMetacls, this, "MethCls", tObjCls);
		this.tMeth = new Cls(tMethCls, this, "Meth", tObj);
		this.tJavaMethCls = new Cls(tMetacls, this, "JavMethCls", tMethCls);
		this.tJavaMeth = new Cls(tJavaMethCls, this, "JavMeth", tMeth);
		this.tUsrMethCls = new Cls(tMetacls, this, "UsrMethCls", tMethCls);
		this.tUsrMeth = new Cls(tUsrMethCls, this, "UsrMeth", tMeth);
		this.tSysCls = new Cls(tMetacls, this, "SysCls", tObjCls);
		this.tSys = new Cls(tSysCls, this, "Sys", tObj);
		this.tNumCls = new Cls(tMetacls, this, "NumCls", tObjCls);
		this.tNum = new Cls(tNumCls, this, "Num", tObj);
		this.tBufCls = new Cls(tMetacls, this, "BufCls", tObjCls);
		this.tBuf = new Cls(tBufCls, this, "Buf", tObj);
		this.tStrCls = new Cls(tMetacls, this, "StrCls", tObjCls);
		this.tStr = new Cls(tStrCls, this, "Str", tObj);

		// UsrCls is the only concrete Java subtype of Cls,
		// and it is a little bit weird.
		this.tUsrCls = new Cls(tMetacls, this, "UsrCls", tObjCls);
		this.tUsr = new Usr.UsrCls(tUsrCls, this, "Usr", tObj);
		this.tTmpCls = new Cls(tMetacls, this, "TmpCls", tUsrCls);
		this.tTmp = new Usr.UsrCls(tTmpCls, this, "Tmp", tUsr);
		// this.tUsr.myVars = strs("p", "q"); // TODO: temp hack.
		// this.tUsr.recalculateAllVarsHereAndBelow();

		this.tUndefinedCls = new Cls(tMetacls, this, "UndefinedCls", tObjCls);
		this.tUndefined = new Cls(tUndefinedCls, this, "Undefined", tObj);
		this.tBlkCls = new Cls(tMetacls, this, "BlkCls", tObjCls);
		this.tBlk = new Cls(tBlkCls, this, "Blk", tObj);
		this.tVecCls = new Cls(tMetacls, this, "VecCls", tObjCls);
		this.tVec = new Cls(tVecCls, this, "Vec", tObj);
		this.tDictCls = new Cls(tMetacls, this, "DictCls", tObjCls);
		this.tDict = new Cls(tDictCls, this, "Dict", tObj);

		this.tExprCls = new Cls(tMetacls, this, "ExprCls", tObjCls);
		this.tExpr = new Cls(tExprCls, this, "Expr", tObj);

		this.instTrue = newNum(1);
		this.instFalse = newNum(0);
		this.instNil = new Undefined(tUndefined);
		this.instSuper = new Ur.Super(this);

		// TODO: Convert all methods to "wrap" style, and get rid of all these
		// static inits.
		Buf.addBuiltinMethodsForBuf(this);
		Str.addBuiltinMethodsForStr(this);
		Vec.addBuiltinMethodsForVec(this);
		Dict.addBuiltinMethodsForDict(this);

		tolerateNullClass = true;
		loadingWorldFile = true;
		wrap = new Wrap();
		wrap.installClasses(this);
		wrap.installMethods(this);
		loadingWorldFile = false;
		tolerateNullClass = false;

		if (pleaseLoadPrelude) {
			loadPrelude();
		}

		if (worldName.length() > 0) {
			loadWorldException = new InitialWorldReader(worldName).loadFile(worldFilename);
			if (loadWorldException != null && !(loadWorldException instanceof FileNotFoundException)) {
				say("COULD BE A PROBLEM IN InitialWorldReader(%s)loadfile(%s): %s", worldName, worldFilename, loadWorldException);
			}
		}
	}

	final static int LOG_LEN = 128;
	String[] logArray = new String[LOG_LEN];
	int logPtr = 0;
	protected void recordLog(String msg) {
//		double t = (System.nanoTime() % (100 * 1000000000)) / 1000000000.0;
		double t = (System.currentTimeMillis() % (100 * 1000)) / 1000.0;
		logArray[logPtr] = fmt("[%.3f] %s", t, msg);
		logPtr = (logPtr + 1) % LOG_LEN;
	}
	public String[] getLog() {
		int n = 0;
		for (int i = 0; i < LOG_LEN; i++) {
			if (logArray[i] != null) {
				++n;
			}
		}
		String[] z = new String[n];
		int k = 0;
		for (int i = 0; i < LOG_LEN; i++) {
			int j = (i + logPtr) % LOG_LEN;
			if (logArray[j] != null) {
				z[k] = logArray[j];
				++k;
			}
		}
		for (int i = 0; i < n/2; i++) {
			String t = z[i];
			z[i] = z[n-i-1];
			z[n-i-1] = t;
		}
		return z;
	}
	
	
	public static class TooManyTicks extends Error {
	}

	public void tick() {
		--tickCounter;
		if (tickCounter < 1) {
			try {
				throw new RuntimeException("Going To Throw TooManyTicks");
			} catch (RuntimeException ex) {
				ex.printStackTrace();

				StringBuffer sb = new StringBuffer(ex.toString());
				StackTraceElement[] elems = ex.getStackTrace();
				for (StackTraceElement e : elems) {
					sb.append("\n  * ");
					sb.append(e.toString());
				}
				
				say(sb.toString());
			}
			throw new TooManyTicks();
		}

	}

	public Frame newFrame(Frame prev, Ur self, Expr.MethTop top) {
		return new Frame(prev, self, top);
	}

	public class Frame extends Obj { // Important: non-static, tied to a Terp.

		// =get Frame . prev prevFrame
		Frame prev; // for listing the stack
		// =get Frame . self receiver
		Ur self;
		// =get Frame Ur[] locals localVars
		Ur[] locals;
		// =get Frame . top currentMethodExpr
		Expr.MethTop top;
		// =get Frame int level level
		int level;

		// =cls "Sys" Frame Obj
		private Frame(Frame prev, Ur self, Expr.MethTop top) {
			super(wrap.clsFrame);
			this.prev = prev;
			this.self = self;
			this.top = top;
			this.level = (prev == null) ? 0 : prev.level + 1;
			this.locals = new Ur[top.numLocals];
			for (int i = 0; i < locals.length; i++) {
				this.locals[i] = getTerp().instNil;
			}
		}

		@Override
		public String toString() {
			StringBuffer sb = new StringBuffer("FRAME{");
			sb.append(fmt("self=<%s>; ", self));
			for (int i = 0; i < locals.length; i++) {
				sb.append(fmt("\"%d\"<%s>; ", i, locals[i]));
			}
			sb.append("}");
			return sb.toString();
		}
	}

	final public Terp getTerp() {
		return this;
	}
	
	final public Ur nullToNil(Ur x) {
		return (x == null) ? instNil : x;
	}

	final public Num newNum(double a) {
		return new Num(this, a);
	}

	final public Str newStr(String s) {
		return new Str(this, s);
	}

	final public Tmp newTmp() {
		return new Tmp(this);
	}

	final public Dict newDict(Ur[] arr) {
		Dict z = new Dict(this);
		for (int i = 0; i < arr.length; i++) {
			if (arr[i] instanceof Vec) {
				Vec v = (Vec) arr[i];
				if (v.vec.size() == 2) {
					z.dict.put(v.vec.get(0), v.vec.get(1));
				} else {
					toss("To initialize assoc in Dict, expected Vec of length 2, but got <%s#%d#%s>; inside <%s>",
							v.cls, v.vec.size(), v, arrayToString(arr));
				}
			} else {
				toss("To initialize assoc in Dict, expected Vec of length 2, but got <%s#%s>; inside <%s>",
						arr[i].cls, arr[i], arrayToString(arr));
			}
		}
		return z;
	}

	final public Vec newVec(Ur[] a) {
		Vec z = new Vec(this);
		for (int i = 0; i < a.length; ++i) {
			z.vec.add(a[i]);
		}
		return z;
	}

	final public Vec newVec(int[] a) {
		Vec z = new Vec(this);
		for (int i = 0; i < a.length; ++i) {
			z.vec.add(newNum(a[i]));
		}
		return z;
	}

	final public Vec mkStrVec(String... strs) {
		Ur[] arr = new Ur[strs.length];
		for (int i = 0; i < strs.length; i++) {
			arr[i] = newStr(strs[i]);
		}
		return newVec(arr);
	}

	final public Vec mkSingletonStrVecVec(String... strs) {
		Ur[] arr = new Ur[strs.length];
		for (int i = 0; i < strs.length; i++) {
			arr[i] = mkStrVec(strs[i]);
		}
		return newVec(arr);
	}

	public Dict handleUrl(String url, HashMap<String, String> query) {
		say("runUrl: %s", url);
		query = (query == null) ? new HashMap<String, String>() : query;
		Ur[] queryArr = new Ur[query.size()];
		int i = 0;
		for (String k : query.keySet()) {
			String v = query.get(k);
			if (k == null)
				k = "HOW_DID_WE_GET_A_NULL_KEY";
			if (v == null)
				v = "HOW_DID_WE_GET_A_NULL_VALUE";
			Ur queryKey = newStr(k);
			Ur queryValue = newStr(v.replaceAll("\r\n", "\n"));
			queryArr[i] = new Vec(this, urs(queryKey, queryValue));
			++i;
		}
		Dict qDict = newDict(queryArr);
		assert url.startsWith("/");
		if (url.equals("/")) {
			url = "/Top";
		}

		// To get app name, skip the initial '/', and split on dots.
		String[] word = url.substring(1).split("[.]");
		assert word.length > 0;
		String appName = word[0];

		Dict result = null;
		try {
			Cls cls = getTerp().clss.get(appName.toLowerCase());
			if (cls == null) {
				toss("Rendering class does not exist: <%s>", appName);
			}

			String urlRepr = newStr(url).repr();
			String qDictRepr = qDict.repr(); // Inefficient. TODO.
			Ur result_ur = instNil;
			int id = 0;

			Obj inst = null;
			try {
				id = Integer.parseInt(word[1]);
				if (cls instanceof Usr.UsrCls) {
					inst = ((Usr.UsrCls) cls).cache.find(id);
				}
			} catch (Exception _) {
				// pass.
			}
			long before = tickCounter;
			long nanosBefore = System.nanoTime();
			if (inst != null) {
				result_ur = inst.eval(fmt("self handle: (%s) query: (%s)",
						urlRepr, qDictRepr));
			} else if (Send.understands(cls, "handle:query:")) {
				say("CLS <%s> understands handle:query: so sending to class.", cls);
				// First try sending to the class.
				result_ur = cls.eval(fmt("self handle: (%s) query: (%s)",
						urlRepr, qDictRepr));
			} else {
				Ur instance = cls.eval("self new");
				Usr usrInst = instance.asUsr();
				// TODO: LRU & mention() conflict with Cls.insts map.
				id = usrInst == null ? 0 : usrInst.omention(); // LRU Cache

				// Next try creating new instance, and send to it.
				result_ur = instance.asObj().eval(
						fmt("self handle: (%s) query: (%s)",
								newStr(url).repr(), qDict.repr()));
			}
			result = result_ur.asDict();
			if (result == null) {
				toss("Sending <handle:query:> to instance of <%s> did not return a Dict: <%s>",
						appName, result_ur);
			}
			result.dict.put(newStr("id"), newStr(Integer.toString(id)));
			long after = tickCounter;
			long nanosAfter = System.nanoTime();
			result.dict.put(newStr("ticks"), newNum(before - after));
			result.dict.put(newStr("nanos"), newNum(nanosAfter - nanosBefore));
			say("<handle:query:> used %d ticks and %.3f secs.", before - after,
					(double) (nanosAfter - nanosBefore) / 1000000000.0);

		} catch (Exception ex) {
			ex.printStackTrace();
			StringBuffer sb = new StringBuffer(ex.toString());
			StackTraceElement[] elems = ex.getStackTrace();
			for (StackTraceElement e : elems) {
				sb.append("\n  * ");
				sb.append(e.toString());
			}
			Ur[] dict_arr = urs(
					new Vec(this, urs(newStr("type"), newStr("text"))),
					new Vec(this, urs(newStr("title"), newStr(ex.toString()))),
					new Vec(this, urs(newStr("value"), newStr(sb.toString()))));
			result = newDict(dict_arr);
		} catch (TooManyTicks err) {
			err.printStackTrace();
			String s = fmt("TOO_MANY_TICKS_IN_handleUrl <%s> qdict <%s>", url, qDict);
			Ur[] dict_arr = urs(
					new Vec(this, urs(newStr("type"), newStr("text"))),
					new Vec(this, urs(newStr("title"), newStr(err.toString()))),
					new Vec(this, urs(newStr("value"), newStr(s))));
			result = newDict(dict_arr);
		} catch (Error err) {
			err.printStackTrace();
			Ur[] dict_arr = urs(
					new Vec(this, urs(newStr("type"), newStr("text"))),
					new Vec(this, urs(newStr("title"), newStr(err.toString()))),
					new Vec(this, urs(newStr("value"), newStr(err.toString()))));
			result = newDict(dict_arr);
		}
		return result;
	}

	static Pattern INITIAL_WHITE = Pattern.compile("^\\s");

	public abstract class WorldReader {
		protected int lineNum;
		protected String world;
		
		public WorldReader(String world) {
			this.world = world;
		}

		abstract public void doCls(String[] words, String more);

		abstract public void doVars(String[] words, String more);

		abstract public void doMeth(String[] words, String more);

		abstract public void doEquals(String[] words, String more);

		abstract public void doInst(String[] words, String more);

		public Exception loadFile(String initFilename) {
			try {
				say("LOADING FILE <%s> for world <%s>", initFilename, worldName);
				FileInputStream fis = openFileRead(initFilename);
				loadReader(new InputStreamReader(fis));
				discoverAllMethodNames();
			} catch (IOException ex) {
				ex.printStackTrace();
				say("ERROR Loading <%s> at line %d:  %s", initFilename, lineNum, ex);
				return ex;
			} catch (RuntimeException ex) {
				ex.printStackTrace();
				say("ERROR Loading <%s> at line %d:  %s", initFilename, lineNum, ex);
				return ex;
			}
			return null;
		}

		public void loadReader(InputStreamReader isr) throws IOException {
			BufferedReader br = new BufferedReader(isr);
			loadingWorldFile = true;
			lineNum = 0;
			try {
				String line = br.readLine();
				++lineNum;
				while (true) {
					// Break at EOF.
					if (line == null)
						break;
					// Skip blank lines.
					String trimmed = line.trim();
					if (trimmed.length() == 0) {
						line = br.readLine();
						++lineNum;
						continue;
					}
					// Skip comments.
					if (line.charAt(0) == '#') {
						line = br.readLine();
						++lineNum;
						continue;
					}

					char c = line.charAt(0);
					if (c != '(' && c != ')') {
						if (line.charAt(0) < 'a' || line.charAt(0) > 'z') {
							toss("loadInitFile: Should have started with some lowercase letter a-z: <%s>",
									line);
						}
					}
					
					say("COMMAND: %s", line);

					// Store the command line in words[] and command.
					String[] words = line.trim().split("\\s+");
					if (words.length < 1) {
						line = br.readLine();
						++lineNum;
						continue;
					}
					// Recognize new timestamped format with "(".
					if (words[0].equals("(") && words.length > 2) {
						// Skip two words: the "("or ")", and a timestamp that
						// follows
						// it.
						// words = Arrays.copyOfRange(words, 2, words.length);
						String[] new_words = new String[words.length - 2];
						System.arraycopy(words, 2, new_words, 0,
								words.length - 2);
						words = new_words;
					}
					// Recognize new timestamped format ending with ")" line.
					if (words[0].equals(")")) {
						line = br.readLine();
						++lineNum;
						continue;
					}

					String command = words[0];

					// Now slurp any lines beginning with white space into more.
					StringBuffer sb = new StringBuffer();
					line = br.readLine();
					++lineNum;
					while (line != null
							&& (INITIAL_WHITE.matcher(line).lookingAt() || line
									.length() == 0)) {
						sb.append(line.length() > 0 ? line.substring(1) : "");
						sb.append("\n");
						line = br.readLine();
						++lineNum;
					}
					// Keep line for next time through big loop.
					String more = sb.toString();
					//say("MORE: len=%s", more.length());

					try {
						int ca0 = command.charAt(0);
						say("Char At 0 == %d; cmd=%s; words=%s", ca0, command, mkStrVec(words));
						if (command.charAt(0) == ')') {
							// NOP.
						} else if (command.equals("meth")) {
							doMeth(words, more);
						} else if (command.equals("vars")) {
							doVars(words, more);
						} else if (command.equals("instvars")) { // Archaic spelling.
							doVars(words, more);
						} else if (command.equals("inst")) {
							doInst(words, more);
						} else if (command.equals("cls")) {
							doCls(words, more);
						} else if (command.equals("class")) {  // Archaic spelling.
							doCls(words, more);
						} else if (command.equals("equals")) {
							doEquals(words, more);
						} else if (command.equals("stop")) {
							break; // For testing partial files.
						} else {
							toss("loadInitFile: Unknown command: <%s>", mkStrVec(words));
						}
					} catch (RuntimeException ex) {
						ex.printStackTrace();
						toss("Loading at line %d:  %s", lineNum, ex);
					}
				}
			} finally {
				loadingWorldFile = false;
			}
		}
	}

	public class InitialWorldReader extends WorldReader {

		public InitialWorldReader(String world) {
			super(world);
		}

		@Override
		public void doCls(String[] words, String more) {
			String className = words[1];
			String superName = words[2];
			Cls clsObj = clss.get(className.toLowerCase());
			Cls supObj = clss.get(superName.toLowerCase());
			if (more.trim().length() > 0) {
				toss("Was not expecting more: <%s>");
			}
			if (supObj == null) {
				toss("loadInitFile: Cannot define subclass <%s> of <%s>: class <%s> does not exist.",
						className, superName, superName);
			}

			if (clsObj == null && supObj != null) {
				// We can create the class.
				supObj.defineSubclass(className);
			} else {
				toss("loadInitFile: Cannot define subclass <%s> of <%s>",
						className, superName);
			}
		}

		@Override
		public void doVars(String[] words, String more) {
			String className = words[1];
			Cls cls = clss.get(className.toLowerCase());
			if (cls == null) {
				cls = defineOrphan(className);
			}
			cls.defVars_(more);
		}

		@Override
		public void doMeth(String[] words, String more) {
			String className = words[1];
			String methodName = words[2];

			Cls cls = clss.get(className.toLowerCase());
			if (cls == null) {
				cls = defineOrphan(className);
			}
			UsrMeth um = new UsrMeth(cls, methodName, "", "", more, null);
			cls.meths.put(methodName.toLowerCase(), um);

		}

		@Override
		public void doEquals(String[] words, String more) {
			String expected = "";
			for (int i = 1; i < words.length; i++) {
				expected += words[i] + " ";
			}
			Ur p1 = newTmp().eval(expected);
			Ur p2 = newTmp().eval(more);
			if (!p1.equals(p2)) {
				toss("loadInitFile: eq check failed: line %d: <%s> --> <%s> but <%s> --> <%s>",
						lineNum, expected, p1, more, p2);
			}
		}

		@Override
		public void doInst(String[] words, String more) {
			String className = words[1];
			String instName = words[2];
			Matcher match_nww = Usr.NAME_WITH_WORLD.matcher(instName);
			if (match_nww.lookingAt()) {
				// Already has the world on it.
			} else {
				instName += "_" + world;
			}
			
			UsrCls usrCls = (UsrCls) clss.get(className.toLowerCase());
			if (usrCls == null) {
				usrCls = defineOrphan(className);
			}
			usrCls.savedInstsUnrealized.put(instName, more);
		}

	}

	void appendWorldFile(String firstLine, String[] rest) throws IOException {
		if (loadingWorldFile) {
			return; // Don't cause infinite loop!
		}
		if (worldFilename.length() == 0) {
			return; // For unit tests.
		}
		long timestamp = System.currentTimeMillis() / 1000;
		FileOutputStream fos = openFileAppend(worldFilename);
		PrintStream ps = new PrintStream(fos);
		ps.println(fmt("( %d %s", timestamp, firstLine));
		if (rest != null) {
			for (String s : rest) {
				ps.println(" " + s); // Stuff 1 space before each line.
			}
		}
		ps.println(fmt(") %d", timestamp));
		ps.flush();
		fos.flush();
		fos.close();
	}

	public UsrCls defineOrphan(String className) {
		UsrCls orphan = (UsrCls) clss.get("orphan");
		say("COULD BE A PROBLEM:  Defining Orphaned Class <%s>", className);
		return (UsrCls) orphan.defineSubclass(className);
	}
	public Ur toss(String s, Object... objects) {
		if (expectingTerseException > 0) {
			throw new TerseExpectedException(s, objects);
		} else {
			throw new TerseException(s, objects);
		}
	}

	public Ur retoss(String s, Object... objects) {
		if (expectingTerseException > 0) {
			throw new TerseExpectedRetossException(s, objects);
		} else {
			throw new TerseRetossException(s, objects);
		}
	}

	public Ur tossNotUnderstood(Cls c, String msg) {
		return toss("Message <%s> not understood by class <%s>", msg, c.cname);
	}

	public void checkTxtFileNameSyntax(String filename) {
		if (!TXTFILE_P.matcher(filename).matches()) {
			toss("Bad filename, should match <%s> : <%s>", TXTFILE_P, filename);
		}
	}

	public Num boolObj(boolean x) {
		return x ?instTrue : instFalse;
	}

	public abstract String say(String s, Object... objects);

	public abstract void loadPrelude() throws IOException;

	public abstract FileInputStream openFileRead(String filename)
			throws FileNotFoundException;

	public abstract FileOutputStream openFileWrite(String filename)
			throws FileNotFoundException;

	public abstract FileOutputStream openFileAppend(String filename)
			throws FileNotFoundException;

	public abstract File getFilesDir();
	
	public abstract boolean deleteFile(String filename);

	public abstract Vec listOfWebFiles();

	public abstract void pushToWeb(String filename, String content);

	public abstract String pullFromWeb(String filename);

	// DRAW ABSTRACTIONS
	public interface IInk {
		void setColor(int rgbDecimal);

		void setFont(String name);

		void setFontSize(int scaledPoints);

		void setThickness(int pixels);
	}

	public interface ICanv {
		void drawLine(IInk k, float x1, float y1, float x2, float y2);

		void drawRect(IInk k, float x1, float y1, float x2, float y2);

		void drawText(IInk k, float x, float y);
	}

	public static class PosixTerp extends Terp {

		protected PosixTerp(boolean loadPrelude, String imageName)
				throws IOException {
			super(loadPrelude, imageName);
		}

		@Override
		public String say(String s, Object... objects) {
			String msg = fmt(s, objects);
			System.err.println(msg);
			recordLog(msg);
			return msg;
		}

		@Override
		public void loadPrelude() throws IOException {
			new InitialWorldReader("pre0").loadFile("prelude.txt");
		}

		@Override
		public FileInputStream openFileRead(String filename)
				throws FileNotFoundException {
			return new FileInputStream(filename);
		}

		@Override
		public FileOutputStream openFileWrite(String filename)
				throws FileNotFoundException {
			return new FileOutputStream(filename, false);
		}

		@Override
		public FileOutputStream openFileAppend(String filename)
				throws FileNotFoundException {
			return new FileOutputStream(filename, true);
		}

		@Override
		public File getFilesDir() {
			return new File(".");
		}

		@Override
		public void pushToWeb(String filename, String content) {
			// import org.apache.http.impl.client.DefaultHttpClient;
			toss("Not Implemented in PosixTerp: pushToWeb");
		}

		@Override
		public String pullFromWeb(String filename) {
			// import org.apache.http.impl.client.DefaultHttpClient;
			toss("Not Implemented in PosixTerp: pullFromWeb");
			return null;
		}

		@Override
		public Vec listOfWebFiles() {
			toss("Not Implemented in PosixTerp: listOfWebFiles");
			return null;
		}

		@Override
		public boolean deleteFile(String filename) {
			File f = new File(filename);
			return f.delete();
			// TODO: some regexp.
		}
	}

	public interface Factory {
		Terp createTerp(boolean loadPrelude, String imageName)
				throws IOException;
	}

	void discoverAllMethodNames() {
		allMethodNames.clear();
		for (String clsName : clss.keySet()) {
			Cls c = clss.get(clsName);
			for (String methName : c.meths.keySet()) {
				int numColons = 0;
				for (int i = 0; i < methName.length(); i++) {
					if (methName.charAt(i) == ':')
						++numColons;
				}
				allMethodNames.put(methName, numColons);
			}
		}
	}
	

	private class TerseBaseException extends RuntimeException {
		private static final long serialVersionUID = 1L;
		protected String msg;
		protected Object[] args;
		protected boolean retoss;

		public TerseBaseException(boolean retoss, String format,
				Object... args) {
			this.retoss = retoss;
			this.msg = "?????";
			this.args = args;

			String[] argstrs = new String[args.length];
			for (int i = 0; i < args.length; i++) {
				try {
					argstrs[i] = fmt("<%s:%s>", args[i].getClass().getName(),
							args[i].toString());
				} catch (RuntimeException _) {
					argstrs[i] = "???";
				}
			}
			this.msg = fmt(format, (Object[]) /*argstrs*/ args);
		}

		@Override
		public String toString() {
			return fmt(msg, args);
		}
	}
	private class TerseException extends TerseBaseException {
		private static final long serialVersionUID = 1L;

		public TerseException(String format,
				Object[] args) {
			super(false, format, args);
			say("TerseException(%s)<<%s>>", retoss ? "retoss" : "", msg);
			if (retoss) {
				breakHere("retoss");
			} else {
				breakHere("toss");
			}
		}
	}
	private class TerseRetossException extends TerseBaseException {
		private static final long serialVersionUID = 1L;

		public TerseRetossException(String format,
				Object[] args) {
			super(true, format, args);
			say("TerseException(%s)<<%s>>", retoss ? "retoss" : "", msg);
			if (retoss) {
				breakHere("retoss");
			} else {
				breakHere("toss");
			}
		}
	}
	private class TerseExpectedException extends TerseBaseException {
		private static final long serialVersionUID = 1L;

		public TerseExpectedException(String format,
				Object[] args) {
			super(false, format, args);
			say("TerseEXPECTEDException(%s)<<%s>>", retoss ? "retoss" : "", msg);
			if (retoss) {
				breakHere("expected retoss");
			} else {
				breakHere("expected toss");
			}
		}
	}
	private class TerseExpectedRetossException extends TerseBaseException {
		private static final long serialVersionUID = 1L;

		public TerseExpectedRetossException(String format,
				Object[] args) {
			super(true, format, args);
			say("TerseEXPECTEDException(%s)<<%s>>", retoss ? "retoss" : "", msg);
			if (retoss) {
				breakHere("expected retoss");
			} else {
				breakHere("expected toss");
			}
		}
	}
}
