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

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import net.morilib.automata.DFAState;
import net.morilib.nina.DFABuilder;
import net.morilib.nina.NinaState;
import net.morilib.nina.Quadro;
import net.morilib.nina.TMBuilder;
import net.morilib.nina.translate.sh.ReplaceStrangeChar;
import net.morilib.range.Interval;
import net.morilib.range.Range;
import net.morilib.sh.ShEnvironment;

/**
 *
 *
 * @author MORIGUCHI, Yuichiro 2013/10/23
 */
public class NinaTranslatorJava extends AbstractNinaTranslator {

	//
	private static final Pattern RET_CONST = Pattern.compile(
			"return +([A-Za-z][A-Za-z0-9]*\\.)*[A-Z][A-Z0-9]*;");
	private static final Pattern E_CONST = Pattern.compile(
			"([A-Za-z][A-Za-z0-9]*\\.)*[A-Z][A-Z0-9]*");
	private static final Pattern _IAT = Pattern.compile(
			"(.*),([A-Z]+|\\*)");
	private static final Pattern _CLS = Pattern.compile("<(.*)>");
	private static final Pattern _STR = Pattern.compile("\"(.*)\"");
	private static final String LOK = "LOOKAHEAD($c);";

	/**
	 * 
	 */
	public NinaTranslatorJava() {
		parent = this;
	}

	//
	private int infimumbound(Interval a) {
		Object o;

		o = a.getInfimumBound();
		return o == Interval.FROM_INFIMUM ? 0 : ((Integer)o).intValue();
	}

	//
	private int infimumbound(Range a) {
		Interval v;

		v = a.intervals().first();
		return infimumbound(v);
	}

	//
	private String tostr(int c) {
		String x;

		if(c == '\\') {
			x = "'\\\\'";
		} else if(c == '\n') {
			x = "'\\n'";
		} else if(c == '\'') {
			x = "'\\''";
		} else if(Character.isISOControl(c)) {
			x = Integer.toString(c);
		} else if(c >= 0x80) {
			x = Integer.toString(c);
		} else if(c < Character.MAX_VALUE) {
			x = String.format("'%c'", (char)c);
		} else {
			x = Integer.toString(c);
		}
		return x;
	}

	//
	private boolean isLookahead(Object o) {
		return o != null && o.toString().startsWith(LOK);
	}

	//
	private void printintv1(PrintStream out, Interval v, boolean els,
			Object o) {
		String s, t, x, y, z;
		int c, d;

		s = v.isInfimumClosed() ? ">=" : ">";
		t = v.isSupremumClosed() ? "<=" : "<";
		c = ((Integer)v.getInfimumBound()).intValue();
		d = ((Integer)v.getSupremumBound()).intValue();
		x = tostr(c);
		y = tostr(d);
		z = isLookahead(o) ? "__l__ && " : "";
		if(v.isClosed() && c == d) {
			out.format("(%s$c == %s)", z, x);
		} else {
			out.format("(%s$c %s %s && $c %s %s)", z, s, x, t, y);
		}
	}

	//
	private void printintv(PrintStream out, Range r, boolean els,
			Object o) {
		String a, d = "";

		a = els ? "\t\t\t} else if" : "\t\t\tif";
		out.format("%s(", a);
		for(Interval v : r.intervals()) {
			out.print(d);
			printintv1(out, v, els, o);
			d = " || ";
		}
		out.format(") {\n");
	}

	//
	private void printobj(PrintStream out, Object o, boolean els,
			Object m) {
		String a, z;

		a = els ? "\t\t\t} else if" : "\t\t\tif";
		z = isLookahead(m) ? "__l__ && " : "";
		out.format("%s(%s$c.equals(\"%s\")) {\n", a, z, o.toString());
	}

	//
	private void printclchar(PrintStream out, char c, String a,
			String z) {
		String s;

		if(c == '\\' || c == '\'') {
			s = String.format("Character.valueOf('\\%c')", c);
		} else if(c < 0x20) {
			s = String.format("Character.valueOf((char)%d)", (int)c);
		} else {
			s = String.format("Character.valueOf('%c')", c);
		}
		out.format("%s(%s%s.equals($c)) {\n", a, z, s);
	}

