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

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import net.morilib.automata.AlphabetsNotDiscreteException;
import net.morilib.automata.DFA;
import net.morilib.automata.DFAState;
import net.morilib.automata.NFA;
import net.morilib.automata.NFAEdges;
import net.morilib.automata.NFAState;
import net.morilib.automata.TextBound;
import net.morilib.automata.dfa.ConvertedRangeDFA;
import net.morilib.automata.dfa.DFAs;
import net.morilib.automata.nfa.RegexParseException;
import net.morilib.automata.nfa.RegexpParser;
import net.morilib.range.CharSets;
import net.morilib.range.Interval;
import net.morilib.range.Range;
import net.morilib.range.RangeAdder;
import net.morilib.util.TreeSectionMap;
import net.morilib.util.Tuple2;

/**
 *
 *
 * @author MORIGUCHI, Yuichiro 2013/10/14
 */
public class DFABuilder implements NinaAction {

	private static class W {

		private CharSequence wrap;
		private int quote;

		W(CharSequence s, int qc) {
			wrap = s;
			quote = qc;
		}

	}

	private static class RT {

		private String name;

		RT(String s) {
			name = s;
		}

	}

	private static class UE {

		private String edge;

		UE(String s) {
			edge = s;
		}

	}

	private static class SQ {

		private int num;

		SQ(int n) {
			num = n;
		}

	}

