/*
 * Copyright 2013 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.sh;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import net.morilib.sh.builtin.ShBuiltInCommands;
import net.morilib.sh.misc.XtraceStream;

/**
 * 文を簡易コンパイルした結果の中間表現です。
 *
 *
 * @author MORIGUCHI, Yuichiro 2013/04/14
 */
public class ShTreeExpressionMachine implements ShTree {

	/**
	 * 簡易コンパイルした命令セットのBuilderクラスです。
	 *
	 *
	 * @author MORIGUCHI, Yuichiro 2013/04/14
	 */
	public static class Builder {

		private Map<Object, Integer> labels;
		private Map<Object, List<Code>> unresolved;
		private List<Code> codes;

		/**
		 * Builderを生成します。
		 */
		public Builder() {
			codes = new ArrayList<Code>();
			labels = new HashMap<Object, Integer>();
			unresolved = new HashMap<Object, List<Code>>();
		}

		/**
		 * 命令を追加します。
		 * 
		 * @param e 中間表現
		 */
		public void add(ShTree e) {
			codes.add(new Code(Mnemonic.COMP, e, codes.size()));
		}

		Code add(Mnemonic m, Object label) {
			List<Code> c;
			Code d;

			if(label == null) {
				codes.add(d = new Code(m, null, JMP_END));
			} else if(labels.containsKey(label)) {
				codes.add(d = new Code(m, null, labels.get(label)));
			} else {
				codes.add(d = new Code(m, null, UNRESOLVED));
				if((c = unresolved.get(label)) == null) {
					c = new ArrayList<Code>();
					unresolved.put(label, c);
				}
				c.add(d);
			}
			return d;
		}

		public void addPtn(ShToken variable, ShPattern ptn) {
			Code c = new Code(Mnemonic.PTN, null, -1);

			c.pattern  = ptn;
			c.variable = variable;
			codes.add(c);
		}

		public void addReturn(ShToken ret) {
			Code c = new Code(Mnemonic.RETURN, null, 0);

			c.variable = ret;
			codes.add(c);
		}

		public void addExit() {
			codes.add(new Code(Mnemonic.EXIT, null, 0));
		}

		public void addJmp(Object l) {
			add(Mnemonic.JMP, l);
		}

		public void addJmpZ(Object l) {
			add(Mnemonic.JMP_Z, l);
		}

		public void addJmpNZ(Object l) {
			add(Mnemonic.JMP_NZ, l);
		}

		public void addPushin(List<ShToken> ts) {
			Code c = new Code(Mnemonic.PUSHIN, null, -1);

			c.tokens = new ArrayList<ShToken>(ts);
			codes.add(c);
		}

		public void addGetin(String s, Object l) {
			add(Mnemonic.GETIN, l).name = s;
		}

		public void addRes() {
			codes.add(new Code(Mnemonic.RES, null, 0));
		}

		public void addLabel(Object label) {
			List<Code> c;
			int addr = codes.size();

			if((c = unresolved.get(label)) != null) {
				for(Code d : c)  d.next = addr;
			}
			labels.put(label, addr);
		}

		public ShTree get() {
			return new ShTreeExpressionMachine(
					codes.toArray(new Code[0]));
		}

	}

	static enum Mnemonic {
		COMP, JMP, JMP_Z, JMP_NZ, RES, PTN, PUSHIN, GETIN, RETURN, EXIT
	}

	static final int UNRESOLVED = -1;
	static final int JMP_END = Integer.MAX_VALUE;

	static class Code {

		List<ShToken> tokens;
		ShPattern pattern;
		Mnemonic mnemonic;
		ShToken variable;
		ShTree expr;
		String name;
		int next;

		Code(Mnemonic m, ShTree e, String s, int n) {
			mnemonic = m;
			expr = e;
			next = n;
			name = s;
		}

		Code(Mnemonic m, ShTree e, int n) {
			this(m, e, null, n);
		}

	}

	private Code[] codes;

	private ShTreeExpressionMachine(Code... codes) {
		this.codes = codes;
	}

	public int eval(ShEnvironment env, ShFileSystem fs,
			ShBuiltInCommands cmds, ShRuntime run, InputStream in,
			PrintStream out,
			PrintStream err,
			XtraceStream p) throws IOException, ShSyntaxException {
		Stack<Iterator<String>> st = new Stack<Iterator<String>>();
		int e = 0, pc = 0;
		List<String> ls;
		String s;
		Code c;

		while(pc < codes.length) {
			if(pc < 0)  throw new RuntimeException();
			c = codes[pc];
			switch(c.mnemonic) {
			case COMP:
				e = c.expr.eval(env, fs, cmds, run, in, out, err, p);
				pc++;
				break;
			case JMP:
				pc = c.next;  break;
			case JMP_Z:
				pc = e != 0 ? c.next : pc + 1;  break;
			case JMP_NZ:
				pc = e != 0 ? pc + 1 : c.next;  break;
			case PTN:
				s  = ShTrees.substitute(env, run, fs, err, p,
						c.variable);
				e  = c.pattern.matches(env, fs, run, err, p, s) ?
						0 : 1;
				pc++;
				break;
			case PUSHIN:
				ls = ShTrees.substitute(env, run, fs, err, p,
						c.tokens);
				st.push(ls.iterator());
				pc++;
				break;
			case GETIN:
				if(st.peek().hasNext()) {
					s = st.peek().next();
					env.bind(c.name, s);
					pc++;
				} else {
					st.pop();  pc = c.next;
				}
				break;
			case RETURN:
				try {
					if((s = env.getTrap(ShSignal.RETURN)) != null) {
						run.eval(env, fs, in, out, err, p, s);
					}
					s = ShTrees.substitute(env, run, fs, err, p,
							c.variable);
					return Integer.parseInt(s);
				} catch(NumberFormatException x) {
					return 0;
				}
			case EXIT:  throw new ShExitException(e);
			case RES:  e = 0;  pc++;  break;
			}
		}
		return e;
	}

	public void compileInternally(ShTreeExpressionMachine.Builder b,
			Object brk, Object cnt) {
		b.add(this);
	}

}