	//
	private boolean printclass1(PrintStream out, Object o, boolean els,
			Object m) {
		String a, s, z;
		Matcher t;

		a = els ? "\t\t\t} else if" : "\t\t\tif";
		z = isLookahead(m) ? "__l__ && " : "";
		if(o instanceof Character) {
			printclchar(out, ((Character)o).charValue(), a, z);
		} else if(o instanceof Integer) {
			printclchar(out, (char)((Integer)o).intValue(), a, z);
		} else if(E_CONST.matcher(s = o.toString()).matches()) {
			out.format("%s(%s%s.equals($c)) {\n", a, z, s);
		} else if((t = _STR.matcher(s)).matches()) {
			out.format("%s(%s\"%s\".equals($c)) {\n", a, z,
					escapeprint(t.group(1)));
		} else if((t = _CLS.matcher(s)).matches()) {
			// do later
			return false;
		} else if(s.length() == 1) {
			printclchar(out, s.charAt(0), a, z);
			out.format("\t\t\t} else if(%s\"%s\".equals($c)) {\n", z,
					escapeprint(s));
		} else {
			out.format("%s(%s\"%s\".equals($c)) {\n", a, z,
					escapeprint(s));
		}
		return true;
	}

	//
	private boolean printclass2(PrintStream out, Object o, boolean els,
			Object m) {
		String a, s, z;
		Matcher t;

		a = els ? "\t\t\t} else if" : "\t\t\tif";
		z = isLookahead(m) ? "__l__ && " : "";
		if(o instanceof Character) {
			// done former
		} else if(o instanceof Integer) {
			// done former
		} else if(E_CONST.matcher(s = o.toString()).matches()) {
			// done former
		} else if((t = _STR.matcher(s)).matches()) {
			// done former
		} else if((t = _CLS.matcher(s)).matches()) {
			out.format("%s(%s$c instanceof %s) {\n", a, z, t.group(1));
			return true;
		} else if(s.length() == 1) {
			// done former
		} else {
			// done former
		}
		return false;
	}

	//
	private String outln(boolean els, PrintStream out, String s) {
		if(els) {
			out.printf("\t\t\t} else if(%s) {\n", s);
		} else {
			out.printf("\t\t\tif(%s) {\n", s);
		}
		return "\t";
	}

	private boolean putend(String s, PrintStream out,
			DFAState<Object, ?, Void> dfa, boolean els, int[] all) {
		DFABuilder.DBS b;
		TMBuilder.DBS x;
		Object o;

		if(dfa instanceof DFABuilder.DBS &&
				(b = ((DFABuilder.DBS)dfa).getEnd()) != null) {
			outln(els, out, s);
			if((o = ((DFABuilder.DBS)dfa).getMealyEnd()) != null) {
				out.format("\t\t\t\t%s\n", o.toString());
			}
			out.format("\t\t\t\tSTATE = %d;\n", getStateNo(b));
			out.format("\t\t\t\treturn 1;\n");
			els = true;
			all[0]++;
		} else if(dfa instanceof TMBuilder.DBS &&
				(x = ((TMBuilder.DBS)dfa).getEnd()) != null) {
			outln(els, out, s);
			printIntervalAction(out, dfa,
					((TMBuilder.DBS)dfa).getMealyEnd());
			out.format("\t\t\t\tSTATE = %d;\n", getStateNo(x));
			out.format("\t\t\t\treturn 1;\n");
			els = true;
			all[0]++;
		}
		return els;
	}

	private boolean putuseredges(PrintStream out,
			DFAState<Object, ?, Void> dfa, boolean els) {
		DFABuilder.DBS b;
		Set<String> k;
		Object o;

		if(dfa instanceof DFABuilder.DBS &&
				(k = ((DFABuilder.DBS)dfa).getUserEdges()) != null) {
			for(String x : k) {
				b = ((DFABuilder.DBS)dfa).getUserEdge(x);
				o = ((DFABuilder.DBS)dfa).getUserMealyEdge(x);
				if(els) {
					out.printf("\t\t\t} else if(%s) {\n", x);
				} else {
					out.printf("\t\t\tif(%s) {\n", x);
				}

				if(o != null) {
					out.format("\t\t\t\t%s\n", o.toString());
				}
				out.format("\t\t\t\tSTATE = %d;\n", getStateNo(b));
				out.format("\t\t\t\treturn 1;\n");
				els = true;
			}
		}
		return els;
	}

