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

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import net.morilib.sh.ShEnvironment;
import net.morilib.sh.ShFileSystem;
import net.morilib.sh.ShProcess;
import net.morilib.sh.misc.LookaheadIterator;
import net.morilib.sh.misc.WrappedLookaheadIterator;

/**
 *
 *
 * @author MORIGUCHI, Yuichiro 2013/05/18
 */
public final class ShTestParser implements ShProcess {

	private static enum S1 {
		S00, S01, S02, S03, S04, S05, S06, S07, S08, S09,
		S10, S11, S12, S13, S14, S15
	}

	private Map<String, ShTest1Term>  sgl;
	private Map<String, ShTest2Terms> dbl;

	public static ShTestParser getDefault() {
		Map<String, ShTest1Term>  ma;
		Map<String, ShTest2Terms> mb;

		ma = new HashMap<String, ShTest1Term>();
		ma.put("-a", ShTestStrategies.EXIST);
		ma.put("-d", ShTestStrategies.DIRECTORY);
		ma.put("-e", ShTestStrategies.EXIST);
		ma.put("-f", ShTestStrategies.FILE);
		ma.put("-r", ShTestStrategies.READABLE);
		ma.put("-s", ShTestStrategies.NOTZEROFILE);
		ma.put("-w", ShTestStrategies.WRITABLE);
		ma.put("-x", ShTestStrategies.EXECUTABLE);
		ma.put("-z", ShTestStrategies.EMPTY);
		ma.put("-n", ShTestStrategies.NONEMPTY);

		mb = new HashMap<String, ShTest2Terms>();
		mb.put("-nt", ShTestStrategies.NEWER);
		mb.put("-ot", ShTestStrategies.OLDER);
		mb.put("=",   ShTestStrategies.EQUALS);
		mb.put("!=",  ShTestStrategies.NOT_EQUALS);
		mb.put("<",   ShTestStrategies.LESS);
		mb.put("<=",  ShTestStrategies.LESS_EQ);
		mb.put(">",   ShTestStrategies.GREATER);
		mb.put(">=",  ShTestStrategies.GREATER_EQ);
		mb.put("-eq", ShTestStrategies.EQ);
		mb.put("-ne", ShTestStrategies.NE);
		mb.put("-lt", ShTestStrategies.LT);
		mb.put("-le", ShTestStrategies.LE);
		mb.put("-gt", ShTestStrategies.GT);
		mb.put("-ge", ShTestStrategies.GE);
		return new ShTestParser(ma, mb);
	}

	ShTestParser(Map<String, ShTest1Term> sgl,
			Map<String, ShTest2Terms> dbl) {
		this.sgl = sgl;
		this.dbl = dbl;
	}

	private boolean ismeta(Object s) {
		return (s.equals("(")  || s.equals(")")  || s.equals("!") ||
				s.equals("-a") || s.equals("-o"));
	}

	private void shift(List<Object> s, List<S1> t,
			LookaheadIterator<String> r, S1 stat) {
		if(r.hasNext())  s.add(0, r.next());
	}

	private Object look(LookaheadIterator<String> r) {
		return r.hasNext() ? r.peek() : Integer.MAX_VALUE;
	}

