package yak;

import yak.Cls.Meth;
import yak.Pro.Blk;
import yak.Talk.Terp.Frame;

public abstract class Expr extends Talk {

	abstract Pro eval(Frame f);

	static String arrayToString(Expr[] a) {
		String z = "[ ";
		for (Expr e : a) {
			z += fmt("{0}, ", e);
		}
		return z + "]";
	}
	static String arrayToString(String[] a) {
		String z = "[ ";
		for (String e : a) {
			z += fmt("\"{0}\", ", e);
		}
		return z + "]";
	}

	static class GetLocalVar extends Expr {
		String key;

		GetLocalVar(String key) {
			this.key = key;
		}
		Pro eval(Frame f) {
			if (Character.isUpperCase(key.charAt(0))) {
				return f.globals().get(key);
			} else {
				return f.get(key);
			}
		}
		public String toString() { return fmt("GetLocalVar({0})", key); }
	}

	static class GetGlobalVar extends Expr {
		String key;

		GetGlobalVar(String key) {
			this.key = key;
		}
		Pro eval(Frame f) {
			if (Character.isUpperCase(key.charAt(0))) {
				return f.globals().get(key);
			} else {
				return f.get(key);
			}
		}
		public String toString() { return fmt("GetGlobalVar({0})", key); }
	}

	static class PutLocalVar extends Expr {
		String key;
		Expr expr;

		PutLocalVar(String key, Expr expr) {
			this.key = key;
			this.expr = expr;
		}
		Pro eval(Frame f) {
			Pro value = expr.eval(f);
			f.put(key, value);
			return value;
		}
		public String toString() { return fmt("PutLocalVar({0})<-({1})", key, expr); }
	}

	static class PutGlobalVar extends Expr {
		String key;
		Expr expr;

		PutGlobalVar(String key, Expr expr) {
			this.key = key;
			this.expr = expr;
		}
		Pro eval(Frame f) {
			Pro value = expr.eval(f);
			f.globals().put(key, value);
			return value;
		}
		public String toString() { return fmt("PutGlobalVar({0})<-({1})", key, expr); }
	}

	static class Send extends Expr {
		Expr rcvr;
		String msg;
		Expr args[];

		Send(Expr rcvr, String msg, Expr args[]) {
			this.rcvr = rcvr;
			this.msg = msg;
			this.args = args;
		}

		private Meth findMeth(Pro r) {
			for (Cls c = r.cls; c != null; c = c.supercls) {
				Meth m = c.meths.get(msg);
				if (m != null)
					return m;
			}
			toss("MessageNotUnderstood: msg {0} on class {1}", msg, r.cls.name);
			return null;
		}

		@Override
		Pro eval(Frame f) {
			Pro r = rcvr.eval(f);
			Pro a[] = new Pro[args.length];
			for (int i = 0; i < args.length; i++) {
				a[i] = args[i].eval(f);
			}
			Meth m = findMeth(r);
			return m.eval(f, r, a);
		}
		public String toString() { return fmt("Send({0}){1}({2})", rcvr, msg, arrayToString(args)); }
	}

	static class Block extends Expr {
		Expr body;
		String[] params;

		public Block(Expr body, String[] params) {
			this.body = body;
			this.params = params;
		}
		@Override
		Pro eval(Frame f) {
			return new Blk(f.terp().tBlk, body, params, f);
		}
		public String toString() { return fmt("Block[ |{0}| {1}]", arrayToString(params), body); }
	}

	static class Seq extends Expr {
		Expr[] body;

		public Seq(Expr[] body) {
			this.body = body;
		}
		Pro eval(Frame f) {
			Pro z = null;
			for (Expr expr : body) {
				z = expr.eval(f);
				if (z.asExc() != null)
					return z;
			}
			return z;
		}
		public String toString() { return fmt("Seq{0}", arrayToString(body)); }
	}

	static class Lit extends Expr {
		Pro value;

		public Lit(Pro value) {
			this.value = value;
		}
		Pro eval(Frame f) {
			return value;
		}
		public String toString() { return fmt("Lit({0})", value); }
	}
}