	private boolean putothers(PrintStream out,
			DFAState<Object, ?, Void> dfa, boolean els, String so,
			int[] all) {
		DFABuilder.DBS a, b;
		TMBuilder.DBS x;
		String s, t;
		Object o;

		if(dfa instanceof TMBuilder.DBS) {
			if((x = ((TMBuilder.DBS)dfa).getOthers()) != null) {
				s = outln(els, out, so);
				printIntervalAction(out, dfa,
						((TMBuilder.DBS)dfa).getMealyOthers());
				out.format("%s\t\t\tSTATE = %d;\n", s, getStateNo(x));
				out.format("%s\t\t\treturn 1;\n", s);
				all[0]++;
				return true;
			} else {
				return els;
			}
		} else if(!(dfa instanceof DFABuilder.DBS)) {
			return els;
		} else if((b = (a = (DFABuilder.DBS)dfa).getOthers()) != null) {
			s = outln(els, out, so);
			if((o = a.getMealyOthers()) != null) {
				out.format("%s\t\t\t%s\n", s, o.toString());
			}
			out.format("%s\t\t\tSTATE = %d;\n", s, getStateNo(b));
			out.format("%s\t\t\treturn 1;\n", s);
			all[0]++;
			return true;
		} else if((b = a.getRecursive()) == null) {
			return els;
		} else if(a.getRecursiveName().startsWith("g:")) {
			s = outln(els, out, so);
			t = a.getRecursiveName().substring(2);
			out.format("%s\t\t\t__stkpush(%d, new LR%sEngine());\n", s,
					getStateNo(b), t);
			out.format("%s\t\t\tSTATE = 0;\n", s);
			out.format("%s\t\t\treturn NINA_ACCEPT;\n", s);
			all[0]++;
			return true;
		} else {
			s = outln(els, out, so);
			t = ReplaceStrangeChar.replace(a.getRecursiveName());
			out.format("%s\t\t\t__stkpush(%d, ENGINE_%s);\n", s,
					getStateNo(b), t);
			out.format("%s\t\t\tSTATE = 0;\n", s);
			out.format("%s\t\t\treturn NINA_ACCEPT;\n", s);
			all[0]++;
			return true;
		}
	}

	//
	private void printNextStep(PrintStream out,
			DFAState<Object, ?, Void> d) {
		out.format("\t\t\t\tSTATE = %d;\n", getStateNo(d));
		out.println("\t\t\t\treturn 1;");
	}

	//
	private void checkRange(Map<Interval, Object> r, Range v,
			Object m) {
		for(Map.Entry<Interval, Object> t : r.entrySet()) {
			if(!t.getKey().independentOf(v) &&
					isLookahead(t.getValue()) == isLookahead(m)) {
				options.pwarn("nondeterministic", v.toString());
			}
		}

		for(Interval z : v.intervals()) {
			r.put(z, m);
		}
	}

	//
	private void checkSet(Map<Object, Object> r, Object v,
			Object m) {
		for(Map.Entry<Object, Object> t : r.entrySet()) {
			if(t.getKey().equals(v) &&
					isLookahead(t.getValue()) == isLookahead(m)) {
				options.pwarn("nondeterministic", v.toString());
			}
		}
		r.put(v, m);
	}

	//
	private boolean printDynamicTransition(PrintStream out,
			DFAState<Object, ?, Void> dfa, boolean els) {
		DFAState<Object, ?, Void> d;
		String s;

		if(dfa instanceof DFABuilder.DBS &&
				(d = ((DFABuilder.DBS)dfa).getDynamic()) != null) {
			s = ((DFABuilder.DBS)dfa).getDynamicVariable();
			out.print("\t\t\t");
			if(els)  out.print("} else ");
			out.printf("if(__l__ && __startWith(%s, $c)) {\n", s);
			out.format("\t\t\t\t__stkpush(%d, new DynamicEngine(%s, %d));\n",
					getStateNo(d), s, getStateNo(dfa));
			out.format("\t\t\t\tSTATE = 0;\n", s);
			out.format("\t\t\t\treturn NINA_ACCEPT;\n", s);
			return true;
		} else {
			return els;
		}
	}

