package yak;

import java.util.Hashtable;

import yak.Cls.JavMeth;
import yak.Cls.Meth;
import yak.Talk.Terp.Frame;

public class Pro extends Talk {
	Cls cls;

	public Pro(Cls cls) {
		this.cls = cls;
	}
	Num asNum() {
		return null;
	}
	Str asStr() {
		return null;
	}
	Cls asCls() {
		return null;
	}
	Exc asExc() {
		return null;
	}
	Err asErr() {
		return null;
	}
	Rtx asRtx() {
		return null;
	}
	Rtn asRtn() {
		return null;
	}
	Brk asBrk() {
		return null;
	}
	Blk asBlk() {
		return null;
	}
	Nil asNil() {
		return null;
	}
	Boolean asBool() {
		return null;
	}

	static Hashtable<String, Meth> builtinObjMethods(Terp t) {
		Hashtable<String, Meth> z = new Hashtable<String, Meth>();
		z.put("cls", new JavMeth(t.tJavMeth) {
			public Pro eval(Frame f, Pro r, Pro[] args) {
				return r.cls;
			}
		});
		return z;
	}

	public static abstract class Obj extends Pro {  // abstarct Exception
		public Obj(Cls cls) {
			super(cls);
		}
		Obj asObj() { return this; }
	}
	public static abstract class Exc extends Pro {  // abstract Exception
		Pro guts;
		public Exc(Cls cls, Pro guts) {
			super(cls);
			this.guts = guts;
		}
		Exc asExc() { return this; }
	}
	public static class Err extends Exc {  // Error Exception
		public Err(Cls cls, Pro guts) {
			super(cls, guts);
		}
		Err asErr() { return this; }
		public String toString() { return fmt("Err({0})", guts); }
	}
	public static class Rtx extends Err {  // Java Runtime Exception Error
		public Rtx(Cls cls, Pro guts) {
			super(cls, guts);
		}
		Rtx asRtx() { return this; }
		public String toString() { return fmt("Rtx({0})", guts); }
	}
	public static class Rtn extends Exc {  // Return Exception
		public Rtn(Cls cls, Pro guts) {
			super(cls, guts);
		}
		Rtn asRtn() { return this; }
		public String toString() { return fmt("Rtn({0})", guts); }
	}
	public static class Brk extends Exc {  // Break Exception
		public Brk(Cls cls, Pro guts) {
			super(cls, guts);
		}
		Brk asBrk() { return this; }
		public String toString() { return fmt("Brk({0})", guts); }
	}
	public Err xErr(Pro guts) { return new Err(cls.terp.tErr, guts); }
	public Err xRtx(Pro guts) { return new Err(cls.terp.tRtx, guts); }
	public Err xRtn(Pro guts) { return new Err(cls.terp.tRtn, guts); }
	public Err xBrk(Pro guts) { return new Err(cls.terp.tBrk, guts); }
	

	public static class Nil extends Obj {  // Nil
		public Nil(Cls cls) {
			super(cls);
		}
		Nil asNil() { return this; }
		public String toString() { return "/N"; }
	}
	public static class Fal extends Obj {  // False
		public Fal(Cls cls) {
			super(cls);
		}
		Boolean asBool() { return Boolean.FALSE; }
		public String toString() { return "/F"; }
		static Hashtable<String, Meth> builtinFalMethods(Terp t) {
			Hashtable<String, Meth> z = new Hashtable<String, Meth>();
			z.put("no:", new JavMeth(t.tJavMeth) {
				public Pro eval(Frame f, Pro r, Pro[] args) {
					assert args.length == 1;
					Blk blk = args[0].asBlk();
					// TODO: Create child frame.
					return blk.body.eval(f);
				}
			});
			z.put("ye:", new JavMeth(t.tJavMeth) {
				public Pro eval(Frame f, Pro r, Pro[] args) {
					// Don't eval the block.  Return Nil.
					return f.terp().instNil;
				}
			});
			return z;
		}
	}
	public static class Tru extends Obj {  // True
		public Tru(Cls cls) {
			super(cls);
		}
		Boolean asBool() { return Boolean.TRUE; }
		public String toString() { return "/T"; }
		static Hashtable<String, Meth> builtinTruMethods(Terp t) {
			Hashtable<String, Meth> z = new Hashtable<String, Meth>();
			z.put("ye:", new JavMeth(t.tJavMeth) {
				public Pro eval(Frame f, Pro r, Pro[] args) {
					assert args.length == 1;
					Blk blk = args[0].asBlk();
					// TODO: Create child frame.
					return blk.body.eval(f);
				}
			});
			z.put("no:", new JavMeth(t.tJavMeth) {
				public Pro eval(Frame f, Pro r, Pro[] args) {
					// Don't eval the block.  Return Nil.
					return f.terp().instNil;
				}
			});
			return z;
		}
	}
	public static class Blk extends Obj {
		Expr body;
		String params[];
		Frame parent;
		public Blk(Cls cls, Expr body, String params[], Frame parent) {
			super(cls);
			this.body = body;
			this.params = params;
			this.parent = parent;
		}
		Blk asBlk() { return this; }
		public String toString() { return fmt("Blk({0}, {1})", params, body); }