	int parseLR(ShFileSystem fs, LookaheadIterator<String> r) {
		List<Object> s = new LinkedList<Object>();
		List<S1> t = new LinkedList<S1>();
		S1 stat = S1.S00;
		boolean a, b;
		String x, y;
		Object o;

		s.add(0, Integer.MAX_VALUE);  t.add(0, stat);
		while(true) {
			switch(stat) {
			case S00:
				// S -> *E
				// E -> *E -o E
				// E -> *E -a E
				// E -> *! E
				// E -> *( E )
				// E -> *unary n
				// E -> *n binary n
				// E -> *n
				if(s.get(0) instanceof Boolean) {
					stat = S1.S01;
				} else if(look(r).equals("(")) {
					shift(s, t, r, stat);  stat = S1.S06;
				} else if(sgl.containsKey(look(r))) {
					shift(s, t, r, stat);  stat = S1.S09;
				} else if(look(r).equals("!")) {
					shift(s, t, r, stat);  stat = S1.S14;
				} else {
					shift(s, t, r, stat);  stat = S1.S11;
				}
				break;
			case S01:
				// S -> E*
				// E -> E *-o E
				// E -> E *-a E
				if(!r.hasNext()) {
					return ((Boolean)s.get(0)).booleanValue() ? 0 : 1;
				} else if(look(r).equals("-o")) {
					shift(s, t, r, stat);  stat = S1.S02;
				} else if(look(r).equals("-a")) {
					shift(s, t, r, stat);  stat = S1.S04;
				} else {
					throw new ShTestSyntaxException();
				}
				break;
			case S02:
				// E -> E -o *E
				// E -> *E -o E
				// E -> *E -a E
				// E -> *( E )
				// E -> *! E
				// E -> *unary n
				// E -> *n binary n
				// E -> *n
				if(s.get(0) instanceof Boolean) {
					stat = S1.S03;
				} else if(look(r).equals("(")) {
					shift(s, t, r, stat);  stat = S1.S06;
				} else if(sgl.containsKey(look(r))) {
					shift(s, t, r, stat);  stat = S1.S09;
				} else if(look(r).equals("!")) {
					shift(s, t, r, stat);  stat = S1.S14;
				} else {
					shift(s, t, r, stat);  stat = S1.S11;
				}
				break;
			case S03:
				// E -> E -o E*
				// E -> E *-o E
				// E -> E *-a E
				if(look(r).equals("-a")) {
					shift(s, t, r, stat);  stat = S1.S04;
				} else {
					b = ((Boolean)s.remove(0)).booleanValue();
					s.remove(0);
					a = ((Boolean)s.remove(0)).booleanValue();
					t.remove(0);  t.remove(0);  t.remove(0);
					s.add(0, Boolean.valueOf(a | b));
					stat = t.remove(0);
				}
				break;
			case S04:
				// E -> E -a *E
				// E -> *E -o E
				// E -> *E -a E
				// E -> *( E )
				// E -> *! E
				// E -> *unary n
				// E -> *n binary n
				// E -> *n
				if(s.get(0) instanceof Boolean) {
					stat = S1.S05;
				} else if(look(r).equals("(")) {
					shift(s, t, r, stat);  stat = S1.S06;
				} else if(sgl.containsKey(look(r))) {
					shift(s, t, r, stat);  stat = S1.S09;
				} else if(look(r).equals("!")) {
					shift(s, t, r, stat);  stat = S1.S14;
				} else {
					shift(s, t, r, stat);  stat = S1.S11;
				}
				break;
			case S05:
				// E -> E -a E*
				// E -> E *-o E
				// E -> E *-a E
				b = ((Boolean)s.remove(0)).booleanValue();
				s.remove(0);
				a = ((Boolean)s.remove(0)).booleanValue();
				t.remove(0);  t.remove(0);  t.remove(0);
				s.add(0, Boolean.valueOf(a & b));
				stat = t.remove(0);
				break;
			case S06:
				// E -> ( *E )
				// E -> *E -o E
				// E -> *E -a E
				// E -> *( E )
				// E -> *! E
				// E -> *unary n
				// E -> *n binary n
				// E -> *n
				if(s.get(0) instanceof Boolean) {
					stat = S1.S07;
				} else if(look(r).equals("(")) {
					shift(s, t, r, stat);  stat = S1.S06;
				} else if(sgl.containsKey(look(r))) {
					shift(s, t, r, stat);  stat = S1.S09;
				} else if(look(r).equals("!")) {
					shift(s, t, r, stat);  stat = S1.S14;
				} else {
					shift(s, t, r, stat);  stat = S1.S11;
				}
				break;
			case S07:
				// E -> ( E *)
				// E -> E *-o E
				// E -> E *-a E
				if(look(r).equals("-o")) {
					shift(s, t, r, stat);  stat = S1.S02;
				} else if(look(r).equals("-a")) {
					shift(s, t, r, stat);  stat = S1.S04;
				} else if(look(r).equals(")")) {
					shift(s, t, r, stat);  stat = S1.S08;
				} else {
					throw new ShTestSyntaxException();
				}
				break;
			case S08:
				// E -> ( E )*
				s.remove(0);  o = s.remove(0);  s.remove(0);
				t.remove(0);  t.remove(0);  t.remove(0);
				s.add(0, o);
				stat = t.remove(0);
				break;
			case S09:
				// E -> unary *n
				if(!ismeta(look(r))) {
					shift(s, t, r, stat);  stat = S1.S10;
				} else {
					throw new ShTestSyntaxException();
				}
				break;
			case S10:
				// E -> unary n*
				x = s.remove(0).toString();  o = s.remove(0);
				t.remove(0);  t.remove(0);
				s.add(0, Boolean.valueOf(sgl.get(o).eval(fs, x) == 0));
				stat = t.remove(0);
				break;
			case S11:
				// E -> n *binary n
				// E -> n*
				if(dbl.containsKey(look(r))) {
					shift(s, t, r, stat);  stat = S1.S12;
				} else {
					x = s.remove(0).toString();
					t.remove(0);
					s.add(0, Boolean.valueOf(x.length() > 0));
					stat = t.remove(0);
				}
				break;
			case S12:
				// E -> n binary *n
				if(!ismeta(look(r))) {
					shift(s, t, r, stat);  stat = S1.S13;
				} else {
					throw new ShTestSyntaxException();
				}
				break;
			case S13:
				// E -> n binary n*
				y = s.remove(0).toString();
				o = s.remove(0);
				x = s.remove(0).toString();
				t.remove(0);  t.remove(0);  t.remove(0);
				s.add(0, Boolean.valueOf(
						dbl.get(o).eval(fs, x, y) == 0));
				stat = t.remove(0);
				break;
			case S14:
				// E -> ! *E
				// E -> *E -o E
				// E -> *E -a E
				// E -> *( E )
				// E -> *! E
				// E -> *unary n
				// E -> *n binary n
				// E -> *n
				if(s.get(0) instanceof Boolean) {
					stat = S1.S15;
				} else if(look(r).equals("(")) {
					shift(s, t, r, stat);  stat = S1.S06;
				} else if(sgl.containsKey(look(r))) {
					shift(s, t, r, stat);  stat = S1.S09;
				} else if(look(r).equals("!")) {
					shift(s, t, r, stat);  stat = S1.S14;
				} else {
					shift(s, t, r, stat);  stat = S1.S11;
				}
				break;
			case S15:
				// E -> ! E*
				a = ((Boolean)s.remove(0)).booleanValue();
				s.remove(0);
				t.remove(0);  t.remove(0);
				s.add(0, Boolean.valueOf(!a));
				stat = t.remove(0);
				break;
			}
			t.add(0, stat);
		}
	}

	/**
	 * 
	 * @param fs
	 * @param args
	 * @return
	 */
	public int parse(ShFileSystem fs, String... args) {
		LookaheadIterator<String> r;

		r = new WrappedLookaheadIterator<String>(
				Arrays.asList(args).iterator());
		if(r.hasNext())  r.next();
		return parseLR(fs, r);
	}

	public int main(ShEnvironment env,  ShFileSystem fs,
			InputStream stdin, PrintStream stdout,
			PrintStream stderr, String... args) throws IOException {
		return parse(fs, args);
	}

}