	//
	private void printWritetape(PrintStream out, String a) {
		if(a.length() == 0) {
			// do nothing
		} else if(a.length() > 1) {
			out.printf("\t\t\t\t_writetape(\"%s\");\n", a);
		} else if(a.charAt(0) == '\\' || a.charAt(0) == '\'') {
			out.printf("\t\t\t\t_writetape('\\%c');\n",
					a.charAt(0));
		} else {
			out.printf("\t\t\t\t_writetape('%c');\n",
					a.charAt(0));
		}
	}

	//
	private void printIntervalAction(PrintStream out,
			DFAState<Object, ?, Void> d, Object o) {
		Matcher m;
		String s;

		if(d instanceof TMBuilder.DBS) {
			if(o == null) {
				out.printf("\t\t\t\t_movetape(R);\n");
			} else if((s = o.toString()).equals("UNGET($c);")) {
				// do nothing
			} else if((m = _IAT.matcher(s)).matches()) {
				printWritetape(out, m.group(1));
				if(!m.group(2).equals("*")) {
					out.printf("\t\t\t\t_movetape(%s);\n", m.group(2));
				}
			} else {
				printWritetape(out, s);
			}
		} else if(o != null) {
			out.format("\t\t\t\t%s\n", o.toString());
		}
	}

	//
	private void printState(PrintStream out,
			DFAState<Object, ?, Void> dfa) {
		Map<Interval, Object> r = new HashMap<Interval, Object>();
		int sn = getStateNo(dfa), c;
		Map<Object, Range> z;
		int[] a = new int[1];
		boolean els = false;
		Object o;

		out.format("\t\tcase %d:\n", sn);

		// if the state has only empty transition
		if(dfa instanceof DFABuilder.DBS &&
				((DFABuilder.DBS)dfa).isOnlyEmptyTransition()) {
			printNextStep(out, ((DFABuilder.DBS)dfa).getOthers());
			return;
		}

		// classify edges
		z = classifyEdges(dfa);

		// with lookahead
		for(Range v : z.values()) {
			if(v.isEmpty())  continue;
			c = infimumbound(v);
			if(v.isInfimumOpen())  c++;
			if(!isLookahead(o = dfa.getLabelInt(c)))  continue;

			checkRange(r, v, o);
			printintv(out, v, els, o);
			printIntervalAction(out, dfa, o);
			printNextStep(out, dfa.goInt(c));
			els = true;
		}

		// without lookahead
		for(Range v : z.values()) {
			if(v.isEmpty())  continue;
			c = infimumbound(v);
			if(v.isInfimumOpen())  c++;
			if(isLookahead(o = dfa.getLabelInt(c)))  continue;

			checkRange(r, v, o);
			printintv(out, v, els, o = dfa.getLabelInt(c));
			printIntervalAction(out, dfa, o);
			printNextStep(out, dfa.goInt(c));
			els = true;
		}

		// dynamic automaton
		els = printDynamicTransition(out, dfa, els);

		a[0] = 0;
		els = putend("$c < 0", out, dfa, els, a);
		els = putuseredges(out, dfa, els);
		els = putothers(out, dfa, els, "$c >= 0", a);
		if(els)  out.println("\t\t\t}");
		if(a[0] < 2)  out.println("\t\t\treturn 0;");
	}

	//
	private void printObjectState(PrintStream out,
			DFAState<Object, ?, Void> dfa) {
		Map<Object, Object> r = new HashMap<Object, Object>();
		int sn = getStateNo(dfa);
		int[] a = new int[1];
		boolean els = false;
		Object o;

		out.format("\t\tcase %d:\n", sn);

		// if the state has only empty transition
		if(dfa instanceof DFABuilder.DBS &&
				((DFABuilder.DBS)dfa).isOnlyEmptyTransition()) {
			printNextStep(out, ((DFABuilder.DBS)dfa).getOthers());
			return;
		}

		// with lookahead
		for(Object p : dfa.getAlphabets()) {
			if(!isLookahead(o = dfa.getLabel(p)))  continue;

			checkSet(r, p, o);
			printobj(out, p, els, o);
			printIntervalAction(out, dfa, o);
			printNextStep(out, dfa.go(p));
			els = true;
		}

		// without lookahead
		for(Object p : dfa.getAlphabets()) {
			if(isLookahead(o = dfa.getLabel(p)))  continue;

			checkSet(r, p, o);
			printobj(out, p, els, o = dfa.getLabel(p));
			printIntervalAction(out, dfa, o);
			printNextStep(out, dfa.go(p));
			els = true;
		}

		// dynamic automaton
		els = printDynamicTransition(out, dfa, els);

		a[0] = 0;
		els = putend("$c == null", out, dfa, els, a);
		els = putuseredges(out, dfa, els);
		els = putothers(out, dfa, els, "$c != null", a);
		if(els)  out.println("\t\t\t}");
		if(a[0] < 2)  out.println("\t\t\treturn 0;");
	}

