package yak;

import java.text.MessageFormat;
import java.util.Hashtable;

import yak.Pro.Num;

public class Talk {
	public static class TalkException extends RuntimeException {
		private static final long serialVersionUID = 1L;
		String msg;
		Object[] args;

		public TalkException(String s, Object... args) {
			this.msg = s;
			this.args = args;
		}

		@Override
		public String toString() {
			return fmt(msg, args);
		}
	}
	static void say(String s, Object... objects) {
		System.out.println(fmt(s, objects));
	}
	static Pro toss(String s, Object... objects) {
		throw new TalkException(s, objects);
	}
	static Pro tossNotUnderstood(Cls c, String msg) {
		throw new TalkException(
				"Message `{0}' not understood by class `{1}'",
				msg, c.name);
	}
	static String fmt(String s, Object... objects) {
		return MessageFormat.format(s, objects);
	}

	public static class Terp {
		Frame glob;
		Cls tPro;
		Cls tProCls;
		Cls tObj;
		Cls tObjCls;
		Cls tCls;
		Cls tClsCls;
		Cls tMetacls;
		Cls tMetaclsCls;
		Cls tMeth;
		Cls tMethCls;
		Cls tJavMeth;
		Cls tJavMethCls;
		Cls tUsrMeth;
		Cls tUsrMethCls;
		Cls tNum;
		Cls tNumCls;
		Cls tStr;
		Cls tStrCls;
		Cls tUsr;
		Cls tUsrCls;
		Cls tExc;
		Cls tExcCls;
		Cls tErr;
		Cls tErrCls;
		Cls tRtx;
		Cls tRtxCls;
		Cls tRtn;
		Cls tRtnCls;
		Cls tBrk;
		Cls tBrkCls;
		Cls tFal;
		Cls tFalCls;
		Cls tTru;
		Cls tTruCls;
		Cls tBlk;
		Cls tBlkCls;

		public Terp() {
			this.glob = new Frame(null, null);
			this.tProCls = new Cls(null/*Metacls*/, this, "ProCls", null/*Cls*/);
			this.tPro = new Cls(tProCls, this, "Pro", null/*Pro has no super*/);

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

			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.tProCls.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.tObjCls.supercls = tCls;

			this.tMethCls = new Cls(tMetaclsCls, this, "MethCls", tObjCls);
			this.tMeth = new Cls(tMethCls, this, "Meth", tObj);
			this.tJavMethCls = new Cls(tMetaclsCls, this, "JavMethCls", tObjCls);
			this.tJavMeth = new Cls(tJavMethCls, this, "JavMeth", tObj);
			this.tUsrMethCls = new Cls(tMetaclsCls, this, "UsrMethCls", tObjCls);
			this.tUsrMeth = new Cls(tUsrMethCls, this, "UsrMeth", tObj);
			this.tNumCls = new Cls(tMetaclsCls, this, "NumCls", tObjCls);
			this.tNum = new Cls(tNumCls, this, "Num", tObj);
			this.tStrCls = new Cls(tMetaclsCls, this, "StrCls", tObjCls);
			this.tStr = new Cls(tStrCls, this, "Str", tObj);
			this.tExcCls = new Cls(tMetaclsCls, this, "ExcCls", tObjCls);
			this.tExc = new Cls(tExcCls, this, "Exc", tObj);
			this.tErrCls = new Cls(tMetaclsCls, this, "ErrCls", tObjCls);
			this.tErr = new Cls(tErrCls, this, "Err", tObj);
			this.tRtxCls = new Cls(tMetaclsCls, this, "RtxCls", tObjCls);
			this.tRtx = new Cls(tRtxCls, this, "Rtx", tObj);
			this.tRtnCls = new Cls(tMetaclsCls, this, "RtnCls", tObjCls);
			this.tRtn = new Cls(tRtnCls, this, "Rtn", tObj);
			this.tBrkCls = new Cls(tMetaclsCls, this, "BrkCls", tObjCls);
			this.tBrk = new Cls(tBrkCls, this, "Brk", tObj);
			this.tFalCls = new Cls(tMetaclsCls, this, "FalCls", tObjCls);
			this.tFal = new Cls(tFalCls, this, "Fal", tObj);
			this.tTruCls = new Cls(tMetaclsCls, this, "TruCls", tObjCls);
			this.tTru = new Cls(tTruCls, this, "Tru", tObj);
			this.tBlkCls = new Cls(tMetaclsCls, this, "BlkCls", tObjCls);
			this.tBlk = new Cls(tBlkCls, this, "Blk", tObj);

			this.tObj.meths = Pro.builtinObjMethods(this);
			this.tCls.meths = Cls.builtinClsMethods(this);
			this.tNum.meths = Num.builtinNumMethods(this);
		}

		Frame newFrame(Frame prev, Frame parent) {
			return new Frame(prev, parent);
		}

		public class Frame { // tied to a Terp
			Frame prev; // for debug
			Frame parent; // inherit from
			Hashtable<String, Pro> dict;

			Frame(Frame prev, Frame parent) {
				this.prev = prev;
				this.parent = parent;
				this.dict = new Hashtable<String, Pro>();
			}

			Pro get(String key) {
				for (Frame f = this; f != null; f = f.parent) {
					Pro value = f.dict.get(key);
					if (value != null) {
						return value;
					}
				}
				return toss("No variable `{0}' in {1} Frame.", key,
						this == glob ? "Global" : "Local");
			}

			void put(String key, Pro value) {
				for (Frame f = this; f != null; f = f.parent) {
					if (f.dict.containsKey(key)) {
						f.dict.put(key, value);
						return;
					}
				}
				// Only create new binding when not found in parents.
				this.dict.put(key, value);
			}
			Frame globals() { return glob; }
			Terp terp() { return getTerp(); }
		}

		Terp getTerp() { return this; }
	}
}