/*
 * 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.List;

/**
 * 
 *
 *
 * @author MORIGUCHI, Yuichiro 2009
 */
public class SynQuasiquote extends Syntax {

	private static Datum getCaar(Cons c) {
		if(c.getCar() instanceof Cons) {
			return ((Cons)c.getCar()).getCar();
		} else {
			return null;
		}
	}

	/*package*/ boolean equalsQuasiquote(Datum d) {
		return SyntaxUtils.equalsReserved(Symbol.QUASIQUOTE, d);
	}

	/*package*/ boolean equalsUnquote(Datum d) {
		return SyntaxUtils.equalsReserved(Symbol.UNQUOTE, d);
	}

	/*package*/ boolean equalsUnquoteSplicing(Datum d) {
		return SyntaxUtils.equalsReserved(Symbol.UNQUOTE_SPLICING, d);
	}

	/*package*/ LispException error(LispMessage mesg) {
		return mesg.getError("err.quasiquote.malform");
	}

	public String toString() {
		return "Syntax:quasiquote";
	}

	private void quote1(
			int level,
			Cons c,
			Environment env,
			LispCompiler comp,
			CompiledCode.Builder build,
			Cons callsym,
			LispMessage mesg,
			List<Cons> symlist,
			CodeExecutor exec,
			IntStack memento) {
		// (car (cdr c))がないときは内部エラー
		if(!(c.getCdr() instanceof Cons)) {
			throw error(mesg);
		}
		Cons c2 = (Cons)c.getCdr();

		//build.addPush(new SubrList());
		build.addBeginList();
		build.addPush(c.getCar());
		build.addAppendList();
		expand(level, c2.getCar(), env, comp, build,
				callsym, mesg, symlist, exec, memento);
		build.addAppendList();
		build.addEndList();
	}

	private void expand(
			int level,
			Datum body,
			Environment env,
			LispCompiler comp,
			CompiledCode.Builder build,
			Cons callsym,
			LispMessage mesg,
			List<Cons> symlist,
			CodeExecutor exec,
			IntStack memento) {
		if(body instanceof Cons) {
			Cons c = (Cons)body;

			if(equalsQuasiquote(c.getCar())) {
				quote1(level + 1, c, env, comp, build,
						callsym, mesg, symlist,
						exec, memento);
			} else if(equalsUnquote(c.getCar())) {
				// (car (cdr c))がないときは内部エラー
				Cons c2 = (Cons)c.getCdr(), c3 = c2;
				int zi = 0;

				if(level > zi) {
					quote1(level - 1, c, env, comp, build,
							callsym, mesg, symlist,
							exec, memento);
				} else if(c.getCdr() instanceof Cons) {
					comp.compile(
							c3.getCar(), env, build,
							callsym, false, symlist,
							exec, memento);
				} else {
					throw error(mesg);
				}
			} else if(equalsUnquoteSplicing(c.getCar())) {
				if(level > 0) {
					quote1(level - 1, c, env, comp, build,
							callsym, mesg, symlist,
							exec, memento);
				} else if(c.getCdr() instanceof Cons) {
					// (car (cdr c))がないときは内部エラー
					Cons c2 = (Cons)c.getCdr();
					comp.compile(
							c2.getCar(), env, build,
							callsym, false, symlist,
							exec, memento);
				} else {
					throw error(mesg);
				}
			} else {
				//build.addPush(new SubrList());
				build.addBeginList();
				while(true) {
					Datum caar = getCaar(c);

					expand(level, c.getCar(), env, comp, build,
							callsym, mesg, symlist,
							exec, memento);

					if(c.getCdr() instanceof Cons) {
						Cons c2 = (Cons)c.getCdr();
						Datum d2 = c2.getCar();

						if(equalsUnquote(d2)) {
							// `(a b . ,(expr))
							if(equalsUnquoteSplicing(caar)) {
								build.addAppendListSplicing();
							} else {
								build.addAppendList();
							}
							expand(level, c.getCdr(), env, comp, build,
									callsym, mesg, symlist,
									exec, memento);
							build.addEndListDot();
							break;
						} else if(equalsUnquoteSplicing(d2)) {
							// `(a b . ,@(expr))
							//throw new LispException(
							//		"syntax error: malformed ,@");
							throw error(mesg);
						} else if(level == 0 &&
								equalsUnquoteSplicing(caar)) {
							// `(a b ,@(expr) d ...)のとき
							build.addAppendListSplicing();
							c = (Cons)c.getCdr();
						} else {
							build.addAppendList();
							c = (Cons)c.getCdr();
						}
					} else if(c.getCdr() == Nil.NIL) {
						if(level == 0 && equalsUnquoteSplicing(caar)) {
							// `(a b ,@(expr))のとき
							// 最後の引数をドットリストに連結する
							build.addEndListDot();
						} else {
							build.addAppendList();
							build.addEndList();
						}
						break;
					} else {
						if(level == 0 && equalsUnquoteSplicing(caar)) {
							// `(a b .@(expr) . d)のとき
							build.addAppendListSplicing();
						} else {
							build.addAppendList();
						}
						expand(level, c.getCdr(), env, comp, build,
								callsym, mesg, symlist,
								exec, memento);
						build.addEndListDot();
						break;
					}
				}
			}
		} else if(body instanceof LispVector) {
			LispVector v = (LispVector)body;

			build.addBeginList();
			for(int i = 0; i < v.size(); i++) {
				expand(level, v.get(i), env, comp, build,
						callsym, mesg, symlist,
						exec, memento);
				if(v.get(i) instanceof Cons) {
					Cons cz = (Cons)v.get(i);

					if(equalsUnquoteSplicing(cz.getCar())) {
						build.addAppendListSplicing();
					} else {
						build.addAppendList();
					}
				} else {
					build.addAppendList();
				}
			}
			build.addEndListVector();
		} else {
			build.addPush(body);
		}
	}

	/*package*/ void compile(
			Datum body,
			Environment env,
			LispCompiler comp,
			CompiledCode.Builder build,
			boolean toplevel,
			Cons callsym,
			boolean istail,
			LispMessage mesg,
			List<Cons> symlist,
			CodeExecutor exec,
			IntStack memento) {
		if(body instanceof Cons) {
			expand(0, ((Cons)body).getCar(),
					env, comp, build, callsym, mesg, symlist,
					exec, memento);
		} else {
			//throw new LispException("invalid quote");
			throw error(mesg);
		}
	}

}