	//
	private void printClassState(PrintStream out,
			DFAState<Object, ?, Void> dfa) {
		Map<Object, Object> r = new HashMap<Object, Object>();
		int sn = getStateNo(dfa);
		int[] a = new int[1];
		boolean els = false;
		Object o;

		out.format("\t\tcase %d:\n", sn);

		// if the state has only empty transition
		if(dfa instanceof DFABuilder.DBS &&
				((DFABuilder.DBS)dfa).isOnlyEmptyTransition()) {
			printNextStep(out, ((DFABuilder.DBS)dfa).getOthers());
			return;
		}

		// with lookahead pass 1
		for(Object p : dfa.getAlphabets()) {
			if(!isLookahead(o = dfa.getLabel(p)))  continue;
			if(!printclass1(out, p, els, o = dfa.getLabel(p))) {
				continue;
			}
			checkSet(r, p, o);
			printIntervalAction(out, dfa, o);
			printNextStep(out, dfa.go(p));
			els = true;
		}

		// with lookahead pass 2
		for(Object p : dfa.getAlphabets()) {
			if(!isLookahead(o = dfa.getLabel(p)))  continue;
			if(!printclass2(out, p, els, o = dfa.getLabel(p))) {
				continue;
			}
			checkSet(r, p, o);
			printIntervalAction(out, dfa, o);
			printNextStep(out, dfa.go(p));
			els = true;
		}

		// without lookahead pass 1
		for(Object p : dfa.getAlphabets()) {
			if(isLookahead(o = dfa.getLabel(p)))  continue;
			if(!printclass1(out, p, els, o = dfa.getLabel(p))) {
				continue;
			}
			checkSet(r, p, o);
			printIntervalAction(out, dfa, o);
			printNextStep(out, dfa.go(p));
			els = true;
		}

		// without lookahead pass 2
		for(Object p : dfa.getAlphabets()) {
			if(isLookahead(o = dfa.getLabel(p)))  continue;
			if(!printclass2(out, p, els, o = dfa.getLabel(p))) {
				continue;
			}
			checkSet(r, p, o);
			printIntervalAction(out, dfa, o);
			printNextStep(out, dfa.go(p));
			els = true;
		}

		// dynamic automaton
		els = printDynamicTransition(out, dfa, els);

		a[0] = 0;
		els = putend("$c == null", out, dfa, els, a);
		els = putuseredges(out, dfa, els);
		els = putothers(out, dfa, els, "$c != null", a);
		if(els)  out.println("\t\t\t}");
		if(a[0] < 2)  out.println("\t\t\treturn 0;");
	}

	//
	private boolean isProcessed(DFAState<Object, ?, Void> state) {
		return containsState(state);
	}

	//
	private int getDeadStateNo() {
		Object s;

		s = builder.getDeadState();
		return containsState(s) ? stateNo(s) : -1;
	}