		static Hashtable<String, Meth> builtinBlkMethods(Terp t) {
			Hashtable<String, Meth> z = new Hashtable<String, Meth>();
			z.put("run", new JavMeth(t.tJavMeth) {
				public Pro eval(Frame f, Pro r, Pro[] args) {
					Blk blk = r.asBlk();
					// TODO: Create child frame.
					return blk.body.eval(f);
				}
			});
			return z;
		}
	}
	
	public static class Usr extends Obj {
//		Pro[] vars;
		public Usr(Cls cls) {
			super(cls);
			// Member variables p..z
//			this.vars = new Pro['z'-'p'+1];
		}
//		public void setVar(char var, Pro value) {
//			int i = var - 'p';
//			vars[i] = value;
//		}
//		public Pro getVar(char var) {
//			int i = var - 'p';
//			return vars[i];
//		}

		static Hashtable<String, Meth> builtinUsrClsMethods(Terp t) {
			Hashtable<String, Meth> z = new Hashtable<String, Meth>();
			z.put("new", new JavMeth(t.tJavMeth) {
				public Usr eval(Frame f, Pro r, Pro[] args) {
					return new Usr(r.asCls());
				}
			});
			return z;
		}
	}

	public static class Num extends Obj {
		double num;

		Num(Terp t, double num) {
			super(t.tNum);
			this.num = num;
		}
		Num asNum() {
			return this;
		}
		public String toString() { return fmt("Num({0})", num); }

		abstract static class BinaryNumMeth extends JavMeth {
			BinaryNumMeth(Cls cls) {
				super(cls);
			}
			public Pro eval(Frame f, Pro r, Pro[] args) {
				assert args.length == 1;
				Num s = r.asNum();
				Num a = args[0].asNum();
				if (s == null) {
					toss("Receiver of Num::pl not a Num");
				}
				if (a == null) {
					toss("Argument of Num::pl not a Num");
				}
				return new Num(f.terp(), compute(s.num, a.num));
			}
			abstract double compute(double snum, double anum);
		}
		abstract static class UnaryNumMeth extends JavMeth {
			UnaryNumMeth(Cls cls) {
				super(cls);
			}
			public Pro eval(Frame f, Pro r, Pro[] args) {
				assert args.length == 0;
				Num s = r.asNum();
				if (s == null) {
					toss("Receiver of Num::pl not a Num");
				}
				return new Num(f.terp(), compute(s.num));
			}
			abstract double compute(double anum);
		}

		static Hashtable<String, Meth> builtinNumMethods(Terp t) {
			Hashtable<String, Meth> z = new Hashtable<String, Meth>();
			z.put("pl:", new BinaryNumMeth(t.tJavMeth) {
				double compute(double snum, double anum) {
					return snum + anum;
				}
			});
			z.put("mi:", new BinaryNumMeth(t.tJavMeth) {
				double compute(double snum, double anum) {
					return snum - anum;
				}
			});
			z.put("ti:", new BinaryNumMeth(t.tJavMeth) {
				double compute(double snum, double anum) {
					return snum * anum;
				}
			});
			z.put("fdiv:", new BinaryNumMeth(t.tJavMeth) {
				double compute(double snum, double anum) {
					return snum / anum;
				}
			});
			z.put("idiv:", new BinaryNumMeth(t.tJavMeth) {
				double compute(double snum, double anum) {
					return (long) snum / (long) anum;
				}
			});
			z.put("imod:", new BinaryNumMeth(t.tJavMeth) {
				double compute(double snum, double anum) {
					return (long) snum % (long) anum;
				}
			});
			z.put("neg", new UnaryNumMeth(t.tJavMeth) {
				double compute(double snum) {
					return -snum;
				}
			});
			z.put("sgn", new UnaryNumMeth(t.tJavMeth) {
				double compute(double snum) {
					return snum < 0 ? -1 : snum > 0 ? 1 : 0;
				}
			});
			z.put("int", new UnaryNumMeth(t.tJavMeth) {
				double compute(double snum) {
					return (long) snum;
				}
			});
			return z;
		}
	}

	public static class Str extends Obj {
		String str;

		Str(Terp t, String str) {
			super(t.tStr);
			this.str = str;
		}
		Str asStr() {
			return this;
		}
		public String toString() { return fmt("Str(\"{0}\")", str); }
	}

}
