/*
 * Copyright 2009 Yuichiro Moriguchi
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.morilib.lisp.nano;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;
import java.util.logging.Logger;

import net.morilib.lisp.nano.CompiledCode.Oper;
import net.morilib.lisp.nano.util.LogEnv;

/**
 * 
 *
 *
 * @author MORIGUCHI, Yuichiro 2009
 */
/*package*/ class CodeExecutorImpl implements CodeExecutor {

	//
	private static Logger _log = LogEnv.init("schlush.vm");

	//
	private LispMessage message;

	//
	/*package*/ CodeExecutorImpl(LispMessage msg) {
		message = msg;
	}

	private /*static*/ class Memento implements IntStack {

		private Stack<Integer>      addrStk    = new Stack<Integer>();
		private Stack<CompiledCode> codeStk    = new Stack<CompiledCode>();
		private Stack<Environment>  envStk     = new Stack<Environment>();
		private Stack<Datum>        dataStk    = new Stack<Datum>();
		private Stack<ConsListBuilder> workStk = new Stack<ConsListBuilder>();
		private Stack<Datum>        callerStk  = new Stack<Datum>();

		// for delay, force
		private Stack<Promise>      memoStk   = new Stack<Promise>();
		private CompiledCode aftret = new CompiledCode();

		private Memento() {
			//do nothing
		}

		public Memento copy() {
			Memento r = new Memento();

			r.addrStk.addAll(addrStk);
			r.codeStk.addAll(codeStk);
			r.dataStk.addAll(dataStk);
			r.callerStk.addAll(callerStk);
			r.memoStk.addAll(memoStk);
			for(ConsListBuilder l : workStk) {
				r.workStk.add(new ConsListBuilder(l, message));
			}

			// 環境はコピーしなければダメ
			for(Environment e : envStk) {
				r.envStk.add(e.copyNotRoot());
			}
			return r;
		}

		private void overwrite(Memento m) {
			addrStk = m.addrStk;
			codeStk = m.codeStk;
			envStk = m.envStk;
			dataStk = m.dataStk;
			workStk = m.workStk;
			callerStk = m.callerStk;
			memoStk = m.memoStk;
		}

		private void push(int addr, CompiledCode code, Environment env,
				Datum callee) {
			addrStk.push(addr);
			codeStk.push(code);
			envStk.push(env);
			callerStk.push(callee);
			memoStk.push(null);
		}

		private void push(int addr, CompiledCode code, Environment env,
				Promise p) {
			addrStk.push(addr);
			codeStk.push(code);
			envStk.push(env);
			callerStk.push(p);
			memoStk.push(p);
		}

		public String toString() {
			StringBuilder bld = new StringBuilder();

			bld.append(addrStk.size()).append(" , ");
			bld.append(codeStk.size()).append(" , ");
			bld.append(envStk.size()).append(" , ");
			bld.append(dataStk.size()).append(" , ");
			bld.append(workStk.size()).append(" , ");
			bld.append(callerStk.size()).append(" , ");
			bld.append(memoStk.size()).append(" , ");
			bld.append(addrStk).append("\n");
			bld.append(envStk).append("\n");
			bld.append(dataStk).append("\n");
			bld.append(workStk).append("\n");
			bld.append(callerStk).append("\n");
			bld.append(memoStk).append("\n");
			return bld.toString();
		}

		private void desc(StringBuilder buf, Datum d) {
			if(d instanceof Subr) {
				Subr s = (Subr)d;

				if(s.symbolName != null &&
						!s.symbolName.equals("error")) {
					buf.append("  -");
					buf.append(message.get("err.stacktrace.subr"));
					buf.append(" ");
					buf.append(s.symbolName);
					buf.append("\n");
				}
			} else if(d instanceof Closure) {
				Closure c = (Closure)d;

				if(c.getName() != null) {
					buf.append("  -");
					buf.append(message.get("err.stacktrace.closure"));
					buf.append(" ");
					buf.append(c.getName());
					buf.append("\n");
				}
			} else if(d instanceof Promise) {
				Promise p = (Promise)d;

				buf.append("  -");
				buf.append(message.get("err.stacktrace.promise"));
				buf.append(" ");
				buf.append(Integer.toString(p.hashCode(), 16));
				buf.append("\n");
			} else {
				buf.append("  -");
				buf.append(LispUtils.toDisplay(d));
				buf.append("\n");
			}
		}

		public String getStackTrace() {
			StringBuilder buf = new StringBuilder();

			for(int i = callerStk.size() - 1; i >= 0; i--) {
				desc(buf, callerStk.get(i));
			}
			return buf.toString();
		}

	}

	private Datum evalApplyValid(Datum body) {
		List<Datum> lst1 = LispUtils.consToList(body, message);
		if(lst1.size() < 2) {
			throw message.getError("err.argument");
		}

		return Undef.UNDEF;
	}

	private Datum evalApply(Datum body) {
		List<Datum> lst1 = LispUtils.consToList(body, message);
		List<Datum> arg  = new ArrayList<Datum>();
		List<Datum> elst;

		// 引数オブジェクトの取得
		for(int i = 0; i < lst1.size() - 1; i++) {
			arg.add(lst1.get(i));
		}

		// 残余引数の取得
		elst = LispUtils.consToList(lst1.get(lst1.size() - 1), message);
		arg.addAll(elst);

		return LispUtils.listToCons(arg);
	}


	private void callSubr(Subr sub, int addr,
			Environment env, Datum d3d, Memento m,
			Datum callee) {
		m.addrStk.push(addr);
		m.codeStk.push(null);
		m.envStk.push(env);
		m.callerStk.push(callee);
		m.memoStk.push(null);

		try {
			m.dataStk.push(sub.eval(d3d, env, message));
		} finally {
			m.addrStk.pop();
			m.codeStk.pop();
			m.envStk.pop();
			m.callerStk.pop();
			m.memoStk.pop();
		}
	}

	private void setContinuationArgs(List<Datum> lst, Memento m) {
		m.dataStk.push(MultiValues.newValues(lst));
	}

	private ClosureClass makeLoad(
			InputPort ipt,
			Environment env,
			Memento memento) {
		CompiledCode.Builder b = new CompiledCode.Builder();
		LispCompiler comp = CompilerFactory.getInstance(message);
		ClosureClass clc;
		Datum d;

		while((d = ipt.readS()) != EOFObject.EOF) {
			// compile
			comp.compile(d, env, b, true, new Cons(), true,
					new LinkedList<Cons>(), this, memento);
		}
		b.addPop();
		b.addPush(Undef.UNDEF);
		b.addReturnOp();
		clc = new ClosureClass(Nil.NIL, b.getCodeRef());

		return clc;
	}

	private Closure makeEval(
			Datum sexp,
			Environment env,
			Memento memento) {
		Datum d = sexp;
		CompiledCode.Builder b = new CompiledCode.Builder();
		LispCompiler comp = CompilerFactory.getInstance(message);
		ClosureClass clc;
		Closure      res;

		// compile
		comp.compile(d, env, b, true, new Cons(), true,
				new LinkedList<Cons>(), this, memento);
		b.addReturnOp();
		clc = new ClosureClass(Nil.NIL, b.getCodeRef());
		res = new Closure(clc, env);
		return res;
	}

	private Environment calltail0(
			CompiledCode.Code c,
			Memento m,
			Closure cl) {
		//
		CompiledCode.Builder bb = new CompiledCode.Builder();

		// tail recursion
		_log.finer(
				"Remove " + c.getRewind() +
				" frame(s) for tail call");

		bb.merge(m.aftret);
		for(int i = 0; i < c.getRewind(); i++) {
			m.envStk.pop();
			m.addrStk.pop();
			m.codeStk.pop();
			m.callerStk.pop();
			m.memoStk.pop();
		}
		m.aftret = bb.getCodeRef();

		Environment env = cl.getEnvironment().copyNotRoot();
		_log.finer(
				"Tail jump top:" +
				cl.printName() + ":dtstk(" +
				m.dataStk.size() + "):cdstk(" +
				m.codeStk.size() + ")");
		return env;
	}

	private void bind0(Environment env, Symbol sym, Datum dest) {
		if(dest instanceof NamableDatum) {
			((NamableDatum)dest).setName(sym.getName());
		}
		env.bindDatum(sym, dest);
	}

	private boolean set0(Environment env, Symbol sym, Datum dest) {
		if(dest instanceof NamableDatum) {
			((NamableDatum)dest).setName(sym.getName());
		}

		try {
			return env.setDatum(sym, dest);
		} catch (ReadOnlyException e) {
			throw message.getError("err.variablereadonly", sym);
		}
	}

	@SuppressWarnings("incomplete-switch")
	private Datum exec(
			CompiledCode   code1,
			Environment    env1,
			IntStack       memento,
			boolean        useCont) {
		int addr  = 0;
		Memento       m = (Memento)memento;
		CompiledCode  code = code1;
		Environment   env = env1;
		Environment   tenv = null;

		while(true) {
			try {
				CompiledCode.Code c;

				if((c = code.getCode(addr)) == null) {
					return Undef.UNDEF;
				}

				switch(c.getOp()) {
				case PUSH:
					Datum aaa = c.getDatum();

					if(aaa instanceof ClosureClass) {
						Closure ncl = new Closure(
								(ClosureClass)aaa, env);
						m.dataStk.push(ncl);
					} else {
						m.dataStk.push(aaa);
					}
					addr++;
					break;
				case POP:
					m.dataStk.pop();
					addr++;
					break;
				case BEGIN_LIST:
					m.workStk.push(new ConsListBuilder());
					addr++;
					break;
				case APPEND_LIST:
					Datum           d11 = m.dataStk.pop();
					ConsListBuilder d12 = m.workStk.peek();

					d12.append(d11);
					addr++;
					break;
				case APPEND_LIST_SPLICING:
					Datum           d13 = m.dataStk.pop();
					ConsListBuilder d14 = m.workStk.peek();

					d14.appendAll(d13, message);
					addr++;
					break;
				case APPEND_LIST_MULTI_VALUES:
					Datum           d15 = m.dataStk.pop();
					ConsListBuilder d16 = m.workStk.peek();

					d16.appendAll(
							LispUtils.listToCons(d15.getValues()),
							message);
					addr++;
					break;
				case END_LIST_DOT:
					Datum           d21 = m.dataStk.pop();
					ConsListBuilder d22 = m.workStk.pop();

					m.dataStk.push(d22.get(d21));
					addr++;
					break;
				case END_LIST:
					ConsListBuilder d23 = m.workStk.pop();

					m.dataStk.push(d23.get());
					addr++;
					break;
				case END_LIST_VECTOR:
					ConsListBuilder d24 = m.workStk.pop();

					m.dataStk.push(new LispVector(
							LispUtils.consToList(d24.get(), message)));
					addr++;
					break;
				case CALL:
				case CALL_TAIL:
					Datum d3d = m.dataStk.pop();
					Datum d3s = m.dataStk.pop();

					if(d3s instanceof Subr) {
						Subr sub = (Subr)d3s;
						Environment ex2;
						ClosureClass cl;

						ex2 = env.getGlobal();
						cl = sub.getClosureClass(ex2);
						if(cl == null) {
							// Subr is called with dynamic scope
							callSubr(sub, addr, env, d3d, m, d3s);
							addr++;
						} else {
							m.push(addr, code, env, d3s);

							env = new Environment(ex2);
							IntLispUtils.bindLocal(
									cl.getParameterList(),
									d3d, env, message);
							code = cl.getCode();
							addr = 0;
							//state = 0;
						}
					} else if(d3s instanceof Subr) {
						callSubr((Subr)d3s, addr, env, d3d, m, d3s);
						addr++;
					} else if(d3s instanceof Closure) {
						Closure cl;

						cl = (Closure)d3s;
						if(c.getOp().equals(Oper.CALL_TAIL)) {
							env = calltail0(c, m, cl);
						} else if(c.getOp().equals(Oper.CALL)) {
							m.push(addr, code, env, d3s);

							env = new Environment(cl.getEnvironment());
							_log.finer(
									"Call:" +
									cl.printName() + ":dtstk(" +
									m.dataStk.size() + "):cdstk(" +
									m.codeStk.size() + ")");
						}

						IntLispUtils.bindLocal(
								cl.getParameterList(),
								d3d, env, message);

						code = cl.getCode();
						addr = 0;
					} else if(d3s instanceof Continuation) {
						Continuation ct = (Continuation)d3s;
						List<Datum> lst =
							LispUtils.consToList(d3d, message);
						Memento k  = (Memento)ct.getMemento();

						// rollback & forward
						exec(m.aftret, env, m, false);
						m.aftret = new CompiledCode();
						int iz = 0;
						for(; (iz < m.envStk.size() &&
								iz < k.envStk.size()); iz++) {
							if(m.envStk.get(iz) != k.envStk.get(iz)) {
								break;
							}
						}

						// overwrite
						m.overwrite(k.copy());

						// pop
						env  = m.envStk.pop();
						code = m.codeStk.pop();
						addr = m.addrStk.pop();
						m.callerStk.pop();
						m.dataStk.pop();
						m.workStk.pop(); // call/ccでworkStkをpushしている
						m.memoStk.pop();
						setContinuationArgs(lst, m);
						addr++;
					} else if(d3s instanceof Undef) {
						m.dataStk.push(Undef.UNDEF);
						addr++;
					} else {
						throw message.getError("err.invalidap", d3s);
					}
					break;
				case CALL_METHOD:
					Datum d3d1 = m.dataStk.pop();
					Datum d3s1 = m.dataStk.pop();

					if(d3s1 instanceof Symbol) {
						String nm = ((Symbol)d3s1).getName();

						if("aux-apply".equals(nm)) {
							m.dataStk.push(evalApply(d3d1));
						} else if("aux-apply-valid".equals(nm)) {
							m.dataStk.push(evalApplyValid(d3d1));
						}
					} else {
						throw new RuntimeException();
					}
					addr++;
					break;
				case JMP:
					addr = code.getAddress(c.getJmpLabel());
					break;
				case JMP_IF:
					if(m.dataStk.peek().isTrue()) {
						addr = code.getAddress(c.getJmpLabel());
					} else {
						addr++;
					}
					break;
				case JMP_UNLESS:
					if(!m.dataStk.peek().isTrue()) {
						addr = code.getAddress(c.getJmpLabel());
					} else {
						addr++;
					}
					break;
				case JMP_TOP:
					_log.finer(
							"Jump to top:cdstk(" +
							m.codeStk.size() + ")");
					addr = 0;
					break;
				case REFER_SYMBOL:
				case REFER_SETTER:
					Datum d51;

					d51 = env.findDatum(c.getDatum());
					if(d51 == null) {
						Datum zx = c.getDatum();

						if(zx instanceof SymbolName &&
								((SymbolName)zx)
								.getName().equals("next-method")) {
							d51 = env.findDatum(
									Symbol.getSymbol("next-method"));
						}

						if(d51 == null) {
							throw message.getError(
									"err.unbound", c.getDatum());
						}
					}

					if(!c.getOp().equals(Oper.REFER_SETTER)) {
						m.dataStk.push(d51);
					}
					addr++;
					break;
				case REFER_DEFINED:
					d51 = env.findDatum(c.getDatum());
					m.dataStk.push(LispBoolean.getInstance(d51 != null));
					addr++;
					break;
				case BIND:
					if(c.getDatum() instanceof SymbolName) {
						Symbol sym = (Symbol)c.getDatum();

						if(tenv != null) {
							bind0(tenv, sym, m.dataStk.pop());
						} else {
							bind0(env, sym, m.dataStk.pop());
						}
					} else {
						throw message.getError(
								"err.symbol", c.getDatum());
					}
					addr++;
					break;
				case SET:
					if(c.getDatum() instanceof Symbol) {
						if(!set0(env, (Symbol)c.getDatum(),
								m.dataStk.peek())) {
							throw message.getError(
									"err.unbound", c.getDatum());
						}
					}
					addr++;
					break;
				case RETURN_OP:
					exec(m.aftret, env, m, false);
					m.aftret = new CompiledCode();
					if(m.callerStk.isEmpty()) {
						return m.dataStk.pop();
					} else {
						Promise p;

						env  = m.envStk.pop();
						code = m.codeStk.pop();
						addr = m.addrStk.pop();
						m.callerStk.pop();
						p     = m.memoStk.pop();
						_log.finer(
								"Return:dtstk(" +
								m.dataStk.size() + "):cdstk(" +
								m.codeStk.size() + ")");

						// 結果をメモする
						if(p != null) {
							p.setMemo(m.dataStk.peek());
						}
						addr++;
					}
					break;
				case PUSH_CONTINUATION:
					Memento cm = m.copy();
					Continuation rs0 = new Continuation(cm);

					m.dataStk.push(rs0);
					addr++;
					break;
				case FORCE:
					Datum d61 = m.dataStk.pop();

					if(d61 instanceof Promise) {
						Promise p = (Promise)d61;

						if(p.getMemo() == null) {
							// 遅延評価する
							m.push(addr, code, env, p);

							env = new Environment(p.getEnvironment());
							code = p.getCode();
							addr = 0;
							//state = 0;
						} else {
							// メモ化された結果を返す
							m.dataStk.push(p.getMemo());
							addr++;
						}
					} else {
						throw message.getError("err.force", "force");
					}
					break;
				case NEW_PROMISE:
					if(c.getDatum() instanceof Promise) {
						Promise p = (Promise)c.getDatum();
						Promise np = new Promise(p.getCode());

						np.setEnvironment(env);
						m.dataStk.push(np);
					} else {
						throw message.getError("err.force");
					}
					addr++;
					break;
				case APPEND_VALUES:
					Datum       d71 = m.dataStk.pop();

					if(d71 instanceof MultiValues) {
						// 多値
						MultiValues d = (MultiValues)d71;

						m.dataStk.push(
								LispUtils.listToCons(d.getValues()));
					} else if(d71 == Undef.UNDEF) {
						// valuesが0件
						m.dataStk.push(Nil.NIL);
					} else {
						// 多値でない
						Cons b = new Cons();

						b.setCar(d71);
						m.dataStk.push(b);
					}
					addr++;
					break;
				case EVAL_CODE:
					Datum d81 = m.dataStk.pop();  // 環境
					Datum d82 = m.dataStk.pop();  // S式
					EnvironmentObject eo;

					if(!(d81 instanceof EnvironmentObject)) {
						throw message.getError("err.environment");
					}

					eo = (EnvironmentObject)d81;
					if(eo.isInherit()) {
						// interaction environment
						Closure cl = makeEval(
								d82, eo.getEnvironment(), m);

						m.push(addr, code, env, cl);

						env = new Environment(cl.getEnvironment());
						code = cl.getCode();
						addr = 0;
					} else {
						// null/RnRS enviromnent
						Closure cl = makeEval(
								d82, eo.getEnvironment(), m);

						m.dataStk.push(exec(
								cl.getCode(),
								eo.getEnvironment(),
								newMemento()));
						addr++;
					}
					break;
				case OVERRIDE_LOAD_CODE:
					Datum d84 = m.dataStk.pop();  // port

					if(d84 instanceof InputPort) {
						InputPort ipt = (InputPort)d84;

						try {
							ClosureClass clc = makeLoad(ipt, env, m);

							code = clc.getCode();
							addr = 0;
						} catch(LispException e) {
							ipt.close();
							throw e;
						}
					} else {
						throw message.getError("err.require.iport");
					}
					break;
				}
			} catch(LispException e) {
				throw e;
			}
		}
	}


	public Datum exec(
			CompiledCode code1, Environment env1, IntStack memento) {
		return exec(code1, env1, memento, true);
	}


	public IntStack newMemento() {
		return new Memento();
	}

}
