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

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

import net.morilib.automata.lr.ContextFreeGrammar;
import net.morilib.automata.lr.ContextFreeRule;
import net.morilib.automata.lr.GrammarSymbol;
import net.morilib.automata.lr.LRAction;
import net.morilib.automata.lr.Nonterminal;
import net.morilib.automata.lr.Terminal;
import net.morilib.deculture.misc.CharacterTerminal;
import net.morilib.deculture.misc.ConstantTerminal;
import net.morilib.deculture.misc.DecultureException;
import net.morilib.deculture.misc.PrecedenceTerminal;
import net.morilib.deculture.misc.SubautomatonTerminal;
import net.morilib.deculture.parser.DecultureDefinition;
import net.morilib.nina.LRObject;
import net.morilib.nina.Quadro;
import net.morilib.nina.translate.sh.ShNinatBuiltInCommands;
import net.morilib.sh.ShEnvironment;
import net.morilib.sh.ShFileSystem;
import net.morilib.sh.ShProcess;

/**
 * 
 * 
 * @author MORIGUCHI, Yuichiro 2006/07/09
 */
public class LRTranslatorJavaScript extends LRTranslator {

	private abstract class Puts implements ShProcess {

		//
		abstract boolean isput(LRObject lr, int st, GrammarSymbol g);

		//
		abstract void put(PrintStream out, LRObject lr, String f,
				int st, GrammarSymbol g);

		//
		abstract void end(PrintStream out, LRObject lr, int st,
				SubautomatonTerminal g);

		@Override
		public int main(ShEnvironment env, ShFileSystem fs,
				InputStream stdin, PrintStream stdout,
				PrintStream stderr, String... args)
				throws IOException {
			Set<Map.Entry<GrammarSymbol, LRAction>> s;
			SubautomatonTerminal g, h;
			LRObject lr = getlr(args);
			GrammarSymbol l;
			String f;

			for(int k = 0; k < lr.getTable().getSizeOfStates(); k++) {
				stdout.printf("\tcase %d:\n", k);
				s = lr.getTable().actions(k).entrySet();
				f = "";  g = null;  l = null;
				for(Map.Entry<GrammarSymbol, LRAction> e : s) {
					if(!isput(lr, k, e.getKey())) {
						// do nothing
					} else if(e.getKey() == ContextFreeGrammar.ENDMARKER) {
						l = e.getKey();
					} else {
						h = _put(lr, this, k, e.getKey(), f, stdout);
						f = h != null ? f : "} else ";
						g = h != null ? h : g;
					}
				}

				if(l != null) {
					stdout.printf("\t\t%s{\n", f);
					put(stdout, lr, f, k, l);
					stdout.printf("\t\t}\n");
				} else {
					if(!f.equals(""))  stdout.printf("\t\t}\n");
					end(stdout, lr, k, g);
				}
			}
			return 0;
		}

	}

	private class PutShift extends Puts {

		@Override
		boolean isput(LRObject lr, int st, GrammarSymbol g) {
			LRAction a;

			a = lr.getTable().action(st, (Terminal)g);
			return a.isShift();
		}

		@Override
		void put(PrintStream out, LRObject lr, String f, int st,
				GrammarSymbol g) {
			LRAction a;

			a = lr.getTable().action(st, (Terminal)g);
			if(a.isShift()) {
				out.printf("\t\t\treturn %d;\n", a.getNextStateID());
			}
		}

		@Override
		void end(PrintStream out, LRObject lr, int st,
				SubautomatonTerminal g) {
			LRAction a;

			if(g == null) {
				out.printf("\t\treturn -1;\n");
			} else if((a = lr.getTable().action(st, g)).isShift()) {
				out.printf("\t\t$this.__stkpush(%d, $this.ENGINE_%s);\n",
						a.getNextStateID(), g.getSubautomaton());
//				out.printf("\t\tSTATE = 0;\n");
//				out.printf("\t\treturn NINA_ACCEPT;\n");
				out.printf("\t\treturn %d;\n",
						-(a.getNextStateID() + 2));
			} else {
				out.printf("\t\treturn -1;\n");
			}
		}

	}

