/*
 * 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.PrintWriter;
import java.io.StringWriter;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;

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.DFAs;
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 2014/04/04
 */
public class TMBuilder implements NinaAction {

	private static class W {

		private CharSequence wrap;

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

	}

	/**
	 * 
	 *
	 *
	 * @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;
		boolean initial, accepted, moore;
		String label;
		Object mealyOthers = null;
		Object mealyEnd = null;
		DBS others = null;
		DBS end = 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 {
				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) {
				throw new NinaNondeterministicException("others");
			} else {
				others = d;
				mealyOthers = m;
			}
		}

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

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

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

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

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

		@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 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 BuildMachine accepted = null;
	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 void _link1(NinaEvent q, DBS v) {
		CharSequence s;
		int k, l;
		Range r;
		DBS x;

		s = ((W)q.getEdge()).wrap;
		l = s.length() - 1;
		x = v;
		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);
	}

	/* (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 W) {
			_link1(q, v);
		} 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 == _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,
			NinaSubautomata sub) {
		throw new NinaException("isnotdfa");
	}

	/* (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#setEdgeGrammar(net.morilib.nina.NinaEvent, java.lang.String)
	 */
	@Override
	public void setEdgeGrammar(NinaEvent q, String name) {
		throw new NinaException("isnotdfa");
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#accept()
	 */
	@Override
	public NinaAction accept(String name) {
		if(accepted == null) {
			this.name = name;
			accepted = new BuildMachine(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) {
		throw new NinaException("isnotdfa");
	}

	/* (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, Range r, int len) {
		throw new NinaException("isnotdfa");
	}

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

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#setAccept(boolean)
	 */
	@Override
	public void setAccept(boolean accept) {
		vertex.accepted = accept;
	}

	@Override
	public void setEdgeDynamic(NinaEvent q, String var) {
		throw new NinaException("isnotdfa");
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#getDynamicTransitMap()
	 */
	@Override
	public Map<String, String> getDynamicTransitMap() {
		return Collections.emptyMap();
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#setEdgeRepeatedResource(net.morilib.nina.NinaEvent, java.lang.String, java.lang.Object)
	 */
	@Override
	public void setEdgeRepeatedResource(NinaEvent q, String s,
			Object d) {
		throw new NinaException("isnotdfa");
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#empty(net.morilib.nina.NinaEvent, net.morilib.nina.NinaConsole)
	 */
	@Override
	public NinaAction empty(NinaEvent q, String name) {
		setStartState(createState(q));
		return accept(name);
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#setEdgeDigits(net.morilib.nina.NinaEvent)
	 */
	@Override
	public void setEdgeDigits(NinaEvent q, int digits, boolean sign) {
		throw new NinaException("isnotfa");
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#setEdgeHexadecimal(net.morilib.nina.NinaEvent, int)
	 */
	@Override
	public void setEdgeHexadecimal(NinaEvent q, int bits) {
		throw new NinaException("isnotfa");
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#setEdgeOctal(net.morilib.nina.NinaEvent, int)
	 */
	@Override
	public void setEdgeOctal(NinaEvent q, int bits) {
		throw new NinaException("isnotfa");
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#setEdgeFloat(net.morilib.nina.NinaEvent)
	 */
	@Override
	public void setEdgeFloat(NinaEvent q) {
		throw new NinaException("isnotfa");
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#setEdgeIndent(net.morilib.nina.NinaEvent, java.lang.String, int)
	 */
	@Override
	public void setEdgeIndent(NinaEvent q, String cmp, int off) {
		throw new NinaException("isnotdfa");
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#setEdgeIdentifier(net.morilib.nina.NinaEvent, java.lang.String)
	 */
	@Override
	public void setEdgeIdentifier(NinaEvent q, String lang) {
		throw new NinaException("isnotfa");
	}

}