	/**
	 * 
	 *
	 *
	 * @author MORIGUCHI, Yuichiro 2013/10/14
	 */
	public static class DBS
	implements DFAState<Object, Object, Void>,
	NFAState, NFAEdges<Object> {

		TreeSectionMap<Interval, Object, DBS> edges;
		TreeSectionMap<Interval, Object, Object> mealy;
		Map<String, DBS> userEdge = null;
		Map<String, Object> userMealy = null;
		boolean initial, accepted, moore;
		String nomFichier, label, recursiveName = null;
		Object mealyOthers = null;
		Object mealyEnd = null;
		DBS others = null;
		DBS end = null;
		DBS recursive = null;

		PrintWriter out = null;
		int devoirFermer = 0;

		Collection<DBS> identical = null;

		private void _linkRange(DBS d, Range o, Interval r, Object m) {
			if(d.identical != null) {
				throw new NinaException("invaliddfasubautomaton");
			} else if(edges == null) {
				edges = new TreeSectionMap<Interval, Object, DBS>(
						r, d);
				mealy = new TreeSectionMap<Interval, Object, Object>(
						r, m);
			} else {
				for(Tuple2<Interval, DBS> w : edges.entries()) {
					if(!o.independentOf(w.getA()) &&
							!d.equals(w.getB())) {
						throw new NinaNondeterministicException(
								o + "," + d + "," + w);
					}
				}
				edges.insert(r, d);
				mealy.insert(r, m);
			}
		}

		void linkAlphabet(DBS d, Object o, Object m) {
			Interval r;

			if(identical != null) {
				for(DBS x : identical) {
					x.linkAlphabet(d, o, m);
				}
			} else {
				r = Interval.newPoint(o);
				_linkRange(d, r, r, m);
			}
		}

		void linkAlphabet(DBS d, Range o, Object m) {
			if(identical != null) {
				for(DBS x : identical) {
					x.linkAlphabet(d, o, m);
				}
			} else {
				for(Interval v : o.intervals()) {
					_linkRange(d, o, v, m);
				}
			}
		}

		void linkOthers(DBS d, Object m) {
			if(others != null || recursive != null) {
				throw new NinaNondeterministicException("others");
			} else {
				others = d;
				mealyOthers = m;
			}
		}

		void linkRecursive(DBS d, String name) {
			if(others != null || end != null) {
				throw new NinaNondeterministicException("others");
			} else {
				recursive = d;
				recursiveName = name;
			}
		}

		void linkEnd(DBS d, Object m) {
			if(end != null || recursive != null) {
				throw new NinaNondeterministicException("END");
			} else {
				end = d;
				mealyEnd = m;
			}
		}

		void linkUser(DBS d, String l, Object m) {
			if(userEdge == null) {
				userEdge  = new HashMap<String, DBS>();
				userMealy = new HashMap<String, Object>();
			}
			userEdge.put(l, d);
			userMealy.put(l, m);
		}

		void sortir(Object o) {
			String s;

			try {
				if(nomFichier == null || nomFichier.equals("")) {
					return;
				} else if(out == null) {
					if(nomFichier.equals("/dev/stdout")) {
						out = new PrintWriter(System.out, true);
						devoirFermer = -1;
					} else if(nomFichier.equals("/dev/stderr")) {
						out = new PrintWriter(System.err, true);
						devoirFermer = -1;
					} else if(nomFichier.equals("/dev/null")) {
						out = null;
					} else {
						out = new PrintWriter(new FileOutputStream(
								nomFichier, devoirFermer > 0), true);
						devoirFermer = 1;
					}
				}
		
				if(out != null && o != null &&
						!(s = o.toString().trim()).equals("")) {
					out.print(s);
					out.flush();
				}
				if(devoirFermer > 0)  out.close();
			} catch(IOException e) {
				// ignore it
				throw new RuntimeException(e);
			}
		}

		void sortirMoore() {
			if(moore)  sortir(label.trim());
		}

		/**
		 * 
		 * @return
		 */
		public DBS getOthers() {
			return others;
		}

		/**
		 * 
		 * @return
		 */
		public Object getMealyOthers() {
			return mealyOthers;
		}

		/**
		 * 
		 * @return
		 */
		public DBS getEnd() {
			return end;
		}

		/**
		 * 
		 * @return
		 */
		public Object getMealyEnd() {
			return mealyEnd;
		}

		/**
		 * 
		 * @return
		 */
		public DBS getRecursive() {
			return recursive;
		}

		/**
		 * 
		 * @return
		 */
		public String getRecursiveName() {
			return recursiveName;
		}

		/**
		 * 
		 * @return
		 */
		public Set<String> getUserEdges() {
			return userEdge != null ? userEdge.keySet() : null;
		}

		/**
		 * 
		 * @param s
		 * @return
		 */
		public DBS getUserEdge(String s) {
			return userEdge != null ? userEdge.get(s) : null;
		}

		/**
		 * 
		 * @param s
		 * @return
		 */
		public Object getUserMealyEdge(String s) {
			return userMealy != null ? userMealy.get(s) : null;
		}

		@Override
		public DFAState<Object, Object, Void> go(Object a) {
			DFAState<Object, Object, Void> d;

			if(edges != null && (d = edges.map(a)) != null) {
				sortir(mealy.map(a));
				if(d instanceof DBS)  ((DBS)d).sortirMoore();
				return d;
			} else if(others == null) {
				return DFAs.<Object, Object, Void>deadState();
			} else {
				sortir(mealyOthers);
				return others;
			}
		}

		@Override
		public DFAState<Object, Object, Void> goInt(int x) {
			return go(Integer.valueOf(x));
		}

		@Override
		public DFAState<Object, Object, Void> goChar(char x) {
			return go(Integer.valueOf(x));
		}

		@Override
		public DFAState<Object, Object, Void> goBound(TextBound b) {
			return DFAs.deadState();
		}

		@Override
		public boolean isInitialState() {
			return initial;
		}

		@Override
		public Set<Object> getAccepted() {
			return accepted ?
					Collections.<Object>singleton(label) :
						Collections.emptySet();
		}

		@Override
		public boolean isDead() {
			return edges == null && others == null;
		}

		@Override
		public boolean isAccepted() {
			return accepted;
		}

		@Override
		public Set<Object> getAlphabets() {
			Set<Object> r = new HashSet<Object>();
			Object a, b;
			int x;

			if(edges != null) {
				for(Interval v : edges.keys()) {
					a = v.getInfimumBound();
					b = v.getSupremumBound();
					if(a.equals(b)) {
						r.add(a);
					} else if(a instanceof Integer) {
						x = ((Integer)a).intValue();
						for(int i = x; v.contains(i); i++) {
							r.add(Integer.valueOf(i));
						}
					} else {
						throw new NinaException("invalidalphabet",
								a);
					}
				}
			}
			return r;
		}

		@Override
		public Iterable<Interval> getAlphabetRanges() {
			return edges != null ?
					edges.keys() : Collections.<Interval>emptySet();
		}

		@Override
		public Object getLabel(Object a) {
			return mealy.map(a);
		}

		@Override
		public Object getLabelInt(int x) {
			return getLabel(Integer.valueOf(x));
		}

		@Override
		public Object getLabelChar(char x) {
			return getLabel(Integer.valueOf(x));
		}

		@Override
		public Set<NFAState> goNext(Object a) {
			DBS d;

			if(edges != null && (d = edges.map(a)) != null) {
				return Collections.singleton((NFAState)d);
			} else if(others == null) {
				return Collections.emptySet();
			} else {
				return Collections.singleton((NFAState)others);
			}
		}

		@Override
		public Set<NFAState> goNext(int a) {
			return goNext(Integer.valueOf(a));
		}

		@Override
		public Set<NFAState> goNext(char a) {
			return goNext(Integer.valueOf(a));
		}

		@Override
		public Set<NFAState> goNextEpsilon() {
			return Collections.emptySet();
		}

		@Override
		public Set<? extends Range> nextAlphabets() {
			Set<Range> x = new HashSet<Range>();
			RangeAdder a = new RangeAdder();

			for(Tuple2<Interval, DBS> t : edges.entries()) {
				x.add(t.getA());
				a.add(t.getA());
			}

			if(others == null) {
				// do nothing
			} else {
				x.add(CharSets.complement(a.toRange()));
			}
			return x;
		}

		@Override
		public boolean isNextEpsilon() {
			return false;
		}

		@Override
		public String toString() {
			return label;
		}

	};

