/*
 * 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.HashSet;
import java.util.Map;
import java.util.Set;

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 LRTranslatorC extends LRTranslator {

	private abstract class Puts implements ShProcess {

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

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

		//
		abstract void end(String n, 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(args[1], lr, this, k, e.getKey(), f,
								stdout);
						f = h != null ? f : "} else ";
						g = h != null ? h : g;
					}
				}

				if(l != null) {
					stdout.printf("\t\t\t\t%s{\n", f);
					put(args[1], stdout, lr, f, k, l);
					stdout.printf("\t\t\t\t}\n");
				} else {
					if(!f.equals(""))  stdout.printf("\t\t}\n");
					end(args[1], 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(String n, 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(String n, PrintStream out, LRObject lr, int st,
				SubautomatonTerminal g) {
			LRAction a;

			if(g == null) {
				out.printf("\t\t\treturn -1;\n");
			} else if((a = lr.getTable().action(st, g)).isShift()) {
				out.printf("\t\t\t__stk_push_C(__this__, %d, %s);\n",
						a.getNextStateID(), g.getSubautomaton());
				out.printf("\t\t\treturn %d;\n", -(a.getNextStateID() + 2));
			} else {
				out.printf("\t\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(String n, 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__ = 0;\n", l);
				s = LRActionConverter.translate(r, lr.getDefinition(),
						o.toString(), LRTranslatorC.this);
				out.printf("\t\t\t%s\n", s);
			} else if(l > 0) {
				s = LRActionConverter.translate(r, lr.getDefinition(),
						"__b__ = $1;", LRTranslatorC.this);
				out.printf("\t\t\t%s\n", s);
			} else {
				out.printf("\t\t\t__b__ = 0;\n", l);
			}
			out.printf("\t\t\tLR%sEngine__pop(__this__, %d);\n", n, l);
			out.printf("\t\t\t__k = LR%sEngine_getGoto(__this__,\n", n);
			out.printf("\t\t\t\t\tLR%sEngine__refsyn(__this__), %d);\n",
					n, lr.getNonterminalNumber(r.getLeftSymbol()));
			out.printf("\t\t\tLR%sEngine__push(__this__, __k, __b__);\n", n);
			out.printf("\t\t\treturn __k;\n");
		}

		@Override
		void end(String n, 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(String n, 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 NINA_TRUE;\n");
			}
		}

		@Override
		void end(String n, PrintStream out, LRObject lr, int st,
				SubautomatonTerminal g) {
			out.printf("\t\t\treturn NINA_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\tTHROWERROR_C(b);  return 0;\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\tSTATE = %d << LRSTATESHIFT;\n", s);
			out.printf("\t\t__this__->__synptr[0].state = %d << LRSTATESHIFT;\n", s);
			return 0;
		}

	}

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

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

		if(g instanceof CharacterTerminal) {
			c = ((CharacterTerminal)g).getCharacter();
			if(c == '\'' || c == '\\') {
				out.printf("\t\t%sif(LR%sEngine__eq(__t, '\\%c')) {\n",
						f, n, (char)c);
			} else if(c == '\n') {
				out.printf("\t\t%sif(LR%sEngine__eq(__t, '\\n')) {\n",
						f, n);
			} else if(c == '\r') {
				out.printf("\t\t%sif(LR%sEngine__eq(__t, '\\r')) {\n",
						f, n);
			} else if(c == '\t') {
				out.printf("\t\t%sif(LR%sEngine__eq(__t, '\\t')) {\n",
						f, n);
			} else {
				out.printf("\t\t%sif(LR%sEngine__eq(__t, '%c')) {\n",
						f, n, (char)c);
			}
		} else if(g instanceof ConstantTerminal) {
			x = ((ConstantTerminal)g).getConstant();
			out.printf("\t\t%sif(LR%sEngine__eq(__t, %s)) {\n",
					f, n, x);
		} else if(g instanceof PrecedenceTerminal) {
			_put0(n, lr, ((PrecedenceTerminal)g).getWrapee(), f, out);
		}
	}

	//
	private SubautomatonTerminal _put(String n, LRObject lr, Puts p,
			int st, GrammarSymbol g, String f, PrintStream out) {
		if(g instanceof CharacterTerminal) {
			_put0(n, lr, g, f, out);
			p.put(n, out, lr, f, st, g);
		} else if(g instanceof ConstantTerminal) {
			_put0(n, lr, g, f, out);
			p.put(n, out, lr, f, st, g);
		} else if(g instanceof PrecedenceTerminal) {
			_put0(n, lr, g, f, out);
			p.put(n, out, lr, f, st, g);
//		} else if(g instanceof SubautomatonTerminal) {
//			return (SubautomatonTerminal)g;
		} else if(g == ContextFreeGrammar.ENDMARKER) {
			out.printf("\t\t%sif(LR%sEngine__end(__t)) {\n", f, n);
			p.put(n, out, lr, f, st, g);
		} else {
			throw new DecultureException(g.toString());
		}
		return null;
	}

	//
	private class PutTokenConst implements ShProcess {

		@Override
		public int main(ShEnvironment env, ShFileSystem fs,
				InputStream in, PrintStream out,
				PrintStream err, String... args)
				throws IOException {
			Set<GrammarSymbol> t = new HashSet<GrammarSymbol>();
			Map<GrammarSymbol, LRAction> s;
			LRObject lr = getlr(args);
			int no = -1;
			String x;

			for(int k = 0; k < lr.getTable().getSizeOfStates(); k++) {
				s = lr.getTable().actions(k);
				for(GrammarSymbol g : s.keySet()) {
					if(g instanceof PrecedenceTerminal) {
						g = ((PrecedenceTerminal)g).getWrapee();
					}

					if(!t.contains(g) && g instanceof ConstantTerminal) {
						x = ((ConstantTerminal)g).getConstant();
						out.printf("#define %s %d\n", x, no--);
						t.add(g);
					}
				}
			}
			return 0;
		}

	}

	/* (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) {
		buf.append(String.format("((__this__->__synptr - %d * 2)->val)", z));
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.LRTranslator#setCommands(net.morilib.nina.translate.sh.ShNinatBuiltInCommands)
	 */
	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("put_token_const", new PutTokenConst());
		c.putCommand("cat_definition", new CatDefinitionPart());
		c.putCommand("echo_lexer", new EchoLexer());
	}

}