	//
	private int getStateNoByLabel(String l) {
		Object s;

		s = builder.getStateByLabel(l);
		return containsState(s) ? stateNo(s) : -1;
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.AbstractNinaTranslator#printStates(java.io.PrintStream)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public void printStates(PrintStream out) {
		DFAState<Object, ?, Void> s;

		getStateNo(dfa.getInitialState());
		while(!isStackEmpty()) {
			printState(out, popStack());
		}

		for(String t : builder.getLabels()) {
			s = (DFAState<Object, ?, Void>)builder.getStateByLabel(t);
			if(s != null && !isProcessed(s)) {
				getStateNo(s);
				while(!isStackEmpty()) {
					printState(out, popStack());
				}
			}
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.AbstractNinaTranslator#printStates(java.io.PrintStream)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public void printObjectStates(PrintStream out) {
		DFAState<Object, ?, Void> s;

		getStateNo(dfa.getInitialState());
		while(!isStackEmpty()) {
			printObjectState(out, popStack());
		}

		for(String t : builder.getLabels()) {
			s = (DFAState<Object, ?, Void>)builder.getStateByLabel(t);
			if(s != null && !isProcessed(s)) {
				getStateNo(s);
				while(!isStackEmpty()) {
					printObjectState(out, popStack());
				}
			}
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.AbstractNinaTranslator#printObjectStates(java.io.PrintStream)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public void printClassStates(PrintStream out) {
		DFAState<Object, ?, Void> s;

		getStateNo(dfa.getInitialState());
		while(!isStackEmpty()) {
			printClassState(out, popStack());
		}

		for(String t : builder.getLabels()) {
			s = (DFAState<Object, ?, Void>)builder.getStateByLabel(t);
			if(s != null && !isProcessed(s)) {
				getStateNo(s);
				while(!isStackEmpty()) {
					printClassState(out, popStack());
				}
			}
		}
	}

	//
	private void printPStates(PrintStream out, Set<Integer> a) {
		String d = "\t\treturn (";

		if(a.size() == 0) {
			out.println("\t\treturn false;");
		} else {
			for(Integer i : a) {
				out.print(d);
				out.format("STATE == %d", i);
				d = " ||\n\t\t\t\t";
			}
			out.println(");");
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.AbstractNinaTranslator#printAcceptStates(java.io.PrintStream)
	 */
	@Override
	public void printAcceptStates(PrintStream out) {
		printPStates(out, acceptsIterable());
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.AbstractNinaTranslator#printDeadStates(java.io.PrintStream)
	 */
	@Override
	public void printDeadStates(PrintStream out) {
		printPStates(out, deadsIterable());
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.AbstractNinaTranslator#printEmptyTransitionStates(java.io.PrintStream)
	 */
	@Override
	public void printEmptyTransitionStates(PrintStream out) {
		printPStates(out, emptyTransitionsIterable());
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.AbstractNinaTranslator#printAcceptToken(java.io.PrintStream)
	 */
	@Override
	public void printAcceptToken(PrintStream out) {
		int m = -1, q;
		NinaState n;
		String x, p;

		for(DFAState<Object, ?, Void> s : stateKeys()) {
			if(!s.isAccepted())  continue;
			p = null;  m = -1;
			out.format("\t\tcase %d:\n", stateNo(s));
			for(Object a : s.getAccepted()) {
				if(a == null || !(a instanceof NinaState)) {
					// do nothing
				} else if((q = ((NinaState)a).getPriority()) < 0) {
					// do nothing
				} else if(q > m) {
					m = q;
				}
			}

			for(Object a : s.getAccepted()) {
				if(a == null || !(a instanceof NinaState)) {
					// do nothing
				} else if((x = (n = (NinaState)a).getLabel()) == null) {
					// do nothing
				} else if((x = x.trim()).equals("")) {
					// do nothing
				} else if(m >= 0) {
					if(n.getPriority() == m)  p = x;
				} else if(RET_CONST.matcher(x).matches()) {
					p = x;  break;
				} else {
					if(p != null) {
						getOptions().pwarn("ambiguousaccept");
					}
					p = x;
				}
			}
			out.format("\t\t\t%s\n", p != null ? p : "return $$;");
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.AbstractNinaTranslator#printAcceptToken(java.io.PrintStream)
	 */
	@Override
	public void printActions(PrintStream out) {
		boolean[] a = new boolean[1];
		String x, p;

		for(DFAState<Object, ?, Void> s : stateKeys()) {
			p = null;
			out.format("\t\tcase %d:\n", stateNo(s));
			if((x = s.toString()) == null) {
				// do nothing
			} else if((x = x.trim()).equals("")) {
				// do nothing
			} else {
				p = x;
			}

			if(p == null) {
				out.println("\t\t\tbreak;");
			} else {
				p = ReplaceAction.replace(p, a, this, stateNo(s),
						builder.getLabelByState(s));
				for(String v : p.split("\n")) {
					out.format("\t\t\t%s\n", v);
				}

				if(!a[0]) {
					out.println("\t\t\tbreak;");
				}
			}
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.AbstractNinaTranslator#printImports(java.io.PrintStream)
	 */
	@Override
	public void printImports(List<String> imp, PrintStream out) {
		for(String s : imp) {
			out.format("import %s;\n", s);
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.AbstractNinaTranslator#printAcceptStates(java.io.PrintStream)
	 */
	@Override
	public void printIsEnd(PrintStream out) {
		Set<Integer> t = new HashSet<Integer>();
		String d = "\t\treturn (";

		for(DFAState<Object, ?, Void> s : stateKeys()) {
			if(!(s instanceof DFABuilder.DBS)) {
				// do nothing
			} else if(((DFABuilder.DBS)s).getEnd() != null) {
				t.add(stateNo(s));
			}
		}

		if(t.size() == 0) {
			out.println("\t\treturn false;");
		} else {
			for(Integer i : t) {
				out.print(d);
				out.format("STATE == %d", i);
				d = " ||\n\t\t\t\t";
			}
			out.println(");");
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.AbstractNinaTranslator#printAcceptStates(java.io.PrintStream)
	 */
	@Override
	public void printDeadState(String n, PrintStream out) {
		out.printf("\t\t\treturn %d;\n", getDeadStateNo());
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.AbstractNinaTranslator#printRecover(java.io.PrintStream)
	 */
	@SuppressWarnings("unchecked")
	@Override
	protected void printRecover(PrintStream out) {
		DFAState<Object, ?, Void> s;
		String d = "";

		for(String x : builder.getLabels()) {
			if(x.endsWith("Exception")) {
				s = (DFAState<Object, ?, Void>)builder.getStateByLabel(
						x);
				out.printf("\t\t\t%sif(e instanceof %s) {\n", d, x);
				out.printf("\t\t\t\treturn %d;\n", getStateNo(s));
				d = "} else ";
			}
		}

		if(d.equals("")) {
			out.println("\t\t\treturn -1;");
		} else {
			out.println("\t\t\t} else {");
			out.println("\t\t\t\treturn -1;");
			out.println("\t\t\t}");
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.AbstractNinaTranslator#printAcceptStates(java.io.PrintStream)
	 */
	@Override
	public void printFinallyState(PrintStream out) {
		out.printf("\t\t\treturn %d;\n", getStateNoByLabel("finally"));
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.AbstractNinaTranslator#printInitTrap(java.io.PrintStream)
	 */
	@Override
	protected void printInitTrap(PrintStream out) {
		// do nothing
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.AbstractNinaTranslator#printNFADeadState(java.io.PrintStream)
	 */
	@Override
	protected void printNFADeadState(PrintStream out) {
		out.print("return (false");
		for(DFAState<Object, ?, Void> s : stateKeys()) {
			if(s.isDead()) {
				out.print(" || ");
				out.print("STATE == ");
				out.print(stateNo(s));
			}
		}
		out.println(");");
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.AbstractNinaTranslator#printAttrs(java.io.PrintStream)
	 */
	@Override
	protected void printAttrs(PrintStream out) {
		// do nothing
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.AbstractNinaTranslator#printConstants(java.io.PrintStream)
	 */
	@Override
	protected void printConstants(PrintStream out) {
		for(Map.Entry<String, Integer> t : quadro.getConstantMap().entrySet()) {
			out.printf("\t\tpublic static final int %s = %d;\n",
					t.getKey(), t.getValue().intValue());
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.AbstractNinaTranslator#reportStatistics(java.io.PrintStream)
	 */
	@Override
	public void reportStatistics(PrintStream std) {
		getOptions().print("statheader");
		getOptions().print("statstates", stateSize());
		getOptions().print("stataccept", acceptsSize());
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.AbstractNinaTranslator#openScript()
	 */
	@Override
	protected InputStream openScript(ShEnvironment env) {
		if(options.getInjectFile() == null) {
			return NinaTranslator.class.getResourceAsStream(
					"/net/morilib/nina/translate/nina_template." +
					getMachine() +
					".java.sh");
		} else {
			env.bind("TRANS_COMMAND",
					"nina_template." + getMachine() + ".java.sh");
			return NinaTranslator.class.getResourceAsStream(
					"/net/morilib/nina/translate/nina_injector.sh");
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.NinaTranslator#getExtension()
	 */
	@Override
	public String getExtension() {
		return ".java";
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.AbstractNinaTranslator#openOutput()
	 */
	@Override
	protected PrintStream openOutput() throws IOException {
		return new PrintStream(getOptions().getOutputStream(
				getOptions().getTitleFile(getExtension())), true);
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.AbstractNinaTranslator#newPrototype()
	 */
	@Override
	protected AbstractNinaTranslator newPrototype() {
		NinaTranslatorJava r;

		r = new NinaTranslatorJava();
		r.quadro = quadro;
		return r;
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.AbstractNinaTranslator#appendValue(java.lang.StringBuffer, java.lang.StringBuffer)
	 */
	protected void appendValue(StringBuffer ot, StringBuffer b1) {
		Object o = builder.getStateByLabel(b1.toString());
		String t = builder.getTypeByLabel(b1.toString());

		if(o == null) {
			ot.append('$');
			ot.append(b1);
		} else if(t == null) {
			ot.append("(__stv[__slen - 1][");
			ot.append(stateNo(o));
			ot.append("])");
		} else if(t.equals("byte")) {
			ot.append("((Byte)(__stv[__slen - 1][");
			ot.append(stateNo(o));
			ot.append("])).byteValue()");
		} else if(t.equals("short")) {
			ot.append("((Short)(__stv[__slen - 1][");
			ot.append(stateNo(o));
			ot.append("])).shortValue()");
		} else if(t.equals("int")) {
			ot.append("((Integer)(__stv[__slen - 1][");
			ot.append(stateNo(o));
			ot.append("])).intValue()");
		} else if(t.equals("long")) {
			ot.append("((Long)(__stv[__slen - 1][");
			ot.append(stateNo(o));
			ot.append("])).longValue()");
		} else if(t.equals("double")) {
			ot.append("((Double)(__stv[__slen - 1][");
			ot.append(stateNo(o));
			ot.append("])).doubleValue()");
		} else if(t.equals("float")) {
			ot.append("((Byte)(__stv[__slen - 1][");
			ot.append(stateNo(o));
			ot.append("])).floatValue()");
		} else if(t.equals("char")) {
			ot.append("((Character)(__stv[__slen - 1][");
			ot.append(stateNo(o));
			ot.append("])).charValue()");
		} else {
			ot.append("((");
			ot.append(t);
			ot.append(")(__stv[__slen - 1][");
			ot.append(stateNo(o));
			ot.append("]))");
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.AbstractNinaTranslator#appendLvalue(java.lang.StringBuffer, java.lang.StringBuffer)
	 */
	protected void appendLvalue(StringBuffer ot, StringBuffer b1) {
		Object o = builder.getStateByLabel(b1.toString());

		if(o == null) {
			ot.append('@');
			ot.append(b1);
		} else {
			ot.append("(__stv[__slen - 1][");
			ot.append(stateNo(o));
			ot.append("])");
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.AbstractNinaTranslator#appendMyPosition(java.lang.StringBuffer, int)
	 */
	protected void appendMyPosition(StringBuffer ot,  String ln,
			int cn) {
		ot.append("(__stv[__slen - 1][");
		ot.append(cn);
		ot.append("])");
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.AbstractNinaTranslator#appendReturn(java.lang.StringBuffer)
	 */
	@Override
	protected void appendReturn(StringBuffer ot) {
		ot.append("$$");
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.AbstractNinaTranslator#getConstName(java.lang.String)
	 */
	@Override
	protected String getConstName(String t) {
		if(t.equals("EXIT")) {
			return "NINA_ACCEPT";
		} else if(t.equals("FAIL")) {
			return "NINA_FAIL";
		} else if(t.equals("ACCEPT")) {
			return "NINA_HALT_ACCEPT";
		} else if(t.equals("REJECT")) {
			return "NINA_HALT_REJECT";
		} else {
			return null;
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.AbstractNinaTranslator#appendYield(java.lang.StringBuffer, java.lang.String)
	 */
	@Override
	protected void appendYield(StringBuffer ot, String b) {
		ot.append("yieldObject = (").append(b).append(");");
		ot.append("return NINA_YIELD;");
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.AbstractNinaTranslator#getLRTranslator(net.morilib.nina.Quadro)
	 */
	@Override
	protected LRTranslator getLRTranslator(Quadro q) {
		return new LRTranslatorJava(q);
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.AbstractNinaTranslator#setLocalenv(net.morilib.sh.ShEnvironment)
	 */
	@Override
	protected void setLocalenv(Quadro q, ShEnvironment env) {
		env.bind("VALTYPE", q.getOptionNvl("valtype", "Object"));
	}

}
