/*
 * 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.List;
import java.util.Map;
import java.util.Set;
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.translate.sh.ReplaceStrangeChar;
import net.morilib.range.Interval;
import net.morilib.sh.ShEnvironment;

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

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

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

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

	//
	private void printintv(PrintStream out, Interval v, Object o) {
		String z;
		int c, d;

		c = ((Integer)v.getInfimumBound()).intValue();
		d = ((Integer)v.getSupremumBound()).intValue();
		z = o != null && o.toString().startsWith(LOK) ?
				" ignoreLookahead=\"true\"" : "";
		out.format("      <condition from=\"%d\" to=\"%d\"%s>\n", c, d,
				z);
	}

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

		z = o != null && o.toString().startsWith(LOK) ?
				" ignoreLookahead=\"true\"" : "";
		out.format("      <condition equals=\"%s\"%s>\n",
				o.toString(), z);
	}

	//
	private void printclass(PrintStream out, Object o, Object m) {
		String s, z;

		z = o != null && o.toString().startsWith(LOK) ?
				" ignoreLookahead=\"true\"" : "";
		if(o instanceof Character) {
			out.format("      <condition equalsChar=\"%d\"%s>\n",
					(int)((Character)o).charValue(), z);
		} else if(E_CONST.matcher(s = o.toString()).matches()) {
			out.format("      <condition equals=\"%s\"%s>\n", s, z);
		} else {
			out.format("      <condition instanceof=\"%s\"%s>\n", s,
					z);
		}
	}

	private void putend(PrintStream out,
			DFAState<Object, ?, Void> dfa) {
		DFABuilder.DBS b;
		Object o;

		if(dfa instanceof DFABuilder.DBS &&
				(b = ((DFABuilder.DBS)dfa).getEnd()) != null) {
			out.println("      <condition end=\"true\">");
			if((o = ((DFABuilder.DBS)dfa).getMealyEnd()) != null) {
				out.println("        <action><![CDATA[");
				out.println(o.toString());
				out.println("        ]]></action>");
			}
			out.format("        <goto state=\"%d\" />\n", getStateNo(b));
			out.println("      </condition>");
		}
	}

	private void putuseredges(PrintStream out,
			DFAState<Object, ?, Void> dfa) {
		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);
				out.printf("      <condition expr=\"%s\">\n", x);
				if(o != null) {
					out.println("        <action><![CDATA[");
					out.println(o.toString());
					out.println("        ]]></action>");
				}
				out.format("        <goto state=\"%d\" />\n", getStateNo(b));
				out.println("      </condition>");
			}
		}
	}

	private void putothers(PrintStream out,
			DFAState<Object, ?, Void> dfa) {
		DFABuilder.DBS a, b;
		String t;
		Object o;

		if(!(dfa instanceof DFABuilder.DBS)) {
			// do nothing
		} else if((b = (a = (DFABuilder.DBS)dfa).getOthers()) != null) {
			out.println("      <conditionElse>");
			if((o = a.getMealyOthers()) != null) {
				out.println("        <action><![CDATA[");
				out.println(o.toString());
				out.println("        ]]></action>");
			}
			out.format("        <goto state=\"%d\" />\n", getStateNo(b));
			out.println("      </conditionElse>");
		} else if((b = a.getRecursive()) != null) {
			out.println("      <conditionElse>");
			t = ReplaceStrangeChar.replace(a.getRecursiveName());
			out.printf("        <call automaton=\"%s\">\n", t);
			out.println("      </conditionElse>");
		}
	}

	//
	private void printAcceptTokenState(PrintStream out,
			DFAState<Object, ?, Void> s) {
		int m = -1, q;
		NinaState n;
		String x, p;

		if(!s.isAccepted())  return;
		p = null;  m = -1;
		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) {
			out.println("      <action><![CDATA[");
			out.println(p);
			out.println("      ]]></action>");
		}
	}

	//
	private void printActionsStateDFA(PrintStream out,
			DFAState<Object, ?, Void> s) {
		String x, p;

		p = null;
		if((x = s.toString()) == null) {
			// do nothing
		} else if((x = x.trim()).equals("")) {
			// do nothing
		} else {
			p = x;
		}

		if(p != null) {
//			p = ReplaceAction.replace(p, a, this, stateNo(s),
//					builder.getLabelByState(s));
			out.println("      <action><![CDATA[");
			out.println(p);
			out.println("      ]]></action>");
		}
	}

	//
	private void printActionState(PrintStream out,
			DFAState<Object, ?, Void> s) {
		if(s instanceof DFABuilder.DBS) {
			printActionsStateDFA(out, s);
		} else {
			printAcceptTokenState(out, s);
		}
	}

	//
	private void printStateHeader(PrintStream out,
			DFAState<Object, ?, Void> dfa) {
		int sn = getStateNo(dfa);
		String s, t = "";

		s = builder.getLabelByState(dfa);
		if(s != null && !s.equals("")) {
			t = t + " label=\"" + s + "\"";
			s = builder.getTypeByLabel(s);
			if(s != null && !s.equals("")) {
				t = t + " type=\"" + s + "\"";
			}
		}
		out.format("    <state name=\"%d\"%s>\n", sn, t);
	}

	//
	private void printState(PrintStream out,
			DFAState<Object, ?, Void> dfa) {
		Object o;
		int c;

		printStateHeader(out, dfa);
		for(Interval v : dfa.getAlphabetRanges()) {
			if(v.isEmpty())  continue;
			c = infimumbound(v);
			if(v.isInfimumOpen())  c++;
			printintv(out, v, o = dfa.getLabelInt(c));
			if(o != null) {
				out.println("        <action><![CDATA[");
				out.println(o.toString());
				out.println("        ]]></action>");
			}

			// print next step
			out.format("        <goto state=\"%d\" />\n",
					getStateNo(dfa.goInt(c)));
			out.println("      </condition>");
		}
		putend(out, dfa);
		putuseredges(out, dfa);
		putothers(out, dfa);
		out.format("      <accept>%s</accept>\n",
				dfa.isAccepted() ? "true" : "false");
		printActionState(out, dfa);
		out.println("    </state>");
	}

	//
	private void printObjectState(PrintStream out,
			DFAState<Object, ?, Void> dfa) {
		Object o;

		printStateHeader(out, dfa);
		for(Object p : dfa.getAlphabets()) {
			printobj(out, p, o = dfa.getLabel(p));
			if(o != null) {
				out.println("        <action><![CDATA[");
				out.println(o.toString());
				out.println("        ]]></action>");
			}

			// print next step
			out.format("        <goto state=\"%d\" />\n",
					getStateNo(dfa.go(p)));
			out.println("      </condition>");
		}
		putend(out, dfa);
		putuseredges(out, dfa);
		putothers(out, dfa);
		out.println("    </state>");
	}

	//
	private void printClassState(PrintStream out,
			DFAState<Object, ?, Void> dfa) {
		Object o;

		printStateHeader(out, dfa);
		for(Object p : dfa.getAlphabets()) {
			printclass(out, p, o = dfa.getLabel(p));
			if(o != null) {
				out.println("        <action><![CDATA[");
				out.println(o.toString());
				out.println("        ]]></action>");
			}

			// print next step
			out.format("        <goto state=\"%d\" />\n",
					getStateNo(dfa.go(p)));
			out.println("      </condition>");
		}
		putend(out, dfa);
		putuseredges(out, dfa);
		putothers(out, dfa);
		out.println("    </state>");
	}

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

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

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

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

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

	/* (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) {
		// do nothing
	}

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

	/* (non-Javadoc)
	 * @see net.morilib.nina.translate.AbstractNinaTranslator#printAcceptStates(java.io.PrintStream)
	 */
	@Override
	public void printDeadState(String n, PrintStream out) {
		int a;

		if((a = getDeadStateNo()) >= 0) {
			out.printf("    <error state=\"%d\" />\n", a);
		}
	}

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

		out.println("    <try>");
		for(String x : builder.getLabels()) {
			if(x.endsWith("Exception")) {
				s = (DFAState<Object, ?, Void>)builder.getStateByLabel(
						x);
				out.printf("      <catch exception=\"%s\" state=\"%d\" />\n",
						x, getStateNo(s));
			}
		}

		if((a = getStateNoByLabel("finally")) >= 0) {
			out.printf("      <finally state=\"%d\" />\n", a);
		}
		out.println("    </try>");
	}

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

	/* (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) {
		// do nothing
	}

	/* (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) {
		out.println("    <defines>");
		for(Map.Entry<String, Integer> t : quadro.getConstantMap().entrySet()) {
			out.printf("      <define name=\"%s\" value=\"%d\" />\n",
					t.getKey(), t.getValue().intValue());
		}
		out.println("    </defines>");
	}

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

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

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

		s = getOptions().getOutputDir();
		return new PrintStream(getOptions().getOutputStream(
				s, getOptions().getOutputFilename() + getExtension()),
				true);
	}

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

		r = new NinaTranslatorXML();
		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) {
		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) {
		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(ln);
	}

	/* (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(b);
	}

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

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

}
