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

/**
 * 
 *
 *
 * @author MORIGUCHI, Yuichiro 2009
 */
/*package*/ class CompiledCode {

	/*package*/ static enum Oper {
		PUSH,
		POP,
		BEGIN_LIST,
		APPEND_LIST,
		APPEND_LIST_SPLICING,
		APPEND_LIST_MULTI_VALUES,
		END_LIST_DOT,
		END_LIST,
		END_LIST_VECTOR,
		CALL,
		CALL_TAIL,
		CALL_METHOD,
		JMP,
		JMP_IF,
		JMP_UNLESS,
		JMP_TOP,
		REFER_SYMBOL,
		REFER_SETTER,
		REFER_DEFINED,
		BIND,
		BIND_MACRO,
		BIND_MACRO_QUOTE,
		SET,
		RETURN_OP,
		PUSH_CONTINUATION,
		FORCE,
		NEW_PROMISE,
		APPEND_VALUES,
		EVAL_CODE,
		OVERRIDE_LOAD_CODE,
	};

	/*package*/ static class Code {

		private Code(Oper op) {
			this.op = op;
		}

		private Oper op;
		private Datum datum;
		private int jmpLabel;
		private int rewind;

		/*package*/ Oper getOp() {
			return op;
		}

		/*package*/ Datum getDatum() {
			return datum;
		}

		/*package*/ int getJmpLabel() {
			return jmpLabel;
		}

		/*package*/ int getRewind() {
			return rewind;
		}

		public String toString() {
			return op + ":" + datum + ":" + jmpLabel;
		}

	}

	private List<Code> opers = new ArrayList<Code>();
	private List<Integer> labels = new ArrayList<Integer>();

	/*package*/ static class Builder {

		private CompiledCode codes = new CompiledCode();

		//
		/*package*/ Builder() {
			// do nothing
		}

		//
		/*package*/ void addPush(Datum d) {
			Code c = new Code(Oper.PUSH);

			c.datum = d;
			codes.opers.add(c);
		}

		//
		/*package*/ void addPop() {
			codes.opers.add(new Code(Oper.POP));
		}

		//
		/*package*/ void addBeginList() {
			codes.opers.add(new Code(Oper.BEGIN_LIST));
		}

		//
		/*package*/ void addAppendList() {
			codes.opers.add(new Code(Oper.APPEND_LIST));
		}

		//
		/*package*/ void addAppendListSplicing() {
			codes.opers.add(new Code(Oper.APPEND_LIST_SPLICING));
		}

		//
		/*package*/ void addAppendListMultiValues() {
			codes.opers.add(new Code(Oper.APPEND_LIST_MULTI_VALUES));
		}

		//
		/*package*/ void addEndListDot() {
			codes.opers.add(new Code(Oper.END_LIST_DOT));
		}

		//
		/*package*/ void addEndList() {
			codes.opers.add(new Code(Oper.END_LIST));
		}

		//
		/*package*/ void addEndListVector() {
			codes.opers.add(new Code(Oper.END_LIST_VECTOR));
		}

		//
		/*package*/ void addCall() {
			codes.opers.add(new Code(Oper.CALL));
		}

		//
		/*package*/ void addCallMethod() {
			codes.opers.add(new Code(Oper.CALL_METHOD));
		}

		//
		/*package*/ void addCallTail(int rewind) {
			Code c = new Code(Oper.CALL_TAIL);

			c.rewind = rewind;
			codes.opers.add(c);
		}

		//
		/*package*/ void addJmp(int jmpLabel) {
			Code c = new Code(Oper.JMP);

			c.jmpLabel = jmpLabel;
			codes.opers.add(c);
		}

		//
		/*package*/ void addJmpIf(int jmpLabel) {
			Code c = new Code(Oper.JMP_IF);

			c.jmpLabel = jmpLabel;
			codes.opers.add(c);
		}

		//
		/*package*/ void addJmpUnless(int jmpLabel) {
			Code c = new Code(Oper.JMP_UNLESS);

			c.jmpLabel = jmpLabel;
			codes.opers.add(c);
		}

		//
		/*package*/ void addJmpTop() {
			codes.opers.add(new Code(Oper.JMP_TOP));
		}

		//
		/*package*/ void addReferSymbol(Datum destSymbol) {
			Code c = new Code(Oper.REFER_SYMBOL);

			c.datum = destSymbol;
			codes.opers.add(c);
		}

		//
		/*package*/ void addReferSetter(Datum destSymbol) {
			Code c = new Code(Oper.REFER_SETTER);

			c.datum = destSymbol;
			codes.opers.add(c);
		}

		//
		/*package*/ void addDefined(Datum destSymbol) {
			Code c = new Code(Oper.REFER_DEFINED);

			c.datum = destSymbol;
			codes.opers.add(c);
		}

		//
		/*package*/ void addBind(Datum destSymbol) {
			Code c = new Code(Oper.BIND);

			c.datum = destSymbol;
			codes.opers.add(c);
		}

		//
		/*package*/ void addSet(Datum destSymbol) {
			Code c = new Code(Oper.SET);

			c.datum = destSymbol;
			codes.opers.add(c);
		}

		//
		/*package*/ void addReturnOp() {
			codes.opers.add(new Code(Oper.RETURN_OP));
		}

		//
		/*package*/ void addPushContinuation() {
			Code c = new Code(Oper.PUSH_CONTINUATION);

			codes.opers.add(c);
		}

		//
		/*package*/ void addForce() {
			codes.opers.add(new Code(Oper.FORCE));
		}

		//
		/*package*/ void addNewPromise(Datum p) {
			Code c = new Code(Oper.NEW_PROMISE);

			c.datum = p;
			codes.opers.add(c);
		}

		//
		/*package*/ void addAppendValues() {
			codes.opers.add(new Code(Oper.APPEND_VALUES));
		}

		//
		/*package*/ void addEvalCode() {
			codes.opers.add(new Code(Oper.EVAL_CODE));
		}

		//
		/*package*/ void addOverrideLoadCode() {
			codes.opers.add(new Code(Oper.OVERRIDE_LOAD_CODE));
		}

		//
		/*package*/ int getCurrentAddress() {
			return codes.opers.size();
		}

		//
		/*package*/ void setCurrentAddressToLabel(int label) {
			codes.labels.set(label, getCurrentAddress());
		}

		//
		/*package*/ int allocLabel() {
			int res = codes.labels.size();

			codes.labels.add(-1);
			return res;
		}

		//
		/*package*/ CompiledCode getCodeRef() {
			return codes;
		}

		//
		/*pacakge*/ void merge(CompiledCode cc) {
			codes.opers.addAll(cc.opers);
			codes.labels.addAll(cc.labels);
		}

	}

	//
	/*package*/ Code getCode(int addr) {
		return addr < opers.size() ? opers.get(addr) : null;
	}

	//
	/*package*/ int getAddress(int label) {
		return labels.get(label);
	}

	/*
	 * (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	public String toString() {
		StringBuilder buf = new StringBuilder();

		for(int i = 0; i < opers.size(); i++) {
			Code c = opers.get(i);

			if(c.datum instanceof ClosureClass) {
				buf.append("----------------------------\n");
				buf.append(((ClosureClass)c.datum).getCode());
				buf.append("----------------------------\n");
			} else if(c.datum instanceof Closure) {
				buf.append("----------------------------\n");
				buf.append(((Closure)c.datum).getCode());
				buf.append("----------------------------\n");
			}

			buf.append(c.toString());
			buf.append("\n");
		}
		return buf.toString();
	}

}
