/*
 * 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.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;

import net.morilib.automata.DFAState;
import net.morilib.nina.DFABuilder;
import net.morilib.nina.NinaException;
import net.morilib.nina.NinaState;
import net.morilib.nina.translate.sh.ReplaceStrangeChar;
import net.morilib.range.Interval;

/**
 *
 *
 * @author MORIGUCHI, Yuichiro 2013/10/23
 */
public class NinaTranslatorC 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 String LOK = "LOOKAHEAD($c);";

	//
	private boolean[] _usesignal = new boolean[16];

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

	//
	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;
	}

	//
	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 < Character.MAX_VALUE) {
			x = String.format("'%c'", (char)c);
		} else {
			x = Integer.toString(c);
		}
		return x;
	}

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

		s = v.isInfimumClosed() ? ">=" : ">";
		t = v.isSupremumClosed() ? "<=" : "<";
		a = els ? "\t\t} else if" : "\t\tif";
		c = ((Integer)v.getInfimumBound()).intValue();
		d = ((Integer)v.getSupremumBound()).intValue();
		x = tostr(c);
		y = tostr(d);
		z = o != null && o.toString().startsWith(LOK) ?
				"__l__ && " : "";
		if(v.isClosed() && c == d) {
			out.format("%s(%s__c__ == %s) {\n", a, z, x);
		} else {
			out.format("%s(%s__c__ %s %s && __c__ %s %s) {\n",
					a, z, s, x, t, y);
		}
	}

	//
	private String outln(boolean els, PrintStream out) {
		if(els) {
			out.println("\t\t\t} else {");
			return "\t";
		} else {
			return "";
		}
	}

	//
	private void printState(PrintStream out,
			DFAState<Object, ?, Void> dfa) {
		boolean els = false, el2 = true;
		boolean[] z = new boolean[1];
		DFAState<Object, ?, Void> d;
		int sn = getStateNo(dfa), c;
		DFABuilder.DBS a, b;
		Object o;
		String s;

		out.format("\tcase %d:\n", sn);
		for(Interval v : dfa.getAlphabetRanges()) {
			c = ((Integer)v.getInfimumBound()).intValue();
			if(v.isInfimumOpen())  c++;
			printintv(out, v, els, o = dfa.getLabelInt(c));
			if(o != null) {
				out.format("\t\t\t\t%s\n", ReplaceAction.replace(
						o.toString(), z, this, getStateNo(dfa),
						builder.getLabelByState(dfa)));
			}

			// print next step
			d = dfa.goInt(c);
			out.format("\t\t\t__this__->state = %d;\n", getStateNo(d));
			out.println("\t\t\treturn 1;");
			els = true;
		}

		if(dfa instanceof DFABuilder.DBS &&
				(b = ((DFABuilder.DBS)dfa).getEnd()) != null) {
			if(els) {
				out.println("\t\t\t} else if(__c__ < 0) {");
			} else {
				out.println("\t\t\tif(__c__ < 0) {");
			}

			if((o = ((DFABuilder.DBS)dfa).getMealyEnd()) != null) {
				out.format("\t\t\t\t%s\n", ReplaceAction.replace(
						o.toString(), z, this, getStateNo(dfa),
						builder.getLabelByState(dfa)));
			}
			out.format("\t\t\t\t__this__->state = %d;\n", getStateNo(b));
			out.format("\t\t\t\treturn 1;\n");
			els = true;
		}

		if(!(dfa instanceof DFABuilder.DBS)) {
			// do nothing
		} else if((b = (a = (DFABuilder.DBS)dfa).getOthers()) != null) {
			s = outln(els, out);
			if((o = a.getMealyOthers()) != null) {
				out.format("%s\t\t\t%s\n", s, ReplaceAction.replace(
						o.toString(), z, this, getStateNo(dfa),
						builder.getLabelByState(dfa)));
			}
			out.format("%s\t\t\t__this__->state = %d;\n", s, getStateNo(b));
			out.format("%s\t\t\treturn 1;\n", s);
			el2 = false;
		} else if((b = a.getRecursive()) != null) {
			s = outln(els, out);
			out.format("%s\t\t\t__stk_push_C(__this__, %d, %s);\n", s,
					getStateNo(b),
					ReplaceStrangeChar.replace(a.getRecursiveName()));
			out.format("%s\t\t\t__this__->state = 0;\n", s);
			out.format("%s\t\t\treturn -1;\n", s);
			el2 = false;
		}

		if(els)  out.println("\t\t\t}");
		if(el2)  out.println("\t\t\treturn 0;");
	}

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

	/* (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)
	 */
	@Override
	public void printObjectStates(PrintStream out) {
		throw new NinaException("unsupportedobjecttype", "C");
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.AbstractNinaTranslator#printClassStates(java.io.PrintStream)
	 */
	@Override
	public void printClassStates(PrintStream out) {
		throw new NinaException("unsupportedclasstype", "C");
	}

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

		if(acceptsSize() == 0) {
			out.println("\treturn 0;");
		} else {
			for(Integer i : acceptsIterable()) {
				out.print(d);
				out.format("__this__->state == %d", i);
				d = " ||\n\t\t\t";
			}
			out.println(");");
		}
	}

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

		for(DFAState<Object, ?, Void> s : stateKeys()) {
			if(!s.isAccepted())  continue;
			p = null;
			out.format("\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;
				}
			}

			if(p != null) {
				p = ReplaceAction.replace(p, z, this, getStateNo(s),
						builder.getLabelByState(s));
			} else {
				p = "return __b__;";
			}
			out.format("\t\t%s\n", p);
		}
	}

	/* (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("\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\tbreak;");
			} else {
				p = ReplaceAction.replace(p, a, this, stateNo(s),
						builder.getLabelByState(s));
				out.format("\t\t%s\n", p);
				if(!a[0]) {
					out.println("\t\tbreak;");
				}
			}
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.AbstractNinaTranslator#printImports(java.io.PrintStream)
	 */
	@Override
	public void printImports(List<String> imp, PrintStream out) {
		// do nothing
	}

	/* (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 = "\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("\treturn 0;");
		} else {
			for(Integer i : t) {
				out.print(d);
				out.format("__this__->state == %d", i);
				d = " ||\n\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("\treturn %d;\n", getDeadStateNo());
	}

	private int signo(String s) {
		if(s == null) {
			return -1;
		} else if(s.equals("SIGHUP")) {
			return 1;
		} else if(s.equals("SIGINT")) {
			return 2;
		} else if(s.equals("SIGQUIT")) {
			return 3;
		} else if(s.equals("SIGILL")) {
			return 4;
		} else if(s.equals("SIGABRT")) {
			return 6;
		} else if(s.equals("SIGFPE")) {
			return 8;
		} else if(s.equals("SIGSEGV")) {
			return 11;
		} else if(s.equals("SIGPIPE")) {
			return 13;
		} else if(s.equals("SIGALRM")) {
			return 14;
		} else if(s.equals("SIGTERM")) {
			return 15;
		} else {
			return -1;
		}
	}

	/* (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 = "";
		int n;

		// catch a signal
		for(String x : builder.getLabels()) {
			if((n = signo(x)) >= 0) {
				((NinaTranslatorC)parent)._usesignal[n] = true;
				s = (DFAState<Object, ?, Void>)builder.getStateByLabel(
						x);
				out.printf("\t%sif(e == %d) {\n", d, n);
				out.printf("\t\treturn %d;\n", getStateNo(s));
				d = "} else ";
			}
		}

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

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

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.AbstractNinaTranslator#printInitTrap(java.io.PrintStream)
	 */
	@Override
	protected void printInitTrap(PrintStream out) {
		for(int k = 1; k < _usesignal.length; k++) {
			if(((NinaTranslatorC)parent)._usesignal[k]) {
				out.printf("\tsignal(%d, __nina_signal);\n", k);
			}
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.AbstractNinaTranslator#printNFADeadState(java.io.PrintStream)
	 */
	@Override
	protected void printNFADeadState(PrintStream out) {
		out.print("return (0");
		for(DFAState<Object, ?, Void> s : stateKeys()) {
			if(s.isDead()) {
				out.print(" || ");
				out.print("__this__->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) {
		boolean b = true;
		String t;

		for(String x : builder.getLabels()) {
			if(x == null || x.equals("")) {
				// do nothing
			} else if((t = builder.getTypeByLabel(x)) == null) {
				// do nothing
			} else if(t.equals("")) {
				// do nothing
			} else {
				b = false;
				out.printf("\t\t%s %s;\n", t, x);
			}
		}
		if(b)  out.println("\t\tchar __dummy__;");
	}

	/* (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() {
		return NinaTranslator.class.getResourceAsStream(
				"/net/morilib/nina/translate/nina_template." +
				getMachine() +
				".c.sh");
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.AbstractNinaTranslator#openOutput()
	 */
	@Override
	protected PrintStream openOutput() throws IOException {
		String s;

		s = (s = getOptions().getOption("output")).equals("") ? "." : s;
		return new PrintStream(new FileOutputStream(
				new File(s, getOptions().getOutputFilename() + ".c")),
				true);
	}

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

		r = new NinaTranslatorC();
		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) {
		String l = b1.toString();
		Object o = builder.getStateByLabel(l);
		String t = builder.getTypeByLabel(l);

		if(l.equals("c")) {
			ot.append("__c__");
		} else if(l.equals("this")) {
			ot.append("(((struct ");
			ot.append(options.getFilename());
			ot.append("_tag *)__this__)->__user_fields)");
		} else if(o == null) {
			ot.append('$');
			ot.append(b1);
		} else if(t == null) {
			ot.append('$');
			ot.append(b1);
		} else {
			ot.append("(__stv_C(__this__)).");
			ot.append(builder.getName());
			ot.append(".");
			ot.append(b1);
		}
	}

	/* (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_C(__this__)).");
			ot.append(builder.getName());
			ot.append(".");
			ot.append(b1);
		}
	}

	/* (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_C(__this__)).");
		ot.append(builder.getName());
		ot.append(".");
		ot.append(ln);
	}

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

	@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;
		}
	}

	@Override
	protected void appendYield(StringBuffer ot, String b) {
		ot.append("__this__->yieldobject = (");
		ot.append(ReplaceActionCSharp.replace(b)).append(");");
		ot.append("return NINA_YIELD;");
	}

}
