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

import net.morilib.automata.NFA;
import net.morilib.automata.NFAState;
import net.morilib.automata.nfa.NFAAccept;
import net.morilib.automata.nfa.NFAConcatenation;
import net.morilib.automata.nfa.NFAObject;
import net.morilib.automata.nfa.SingleObjectNFA;
import net.morilib.range.Range;

/**
 *
 *
 * @author MORIGUCHI, Yuichiro 2013/10/13
 */
public class NFABuilder implements NinaAction {

	private NinaState vertex;
	private NinaNFA nfa;
	private String label;
	private String name;

	/**
	 * 
	 * @param q
	 */
	public NFABuilder() {
		nfa = new NinaNFA();
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#labelAdded(int, java.lang.String, net.morilib.nina.NinaState)
	 */
	@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 = new NinaState();
			nfa.initial = vertex;
		}

		vertex.label = wr.toString();
		if(accept) {
			// accepted state
			nfa.accept.put(vertex, null);
		}
	}

	@SuppressWarnings("unchecked")
	public void link(NinaEvent q, Object ver) {
		NinaState v = vertex;

		vertex = (ver != null) ? (NinaState)ver : new NinaState();
		if(q.getEdge() instanceof NFA) {
			nfa.linkNFA(v, vertex,
					(NFA<Object, NFAState, Void>)q.getEdge());
		} else if(q.getEdge() instanceof Range) {
			nfa.linkAlphabet(v, vertex, (Range)q.getEdge());
		} else {
			nfa.linkAlphabet(v, vertex, q.getEdge());
		}
	}

	/* (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 = (NinaState)o;
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#doneBlockSearching(net.morilib.nina.NinaEvent)
	 */
	@Override
	public void doneBlockSearching(NinaEvent q) {
		// searching a block is done
		vertex = (NinaState)q.getScratch();
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#setEdge(net.morilib.nina.NinaEvent, java.lang.Character)
	 */
	@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 s,
			int qc) {
		List<NFAObject<Object, NFAState, Void>> l;
		NFAObject<Object, NFAState, Void> o;
		Integer c;

		l = new ArrayList<NFAObject<Object, NFAState, Void>>();
		for(int k = 0; k < s.length(); k++) {
			c = Nina.prendCharcode(q.getCharset(), s.charAt(k));
			o = SingleObjectNFA.newInstance((Object)c);
			l.add(o);
		}
		o = NFAConcatenation.newInstance(l);
		q.setEdge(NFAAccept.newInstance(o, null));
	}

	/* (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> b) {
		q.setEdge(p.compileSubautomaton(s, new NFABuilder(), map,
				b).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) {
		q.setEdge(s);
	}

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

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

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

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#setMealyEdge(int)
	 */
	@Override
	public void setMealyEdge(int c) {
		throw new NinaException("isnotdfa");
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#setMealyEdge(java.lang.Object)
	 */
	@Override
	public void setMealyEdge(Object o) {
		throw new NinaException("isnotdfa");
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaAction#setPriority(int)
	 */
	@Override
	public void setPriority(int p) {
		if(vertex != null) {
			vertex.priority = p;
		}
	}

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

	/* (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#setStartState(java.lang.Object)
	 */
	@Override
	public void setStartState(Object o) {
		vertex = (NinaState)o;
		nfa.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, int len) {
		throw new NinaException("isnotdfa");
	}

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

}