	private class PutReduce extends Puts {

		@Override
		boolean isput(LRObject lr, int st, GrammarSymbol g) {
			LRAction a;

			a = lr.getTable().action(st, (Terminal)g);
			return a.isReduce();
		}

		@Override
		void put(PrintStream out, LRObject lr, String f, int st,
				GrammarSymbol g) {
			ContextFreeRule r;
			LRAction a;
			Object o;
			String s;
			int l;

			a = lr.getTable().action(st, (Terminal)g);
			if(!a.isReduce())  return;
			r = a.getReduceRule();
			l = r.getDerivedSymbolLength();
			o = lr.getActionMap().get(a.getReduceRule());

			if(o != null) {
				out.printf("\t\t\t__b__ = null;\n", l);
				s = LRActionConverter.translate(r, lr.getDefinition(),
						o.toString(), LRTranslatorJavaScript.this);
				out.printf("\t\t\t%s\n", s);
			} else if(l > 0) {
				s = LRActionConverter.translate(r, lr.getDefinition(),
						"$$ = $1;", LRTranslatorJavaScript.this);
				out.printf("\t\t\t%s\n", s);
			} else {
				out.printf("\t\t\t__b__ = null;\n", l);
			}
			out.printf("\t\t\tthis._pop(%d);\n", l);
			out.printf("\t\t\t__k = this.getGoto(this._refsyn(), %d);\n",
					lr.getNonterminalNumber(r.getLeftSymbol()));
			out.printf("\t\t\tthis._push(__k, __b__);\n");
			out.printf("\t\t\treturn __k;\n");
		}

		@Override
		void end(PrintStream out, LRObject lr, int st,
				SubautomatonTerminal g) {
			out.printf("\t\t\treturn -1;\n");
		}

	}

	private class PutAccept extends Puts {

		@Override
		boolean isput(LRObject lr, int st, GrammarSymbol g) {
			LRAction a;

			a = lr.getTable().action(st, (Terminal)g);
			return a.isAccept();
		}

		@Override
		void put(PrintStream out, LRObject lr, String f, int st,
				GrammarSymbol g) {
			LRAction a;

			a = lr.getTable().action(st, (Terminal)g);
			if(a.isAccept()) {
				out.printf("\t\t\treturn true;\n");
			}
		}

		@Override
		void end(PrintStream out, LRObject lr, int st,
				SubautomatonTerminal g) {
			out.printf("\t\t\treturn false;\n");
		}

	}

	private class PutGoto implements ShProcess {

		@Override
		public int main(ShEnvironment env, ShFileSystem fs,
				InputStream stdin, PrintStream out,
				PrintStream stderr, String... args)
				throws IOException {
			Set<Map.Entry<Nonterminal, Integer>> s;
			LRObject lr = getlr(args);
			String f;
			int n;

			for(int k = 0; k < lr.getTable().getSizeOfStates(); k++) {
				out.printf("\tcase %d:\n", k);
				s = lr.getTable().goTos(k).entrySet();
				f = "";
				for(Map.Entry<Nonterminal, Integer> e : s) {
					n = lr.getNonterminalNumber(e.getKey());
					out.printf("\t\t%sif(__n == %d) {\n", f, n);
					out.printf("\t\t\treturn %d;\n", e.getValue());
					f = "} else ";
				}
				if(!f.equals(""))  out.printf("\t\t}\n");
				out.printf("\t\tthrow \"RuntimeException\";\n");
			}
			return 0;
		}

	}

	private class PutGrammarInitial implements ShProcess {

		@Override
		public int main(ShEnvironment env, ShFileSystem fs,
				InputStream in, PrintStream out, PrintStream err,
				String... args) throws IOException {
			LRObject lr = getlr(args);
			int s = lr.getStartNumber();

			out.printf("\t\t$this.__state = %d << LRSTATESHIFT;\n", s);
			out.printf("\t\tthis.__synarray[0] = %d << LRSTATESHIFT;\n", s);
			return 0;
		}

	}