	private static class FA
	implements DFA<Object, Object, Void>, NFA<Object, Object, Void> {

		private String name;
		private DBS initial;

		private transient Set<Object> allStates;

		FA(String name, DBS init) {
			this.name = name;
			initial   = init;
			allStates = new HashSet<Object>(DFAs.allStateRanges(init));
		}

		@Override
		public boolean isState(NFAState o) {
			return allStates.contains(o);
		}

		@Override
		public Set<NFAState> getStates(NFAState s, Object a) {
			DFAState<Object, Object, Void> d;

			if(!isState(s)) {
				return Collections.emptySet();
			} else if((d = ((DBS)s).go(a)).isDead()) {
				return Collections.emptySet();
			} else {
				return Collections.singleton((NFAState)d);
			}
		}

		@Override
		public Set<NFAState> getStates(NFAState s, Range r) {
			Set<NFAState> x;
			DBS d;

			if(!isState(s)) {
				return Collections.emptySet();
			} else {
				d = (DBS)s;
				x = new HashSet<NFAState>();
				for(Tuple2<Interval, DBS> t : d.edges.entries()) {
					for(Interval v : r.intervals()) {
						if(t.getA().equals(v)) {
							x.add(t.getB());
						}
					}
				}
				return x;
			}
		}

		/* (non-Javadoc)
		 * @see net.morilib.automata.NFA#getStates(net.morilib.automata.NFAState, java.util.EnumSet)
		 */
		@Override
		public Set<NFAState> getStates(NFAState s,
				EnumSet<TextBound> b) {
			return Collections.emptySet();
		}

		/* (non-Javadoc)
		 * @see net.morilib.automata.NFA#getStatesEpsilon(net.morilib.automata.NFAState)
		 */
		@Override
		public Set<NFAState> getStatesEpsilon(NFAState s) {
			return Collections.emptySet();
		}

		/* (non-Javadoc)
		 * @see net.morilib.automata.NFA#getStatesBound(net.morilib.automata.NFAState, java.util.EnumSet)
		 */
		@Override
		public Set<NFAState> getStatesBound(NFAState s,
				EnumSet<TextBound> b) {
			return Collections.emptySet();
		}

		/* (non-Javadoc)
		 * @see net.morilib.automata.NFA#getInitialStates()
		 */
		@Override
		public Set<NFAState> getInitialStates() {
			return Collections.singleton((NFAState)initial);
		}

		/* (non-Javadoc)
		 * @see net.morilib.automata.NFA#isInitialState(net.morilib.automata.NFAState)
		 */
		@Override
		public boolean isInitialState(NFAState o) {
			return initial.equals(o);
		}

		/* (non-Javadoc)
		 * @see net.morilib.automata.NFA#isFinal(net.morilib.automata.NFAState)
		 */
		@Override
		public boolean isFinal(NFAState s) {
			return isState(s) && ((DBS)s).isAccepted();
		}

		/* (non-Javadoc)
		 * @see net.morilib.automata.NFA#isFinalAny(java.util.Set)
		 */
		@Override
		public boolean isFinalAny(Set<NFAState> ss) {
			for(NFAState s : ss) {
				if(isFinal(s))  return true;
			}
			return false;
		}

		/* (non-Javadoc)
		 * @see net.morilib.automata.NFA#getEdges(net.morilib.automata.NFAState)
		 */
		@Override
		public NFAEdges<Object> getEdges(NFAState s) {
			return isState(s) ? (DBS)s : null;
		}

		/* (non-Javadoc)
		 * @see net.morilib.automata.NFA#nextAlphabets(net.morilib.automata.NFAState)
		 */
		@Override
		public Set<Interval> nextAlphabets(NFAState s) {
			Set<Interval> x = new HashSet<Interval>();
			DBS d;

			if(isState(s)) {
				d = (DBS)s;
				for(Tuple2<Interval, DBS> t : d.edges.entries()) {
					x.add(t.getA());
				}
				return x;
			} else {
				return Collections.emptySet();
			}
		}

		/* (non-Javadoc)
		 * @see net.morilib.automata.NFA#nextAlphabets(java.util.Set)
		 */
		@Override
		public Iterable<Interval> nextAlphabets(Set<NFAState> ss) {
			Set<Interval> x = new HashSet<Interval>();

			for(NFAState s : ss) {
				x.addAll(nextAlphabets(s));
			}
			return x;
		}

		/* (non-Javadoc)
		 * @see net.morilib.automata.NFA#nextDiscreteAlphabets(net.morilib.automata.NFAState)
		 */
		@Override
		public Set<Object> nextDiscreteAlphabets(NFAState s) {
			throw new AlphabetsNotDiscreteException();
		}

		/* (non-Javadoc)
		 * @see net.morilib.automata.NFA#nextDiscreteAlphabets(java.util.Set)
		 */
		@Override
		public Iterable<Object> nextDiscreteAlphabets(
				Set<NFAState> s) {
			throw new AlphabetsNotDiscreteException();
		}

		/* (non-Javadoc)
		 * @see net.morilib.automata.NFA#getAcceptedStates()
		 */
		@Override
		public Set<NFAState> getAcceptedStates() {
			Set<NFAState> x = new HashSet<NFAState>();

			for(Object s : allStates) {
				if(((DBS)s).isAccepted())  x.add((DBS)s);
			}
			return x;
		}

		/* (non-Javadoc)
		 * @see net.morilib.automata.NFA#getMatchTag(net.morilib.automata.NFAState)
		 */
		@Override
		public Set<Void> getMatchTag(NFAState s) {
			return Collections.emptySet();
		}

		/* (non-Javadoc)
		 * @see net.morilib.automata.NFA#getMatchTagEnd(net.morilib.automata.NFAState)
		 */
		@Override
		public Set<Void> getMatchTagEnd(NFAState s) {
			return Collections.emptySet();
		}

		/* (non-Javadoc)
		 * @see net.morilib.automata.NFA#getAccept(net.morilib.automata.NFAState)
		 */
		@Override
		public Set<Object> getAccept(NFAState s) {
			if(isAccepted(s)) {
				return Collections.singleton((Object)s);
			} else {
				return Collections.emptySet();
			}
		}

		/* (non-Javadoc)
		 * @see net.morilib.automata.NFA#isAccepted(net.morilib.automata.NFAState)
		 */
		@Override
		public boolean isAccepted(NFAState s) {
			return isState(s) && ((DBS)s).isAccepted();
		}

		/* (non-Javadoc)
		 * @see net.morilib.automata.DFA#getInitialState()
		 */
		@Override
		public DFAState<Object, Object, Void> getInitialState() {
			initial.sortirMoore();
			return initial;
		}

	}

