/*
 * Copyright 2009-2010 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.automata.nfa.op;

import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
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;
import net.morilib.util.Tuple2;

/**
 *
 *
 * @author MORIGUCHI, Yuichiro 2014/01/19
 */
public class MeetNFA<T, A, B, C, D> implements NFA<T, C, D> {

	//
	private static class PState extends Tuple2<NFAState, NFAState>
	implements NFAState {

		public PState(NFAState a, NFAState b) {
			super(a, b);
		}

	};

	//
	private final NFAEdges<T> eedg = new EpsilonEdge<T>(null);

	//
	private NFA<T, A, B> nfa1, nfa2;
	private ResultConverter<A, C> convertA;
	private ResultConverter<B, D> convertB;

	//
	static<T> Set<Tuple2<T, T>> directProduct(Set<T> a, Set<T> b) {
		Set<Tuple2<T, T>> r = new HashSet<Tuple2<T, T>>();

		for(T x : a) {
			for(T y : b) {
				r.add(new Tuple2<T, T>(x, y));
			}
		}
		return r;
	}

	//
	static Set<NFAState> directProductState(Set<NFAState> a,
			Set<NFAState> b) {
		Set<NFAState> r = new HashSet<NFAState>();

		for(NFAState x : a) {
			for(NFAState y : b) {
				r.add(new PState(x, y));
			}
		}
		return r;
	}

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

