/*
 * 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.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;

import net.morilib.automata.NFA;
import net.morilib.automata.NFAEdges;
import net.morilib.automata.NFAState;
import net.morilib.automata.TextBound;
import net.morilib.range.Interval;
import net.morilib.range.Range;
import net.morilib.util.IntervalMap;

public class ConcurrentNFA<T, A, B> implements NFA<T, A, B> {

	private Collection<NFA<T, A, B>> nfas;
	private NFAState initial;

	/**
	 * 
	 * @param ini
	 * @param c
	 */
	public ConcurrentNFA(NFAState ini, Collection<NFA<T, A, B>> c) {
		nfas = new ArrayList<NFA<T, A, B>>(c);
		initial = ini;
	}

	/**
	 * 
	 * @param ini
	 */
	public ConcurrentNFA(NFAState ini) {
		nfas = new ArrayList<NFA<T, A, B>>();
		initial = ini;
	}

	void add(NFA<T, A, B> t) {
		nfas.add(t);
	}

	@Override
	public boolean isState(NFAState o) {
		if(initial.equals(o))  return true;
		for(NFA<T, A, B> n : nfas) {
			if(n.isState(o))  return true;
		}
		return false;
	}

	@Override
	public Set<NFAState> getStates(NFAState state, T alphabet) {
		Set<NFAState> r = new HashSet<NFAState>();

		for(NFA<T, A, B> n : nfas) {
			r.addAll(n.getStates(state, alphabet));
		}
		return r;
	}

	@Override
	public Set<NFAState> getStates(NFAState state, Range rng) {
		Set<NFAState> r = new HashSet<NFAState>();

		for(NFA<T, A, B> n : nfas) {
			r.addAll(n.getStates(state, rng));
		}
		return r;
	}

	@Override
	public Set<NFAState> getStates(NFAState state,
			EnumSet<TextBound> bound) {
		Set<NFAState> r = new HashSet<NFAState>();

		for(NFA<T, A, B> n : nfas) {
			r.addAll(n.getStates(state, bound));
		}
		return r;
	}

	@Override
	public Set<NFAState> getStatesEpsilon(NFAState state) {
		Set<NFAState> r = new HashSet<NFAState>();

		if(initial.equals(state)) {
			for(NFA<T, A, B> n : nfas) {
				r.addAll(n.getInitialStates());
			}
		} else {
			for(NFA<T, A, B> n : nfas) {
				r.addAll(n.getStatesEpsilon(state));
			}
		}
		return r;
	}

	@Override
	public Set<NFAState> getStatesBound(NFAState state,
			EnumSet<TextBound> bound) {
		Set<NFAState> r = new HashSet<NFAState>();

		for(NFA<T, A, B> n : nfas) {
			r.addAll(n.getStatesBound(state, bound));
		}
		return r;
	}

	@Override
	public Set<NFAState> getInitialStates() {
		return Collections.<NFAState>singleton(initial);
	}

	@Override
	public boolean isInitialState(NFAState o) {
		return initial.equals(o);
	}

	@Override
	public boolean isFinal(NFAState state) {
		for(NFA<T, A, B> n : nfas) {
			if(n.isFinal(state))  return true;
		}
		return false;
	}

	@Override
	public boolean isFinalAny(Set<NFAState> states) {
		for(NFA<T, A, B> n : nfas) {
			if(n.isFinalAny(states))  return true;
		}
		return false;
	}

	@Override
	public NFAEdges<T> getEdges(final NFAState state) {
		final List<NFAEdges<T>> l = new ArrayList<NFAEdges<T>>();

		if(initial.equals(state)) {
			return new NFAEdges<T>() {

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

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

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

				@Override
				public Set<NFAState> goNextEpsilon() {
					return getStatesEpsilon(initial);
				}

				@Override
				public Set<? extends Range> nextAlphabets() {
					return Collections.emptySet();
				}

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

			};
		} else {
			for(NFA<T, A, B> n : nfas) {
				l.add(n.getEdges(state));
			}

			return new NFAEdges<T>() {
	
				@Override
				public Set<NFAState> goNext(T alphabet) {
					Set<NFAState> r = new HashSet<NFAState>();
	
					for(NFAEdges<T> n : l) {
						r.addAll(n.goNext(alphabet));
					}
					return r;
				}
	
				@SuppressWarnings("unchecked")
				@Override
				public Set<NFAState> goNext(int alphabet) {
					Set<NFAState> r = new HashSet<NFAState>();
	
					for(NFAEdges<T> n : l) {
						r.addAll(n.goNext(
								(T)Integer.valueOf(alphabet)));
					}
					return r;
				}
	
				@SuppressWarnings("unchecked")
				@Override
				public Set<NFAState> goNext(char alphabet) {
					Set<NFAState> r = new HashSet<NFAState>();
	
					for(NFAEdges<T> n : l) {
						r.addAll(n.goNext(
								(T)Character.valueOf(alphabet)));
					}
					return r;
				}
	
				@Override
				public Set<NFAState> goNextEpsilon() {
					Set<NFAState> r = new HashSet<NFAState>();
	
					for(NFAEdges<T> n : l) {
						r.addAll(n.goNextEpsilon());
					}
					return r;
				}
	
				@Override
				public Set<? extends Range> nextAlphabets() {
					Set<Range> r = new HashSet<Range>();
	
					for(NFAEdges<T> n : l) {
						r.addAll(n.nextAlphabets());
					}
					return r;
				}
	
				@Override
				public boolean isNextEpsilon() {
					for(NFAEdges<T> n : l) {
						if(n.isNextEpsilon())  return true;
					}
					return false;
				}
	
			};
		}
	}

	@Override
	public Set<Interval> nextAlphabets(NFAState state) {
		IntervalMap<Void> m = null;

		m = new IntervalMap<Void>();
		for(NFA<T, A, B> n : nfas) {
			for(Interval v : n.nextAlphabets(state)) {
				m.put(v, null);
			}
		}
		return m.keySet();
	}

	@Override
	public Iterable<Interval> nextAlphabets(
			final Set<NFAState> states) {
		IntervalMap<Void> m = null;

		m = new IntervalMap<Void>();
		for(NFA<T, A, B> n : nfas) {
			for(Interval v : n.nextAlphabets(states)) {
				m.put(v, null);
			}
		}
		return m.keySet();
	}

	@Override
	public Set<T> nextDiscreteAlphabets(NFAState state) {
		Set<T> r = new HashSet<T>();

		for(NFA<T, A, B> n : nfas) {
			r.addAll(n.nextDiscreteAlphabets(state));
		}
		return r;
	}

	@Override
	public Iterable<T> nextDiscreteAlphabets(
			final Set<NFAState> states) {
		return new Iterable<T>() {

			private Iterator<NFAState> i;
			private Iterator<T> j;

			@Override
			public Iterator<T> iterator() {
				i = states.iterator();
				j = i.hasNext() ?
						nextDiscreteAlphabets(i.next()).iterator() :
							null;
				return new Iterator<T>() {

					@Override
					public boolean hasNext() {
						return i.hasNext() || j.hasNext();
					}

					@Override
					public T next() {
						while(!j.hasNext()) {
							if(i.hasNext()) {
								j = nextDiscreteAlphabets(
										i.next()).iterator();
							} else {
								throw new NoSuchElementException();
							}
						}
						return j.next();
					}

					@Override
					public void remove() {
						throw new UnsupportedOperationException();
					}

				};
			}

		};
	}

	@Override
	public Set<NFAState> getAcceptedStates() {
		Set<NFAState> r = new HashSet<NFAState>();

		for(NFA<T, A, B> n : nfas) {
			r.addAll(n.getAcceptedStates());
		}
		return r;
	}

	@Override
	public Set<B> getMatchTag(NFAState state) {
		Set<B> r = new HashSet<B>();

		for(NFA<T, A, B> n : nfas) {
			r.addAll(n.getMatchTag(state));
		}
		return r;
	}

	@Override
	public Set<B> getMatchTagEnd(NFAState state) {
		Set<B> r = new HashSet<B>();

		for(NFA<T, A, B> n : nfas) {
			r.addAll(n.getMatchTagEnd(state));
		}
		return r;
	}

	@Override
	public Set<A> getAccept(NFAState state) {
		Set<A> r = new HashSet<A>();

		for(NFA<T, A, B> n : nfas) {
			r.addAll(n.getAccept(state));
		}
		return r;
	}

	@Override
	public boolean isAccepted(NFAState state) {
		for(NFA<T, A, B> n : nfas) {
			if(n.isAccepted(state))  return true;
		}
		return false;
	}

	public String toString() {
		StringWriter b = new StringWriter();
		PrintWriter p = new PrintWriter(b);

		for(NFA<T, A, B> n : nfas) {
			p.format("%s\n", n);
		}
		return b.toString();
	}

}