	//
	private static final Object _END = new Object();

	//
	private Map<String, DBS> map = new HashMap<String, DBS>();
	private Map<Object, String> inv =
			new IdentityHashMap<Object, String>();
	private Map<String, String> typemap =
			new HashMap<String, String>();
	private DBS initial, vertex;
	private FA accepted = null;
	private int mealyType;
	private Object mealy;
	private String name;

	public DBS createState(NinaEvent q) {
		DBS s;

		s = new DBS();
		s.nomFichier = q.getOption("out");
		s.moore = q.getOptionBoolean("moore");
		return s;
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#labelAdded(net.morilib.nina.NinaEvent, java.lang.String, boolean)
	 */
	@Override
	public void labelAdded(NinaEvent q, NinaFrameReader rd,
			boolean accept) {
		StringWriter wr = new StringWriter();
		char[] a = new char[1024];
		int l;

		while((l = rd.read(a)) >= 0)  wr.write(a, 0, l);
		if(vertex == null) {
			// set initial state if it is not set
			vertex = createState(q);
			vertex.initial = true;
			initial = vertex;
		}

		if(vertex.identical == null) {
			vertex.accepted = accept;
		} else {
			for(DBS x : vertex.identical)  x.accepted = accept;
		}
		vertex.label = wr.toString();
	}

	private DBS _linkv(NinaEvent q, DBS x, DBS a, Range r, String o) {
		x.linkAlphabet(a, r, o);
		return a;
	}

	private DBS _linkc(NinaEvent q, DBS x, int c, String o) {
		Range r;
		DBS a;

		r = Interval.newPoint(Integer.valueOf(c));
		a = createState(q);
		return _linkv(q, x, a, r, o);
	}

	private DBS _linki(NinaEvent q, DBS x, int c, String o) {
		Range r;

		r = Interval.newPoint(Integer.valueOf(c));
		r = CharSets.complement(r);
		return _linkv(q, x, vertex, r, o);
	}

	private void _linkdfa(NinaEvent q, DFA<Object, NFAState, Void> d,
			DBS v, Object o) {
		Collection<DFAState<Object, NFAState, Void>> c;
		Map<DFAState<Object, NFAState, Void>, DBS> m;
		DFAState<Object, NFAState, Void> h;
		String g, lh, lc;
		DBS x, w;

		lh = q.getLanguageOption().getLookahead();
		lc = q.getLanguageOption().getLookaheadCommit();
		c  = DFAs.allStateRanges(d.getInitialState());
		m  = new HashMap<DFAState<Object, NFAState, Void>, DBS>();
		g  = lh + lc;
		if(o != null)  g += o.toString();
		for(DFAState<Object, NFAState, Void> y : c) {
			if(y.equals(d.getInitialState())) {
				x = v;
			} else if((x = m.get(y)) == null) {
				m.put(y, x = new DBS());
			}

			if(y.isAccepted())  x.linkOthers(vertex, g);
			for(Interval a : y.getAlphabetRanges()) {
				h = y.go(a.getInfimumBound());
				if(h.equals(d.getInitialState())) {
					x.linkAlphabet(v, a, lh);
				} else {
					if((w = m.get(h)) == null) {
						m.put(h, w = new DBS());
					}
					x.linkAlphabet(w, a, lh);
				}
			}
		}
	}

	private void _linkdq(NinaEvent q, CharSequence s, DBS v) {
		List<DoubleQuoteTokenizer.Elems> b;
		NFA<Object, NFAState, Void> n;
		DFA<Object, NFAState, Void> d;
		String g, lh, lm, lc;
		boolean z;
		int k, c;
		DBS x, a;
		Range r;

		lh = q.getLanguageOption().getLookahead();
		lm = q.getLanguageOption().getLookaheadMark();
		lc = q.getLanguageOption().getLookaheadCommit();
		b  = DoubleQuoteTokenizer.parse(s.toString());
		for(DoubleQuoteTokenizer.Elems p : b) {
			x = v;
			s = p.getString();
			for(k = 0; k < s.length() - 1; k++) {
				c = s.charAt(k);
				if(x.edges == null) {
					x = _linkc(q, x, c, lh);
				} else if((a = x.edges.map(c)) != null) {
					x = a;
				} else {
					x = _linkc(q, x, c, lh);
				}
			}

			if(p.getRegex() != null) {
				x = _linkc(q, x, s.charAt(k), lh + lm);
				try {
					n = RegexpParser.parse(p.getRegex());
					d = ConvertedRangeDFA.convertDFA(n);
					_linkdfa(q, d, x, mealy);
				} catch (RegexParseException e) {
					throw new NinaException("invalidregex");
				}
			} else if(p.getLookahead() != null) {
				x = _linkc(q, x, s.charAt(k), lh);
				g = lm + lh;
				z = p.isInvertLookahead();
				s = p.getLookahead();
				for(k = 0; k < s.length() - 1; k++) {
					c = s.charAt(k);
					if(z)  _linki(q, x, c, g + lc);
					x = _linkc(q, x, c, g);
					g = lh;
				}
				g = g + lc;
				if(mealy != null)  g += mealy.toString();
				r = Interval.newPoint(Integer.valueOf(s.charAt(k)));
				if(p.isInvertLookahead())  r = CharSets.complement(r);
				x.linkAlphabet(vertex, r, g);
			} else {
				g = lc;
				if(mealy != null)  g += mealy.toString();
				r = Interval.newPoint(Integer.valueOf(s.charAt(k)));
				if(p.isInvertLookahead())  r = CharSets.complement(r);
				x.linkAlphabet(vertex, r, g);
			}
		}
	}

	private void _link1(NinaEvent q, DBS v) {
		CharSequence s;
		int k, b, l;
		Range r;
		DBS x;

		s = ((W)q.getEdge()).wrap;
		b = ((W)q.getEdge()).quote;
		l = s.length() - 1;
		x = v;
		if(b == '"') {
			_linkdq(q, s, v);
		} else {
			for(k = 0; k < l; k++) {
				x = _linkc(q, x, s.charAt(k), null);
			}
			r = Interval.newPoint(Integer.valueOf(s.charAt(k)));
			x.linkAlphabet(vertex, r, mealy);
		}
	}

	private void _linksq(NinaEvent q, DBS v, int len) {
		NinaLanguageOption p = q.getLanguageOption();
		String s, t = "", m;
		DBS x = v, y;

		m = mealy != null ? mealy.toString() : "";
		s = mealyType == '%' ? p.getInitBuffer(m) : "";
		for(int k = 0; k < len - 1; k++, x = y) {
			y = createState(q);
			if(mealyType == '%') {
				t = p.getAppendBuffer(m);
			} else if(mealyType == '#') {
				t = p.getAssignArray(m, k);
			}
			x.linkOthers(y, s + t);
			s = "";
		}

		if(mealyType == '%') {
			t = p.getAppendBuffer(m);
		} else if(mealyType == '#') {
			t = p.getAssignArray(m, len - 1);
		} else {
			t = m;
		}
		x.linkOthers(vertex, t);
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#link(net.morilib.nina.NinaEvent, java.lang.Object)
	 */
	@Override
	public void link(NinaEvent q, Object ver) {
		DBS v = vertex;
		Object o;

		vertex = (ver != null) ? (DBS)ver : createState(q);
		if((o = q.getEdge()) == null) {
			v.linkOthers(vertex, q.getLanguageOption().getUnget());
		} else if(o instanceof FA) {
			v.linkRecursive(vertex, ((FA)o).name);
		} else if(o instanceof RT) {
			v.linkRecursive(vertex, ((RT)o).name);
		} else if(o instanceof W) {
			_link1(q, v);
		} else if(o instanceof SQ) {
			_linksq(q, v, ((SQ)o).num);
		} else if(o.equals(CharSets.ALL_CHAR)) {
			v.linkOthers(vertex, mealy);
		} else if(o instanceof Range) {
			v.linkAlphabet(vertex, (Range)o, mealy);
		} else if(o instanceof UE) {
			v.linkUser(vertex, ((UE)o).edge, mealy);
		} else if(o == _END) {
			v.linkEnd(vertex, mealy);
		} else {
			v.linkAlphabet(vertex, o, mealy);
		}
		mealy = null;
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#getLabel()
	 */
	@Override
	public String getLabel() {
		return vertex.label;
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#getVertex()
	 */
	@Override
	public Object getVertex() {
		return vertex;
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#setVertex(java.lang.Object)
	 */
	@Override
	public void setVertex(Object o) {
		vertex = (DBS)o;
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#doneBlockSearching(net.morilib.nina.NinaEvent)
	 */
	@Override
	public void doneBlockSearching(NinaEvent q) {
		if(q.getScratch() instanceof NinaParser.Z) {
			vertex = (DBS)((NinaParser.Z)q.getScratch()).vertex;
		} else {
			vertex = (DBS)q.getScratch();
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#setEdge(net.morilib.nina.NinaEvent, java.lang.Object)
	 */
	@Override
	public void setEdge(NinaEvent q, Object o) {
		q.setEdge(o);
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#setEdgeCharSequence(net.morilib.nina.NinaEvent, java.lang.CharSequence)
	 */
	@Override
	public void setEdgeCharSequence(NinaEvent q, CharSequence o,
			int qc) {
		q.setEdge(new W(o, qc));
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#setEdgeResource(net.morilib.nina.NinaEvent, net.morilib.nina.NinaParser, java.lang.String)
	 */
	@Override
	public void setEdgeResource(NinaEvent q,
			NinaParser p,
			String s,
			Map<String, String> map,
			Map<String, Object> sub) {
		String t = Nina.getFAName(s);

		if(sub.containsKey(t)) {
			q.setEdge(new RT(t));
		} else {
			sub.put(t, null);
			q.setEdge(p.compileSubautomaton(s, new DFABuilder(), map,
					sub).getMachine());
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#setEdgeNFA(net.morilib.nina.NinaEvent, net.morilib.nina.NinaParser, net.morilib.automata.NFA)
	 */
	@Override
	public void setEdgeNFA(NinaEvent q, NinaParser p,
			NFA<Object, NFAState, Void> s) {
		throw new NinaException("cannotaddnfa");
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#setEdgeEnd(net.morilib.nina.NinaEvent)
	 */
	@Override
	public void setEdgeEnd(NinaEvent q) {
		q.setEdge(_END);
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#accept()
	 */
	@Override
	public NinaAction accept(String name) {
		if(accepted == null) {
			this.name = name;
			accepted = new FA(name, initial);
		}
		return this;
	}

	public DFA<Object, Object, Void> getMachine() {
		return accepted;
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#setMealyEdge(int)
	 */
	@Override
	public void setMealyEdge(int c) {
		mealy = Character.valueOf((char)c);
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#setMealyEdge(java.lang.Object)
	 */
	@Override
	public void setMealyEdge(Object o) {
		mealy = o;
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#setPriority(int)
	 */
	@Override
	public void setPriority(int p) {
		// do nothing
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#putStateByLabel(java.lang.String, java.lang.Object)
	 */
	@Override
	public void putStateByLabel(String l, Object s) {
		map.put(l, (DBS)s);
		inv.put(s, l);
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#getStateByLabel(java.lang.String)
	 */
	@Override
	public DBS getStateByLabel(String l) {
		return map.get(l);
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#getLabelByState(java.lang.Object)
	 */
	@Override
	public String getLabelByState(Object s) {
		return inv.get(s);
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#getDeadState()
	 */
	@Override
	public Object getDeadState() {
		return getStateByLabel(NinaParser.DEADSYM);
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#setStartState(java.lang.Object)
	 */
	@Override
	public void setStartState(Object o) {
		vertex = (DBS)o;
		vertex.initial = true;
		initial = vertex;
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#setUserEdge(net.morilib.nina.NinaEvent, java.lang.String)
	 */
	@Override
	public void setUserEdge(NinaEvent q, String o) {
		q.setEdge(new UE(o));
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#getLabels()
	 */
	@Override
	public Set<String> getLabels() {
		return map.keySet();
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#putTypeByLabel(java.lang.String, java.lang.Object)
	 */
	@Override
	public void putTypeByLabel(String l, String s) {
		typemap.put(l, s);
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#getTypeByLabel(java.lang.String)
	 */
	@Override
	public String getTypeByLabel(String l) {
		return typemap.get(l);
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#getName()
	 */
	@Override
	public String getName() {
		return name;
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#setEdgeSequence(net.morilib.nina.NinaEvent)
	 */
	@Override
	public void setEdgeSequence(NinaEvent q, int len) {
		q.setEdge(new SQ(len));
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#setMealyEdgeType(int)
	 */
	@Override
	public void setMealyEdgeType(int c) {
		mealyType = c;
	}

}