	/* (non-Javadoc)
	 * @see net.morilib.automata.NFA#getStates(net.morilib.automata.NFAState, java.lang.Object)
	 */
	@Override
	public Set<NFAState> getStates(NFAState state, T alphabet) {
		PState p;

		if(state instanceof PState) {
			p = (PState)state;
			return directProductState(
					nfa1.getStates(p.getA(), alphabet),
					nfa2.getStates(p.getB(), alphabet));
		} else {
			return Collections.emptySet();
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.automata.NFA#getStates(net.morilib.automata.NFAState, net.morilib.range.Range)
	 */
	@Override
	public Set<NFAState> getStates(NFAState state, Range rng) {
		PState p;

		if(state instanceof PState) {
			p = (PState)state;
			return directProductState(
					nfa1.getStates(p.getA(), rng),
					nfa2.getStates(p.getB(), rng));
		} else {
			return Collections.emptySet();
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.automata.NFA#getStates(net.morilib.automata.NFAState, java.util.EnumSet)
	 */
	@Override
	public Set<NFAState> getStates(NFAState state,
			EnumSet<TextBound> bound) {
		PState p;

		if(state instanceof PState) {
			p = (PState)state;
			return directProductState(
					nfa1.getStates(p.getA(), bound),
					nfa2.getStates(p.getB(), bound));
		} else {
			return Collections.emptySet();
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.automata.NFA#getStatesEpsilon(net.morilib.automata.NFAState)
	 */
	@Override
	public Set<NFAState> getStatesEpsilon(NFAState state) {
		PState p;

		if(state instanceof PState) {
			p = (PState)state;
			return directProductState(
					nfa1.getStatesEpsilon(p.getA()),
					nfa2.getStatesEpsilon(p.getB()));
		} else {
			return Collections.emptySet();
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.automata.NFA#getStatesBound(net.morilib.automata.NFAState, java.util.EnumSet)
	 */
	@Override
	public Set<NFAState> getStatesBound(NFAState state,
			EnumSet<TextBound> bound) {
		PState p;

		if(state instanceof PState) {
			p = (PState)state;
			return directProductState(
					nfa1.getStatesBound(p.getA(), bound),
					nfa2.getStatesBound(p.getB(), bound));
		} else {
			return Collections.emptySet();
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.automata.NFA#getInitialStates()
	 */
	@Override
	public Set<NFAState> getInitialStates() {
		return directProductState(
				nfa1.getInitialStates(),
				nfa2.getInitialStates());
	}

	/* (non-Javadoc)
	 * @see net.morilib.automata.NFA#isInitialState(net.morilib.automata.NFAState)
	 */
	@Override
	public boolean isInitialState(NFAState state) {
		PState p;

		if(state instanceof PState) {
			p = (PState)state;
			return (nfa1.isInitialState(p.getA()) &&
					nfa2.isInitialState(p.getB()));
		} else {
			return false;
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.automata.NFA#isFinal(net.morilib.automata.NFAState)
	 */
	@Override
	public boolean isFinal(NFAState state) {
		PState p;

		if(state instanceof PState) {
			p = (PState)state;
			return (nfa1.isFinal(p.getA()) &&
					nfa2.isFinal(p.getB()));
		} else {
			return false;
		}
	}

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

	/* (non-Javadoc)
	 * @see net.morilib.automata.NFA#getEdges(net.morilib.automata.NFAState)
	 */
	@Override
	public NFAEdges<T> getEdges(NFAState state) {
		PState p;

		if(state instanceof PState) {
			final NFAEdges<T> g, h;

			p = (PState)state;
			g = nfa1.getEdges(p.getA());
			h = nfa2.getEdges(p.getB());
			return new NFAEdges<T>() {

				@Override
				public Set<NFAState> goNext(T alphabet) {
					return directProductState(g.goNext(alphabet),
							h.goNext(alphabet));
				}

				@Override
				public Set<NFAState> goNext(int alphabet) {
					return directProductState(g.goNext(alphabet),
							h.goNext(alphabet));
				}

				@Override
				public Set<NFAState> goNext(char alphabet) {
					return directProductState(g.goNext(alphabet),
							h.goNext(alphabet));
				}

				@Override
				public Set<NFAState> goNextEpsilon() {
					return directProductState(g.goNextEpsilon(),
							h.goNextEpsilon());
				}

				private void _p(IntervalMap<Void> m, Range r) {
					for(Interval v : r.intervals())  m.put(v, null);
				}

				@Override
				public Set<? extends Range> nextAlphabets() {
					IntervalMap<Void> m = new IntervalMap<Void>();

					for(Range v : g.nextAlphabets())  _p(m, v);
					for(Range v : h.nextAlphabets())  _p(m, v);
					return m.keySet();
				}

				@Override
				public boolean isNextEpsilon() {
					return !goNextEpsilon().isEmpty();
				}

			};
		} else {
			return eedg;
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.automata.NFA#nextAlphabets(net.morilib.automata.NFAState)
	 */
	@Override
	public Set<Interval> nextAlphabets(NFAState state) {
		IntervalMap<Void> m = new IntervalMap<Void>();

		for(Interval v : nfa1.nextAlphabets(state))  m.put(v, null);
		for(Interval v : nfa2.nextAlphabets(state))  m.put(v, null);
		return m.keySet();
	}

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

		for(NFAState s : states) {
			for(Interval v : nextAlphabets(s)) {
				m.put(v, null);
			}
		}
		return m.keySet();
	}

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

		m.addAll(nfa1.nextDiscreteAlphabets(state));
		m.addAll(nfa2.nextDiscreteAlphabets(state));
		return m;
	}

	/* (non-Javadoc)
	 * @see net.morilib.automata.NFA#nextDiscreteAlphabets(java.util.Set)
	 */
	@Override
	public Iterable<T> nextDiscreteAlphabets(Set<NFAState> states) {
		Set<T> m = new HashSet<T>();

		for(NFAState s : states) {
			m.addAll(nextDiscreteAlphabets(s));
		}
		return m;
	}

	/* (non-Javadoc)
	 * @see net.morilib.automata.NFA#getAcceptedStates()
	 */
	@Override
	public Set<NFAState> getAcceptedStates() {
		return directProductState(
				nfa1.getAcceptedStates(),
				nfa2.getAcceptedStates());
	}

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

		for(B x : nfa1.getMatchTag(state)) {
			for(B y : nfa2.getMatchTag(state)) {
				r.add(convertB.convert(x, y));
			}
		}
		return r;
	}

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

		for(B x : nfa1.getMatchTagEnd(state)) {
			for(B y : nfa2.getMatchTagEnd(state)) {
				r.add(convertB.convert(x, y));
			}
		}
		return r;
	}

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

		for(A x : nfa1.getAccept(state)) {
			for(A y : nfa2.getAccept(state)) {
				r.add(convertA.convert(x, y));
			}
		}
		return r;
	}

	/* (non-Javadoc)
	 * @see net.morilib.automata.NFA#isAccepted(net.morilib.automata.NFAState)
	 */
	@Override
	public boolean isAccepted(NFAState state) {
		PState p;

		if(state instanceof PState) {
			p = (PState)state;
			return (nfa1.isAccepted(p.getA()) &&
					nfa2.isAccepted(p.getB()));
		} else {
			return false;
		}
	}

}
