/*
 * 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.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import net.morilib.automata.NFA;
import net.morilib.automata.NFAState;
import net.morilib.math.stochastic.MarkovProcess;
import net.morilib.range.CharSets;
import net.morilib.range.Interval;
import net.morilib.range.Range;
import net.morilib.util.Tuple2;

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

	//
	private static final Pattern PTN1 = Pattern.compile("\"(.*)\"");
	private static final Pattern PTN2 = Pattern.compile("\\[(.*)\\]");

	static class DBS implements MarkovProcess {

		List<Tuple2<Number, DBS>> edges =
				new ArrayList<Tuple2<Number, DBS>>();
		List<Object> outputs = new ArrayList<Object>();
		boolean accepted;
		double sum;

		void linkAlphabet(DBS d, Number o, Object l) {
			edges.add(new Tuple2<Number, DBS>(o, d));
			outputs.add(l);
			sum += o.doubleValue();
		}

		@SuppressWarnings("unchecked")
		private Object getOutput(Object o) {
			List<Integer> l;
			StringBuffer b;
			int k;

			if(o instanceof String) {
				return o;
			} else if(o instanceof List) {
				l = (List<Integer>)o;
				k = (int)(l.size() * Math.random());
				b = new StringBuffer();
				b.appendCodePoint(l.get(k));
				return b.toString();
			} else {
				return "";
			}
		}

		public DBS go(double r, StringBuffer b) {
			Tuple2<Number, DBS> e;
			double l = 0.0, v;
			DBS x = null;

			for(int k = 0; k < edges.size(); k++) {
				e = edges.get(k);
				v = e.getA().doubleValue() / sum;
				x = e.getB();
				if(r > l && r <= l + v) {
					b.append(getOutput(outputs.get(k)));
					return x;
				} else {
					l += v;
				}
			}
			return x;
		}

		public boolean isDead() {
			return edges.isEmpty();
		}

		public boolean isAccepted() {
			return accepted;
		}

	};

	//
	private DBS initial, vertex;
	private String label;
	private String name;
	private String output;

	private Object parseOutput(String s) {
		List<Integer> l;
		Matcher m;
		String t;
		Range r;
		int k;

		if(s == null) {
			return "";
		} else if((m = PTN1.matcher(s)).matches()) {
			return m.group(1);
		} else if((m = PTN2.matcher(s)).matches()) {
			t = m.group(1);
			r = CharSets.parse(t);
			if(r.contains(Integer.MAX_VALUE - 1)) {
				throw new NinaException("notcomplement");
			}

			l = new ArrayList<Integer>();
			for(Interval v : r.intervals()) {
				k = ((Integer)v.getInfimumBound()).intValue();
				for(; v.contains(k); k++)  l.add(k);
			}
			return l;
		} else {
			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) {
		vertex.accepted = accept;
	}

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

		vertex = (ver != null) ? (DBS)ver : createState(q);
		v.linkAlphabet(vertex, Double.valueOf(q.getEdge().toString()),
				parseOutput(output));
		output = null;
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#getLabel()
	 */
	@Override
	public String getLabel() {
		return 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) {
		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(o);
	}

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

	/* (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) {
		throw new NinaException("endtransitionerror", "Markov chain");
	}

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

	/* (non-Javadoc)NinaAction
	 * @see net.morilib.nina.NinaAction#accept()
	 */
	@Override
	public MarkovProcess getMachine() {
		return initial;
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#setMealyEdge(int)
	 */
	@Override
	public void setMealyEdge(int c) {
		char[] a = new char[1];

		a[0] = (char)c;
		setMealyEdge(new String(a));
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#setMealyEdge(java.lang.Object)
	 */
	@Override
	public void setMealyEdge(Object o) {
		output = o != null ? o.toString() : null;
	}

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

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

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

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

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#createState(net.morilib.nina.NinaEvent)
	 */
	public DBS createState(NinaEvent q) {
		return new DBS();
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#setStartState(java.lang.Object)
	 */
	@Override
	public void setStartState(Object o) {
		vertex = (DBS)o;
		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 Collections.emptySet();
	}

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

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

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

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#setEdgeDynamic(net.morilib.nina.NinaEvent, java.lang.String)
	 */
	@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#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#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");
	}

}