	//
	private static final Pattern E_CONST = Pattern.compile(
			"([A-Za-z][A-Za-z0-9]*\\.)*[A-Z][A-Z0-9]*");
	private static final Pattern E_STR = Pattern.compile("\"(.*)\"");

	//
	LRTranslatorJavaScript(Quadro q) {
		super(q);
	}

	//
	private void _put0(LRObject lr, GrammarSymbol g, String f,
			PrintStream out) {
		String x;
		int c;

		if(g instanceof CharacterTerminal) {
			c = ((CharacterTerminal)g).getCharacter();
			out.printf("\t\t%sif(this.__eq(__t, %d)) {\n", f, c);
		} else if(g instanceof ConstantTerminal) {
			x = lr.getDefinition().getType(
					((ConstantTerminal)g).getConstant());
			if(x.equalsIgnoreCase("Number") ||
					x.equalsIgnoreCase("String") ||
					x.equalsIgnoreCase("Boolean")) {
				out.printf("\t\t%sif(typeof __t == '%s') {\n",
						f, x.toLowerCase());
			} else if(E_CONST.matcher(x).matches()) {
				out.printf("\t\t%sif(this.__eq(__t, %s)) {\n", f, x);
			} else if(E_STR.matcher(x).matches()) {
				out.printf("\t\t%sif(__eq(__t, %s)) {\n", f, x);
			} else {
				out.printf("\t\t%sif(__t instanceof %s) {\n", f, x);
			}
		} else if(g instanceof PrecedenceTerminal) {
			_put0(lr, ((PrecedenceTerminal)g).getWrapee(), f, out);
		}
	}

	//
	private SubautomatonTerminal _put(LRObject lr, Puts p, int st,
			GrammarSymbol g, String f, PrintStream out) {
		if(g instanceof CharacterTerminal) {
			_put0(lr, g, f, out);
			p.put(out, lr, f, st, g);
		} else if(g instanceof ConstantTerminal) {
			_put0(lr, g, f, out);
			p.put(out, lr, f, st, g);
		} else if(g instanceof PrecedenceTerminal) {
			_put0(lr, g, f, out);
			p.put(out, lr, f, st, g);
//		} else if(g instanceof SubautomatonTerminal) {
//			return (SubautomatonTerminal)g;
		} else if(g == ContextFreeGrammar.ENDMARKER) {
			// do nothing
		} else {
			throw new DecultureException(g.toString());
		}
		return null;
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.LRTranslator#getReturn()
	 */
	@Override
	public String getReturn() {
		return "__b__";
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.LRTranslator#translateVariable(java.lang.StringBuffer, net.morilib.deculture.parser.DecultureDefinition, java.lang.Object, int)
	 */
	@Override
	public void translateVariable(StringBuffer buf,
			DecultureDefinition def, Object g, int z, boolean bury) {
		Matcher m;
		String s;

		if(g instanceof ConstantTerminal) {
			s = ((ConstantTerminal)g).getConstant();
			s = def.getType(s);
			if(s != null && (m = E_STR.matcher(s)).matches()) {
				buf.append(bury ? m.group(1) : s);
			} else {
				buf.append(String.format("(this._refv(%d))", z));
			}
		} else if(g instanceof PrecedenceTerminal) {
			translateVariable(buf, def,
					((PrecedenceTerminal)g).getWrapee(), z, bury);
		} else {
			buf.append(String.format("(this._refv(%d))", z));
		}
	}

	/**
	 * 
	 * @return
	 */
	public void setCommands(ShNinatBuiltInCommands c) {
		c.putCommand("put_shift", new PutShift());
		c.putCommand("put_reduce", new PutReduce());
		c.putCommand("put_accept", new PutAccept());
		c.putCommand("put_goto", new PutGoto());
		c.putCommand("put_grammar_initial", new PutGrammarInitial());
		c.putCommand("cat_definition", new CatDefinitionPart());
		c.putCommand("echo_lexer", new EchoLexer());
	}

}
