/*
 * Copyright 2009 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.dfa;

import java.util.Collections;
import java.util.EnumSet;
import java.util.Set;

import net.morilib.automata.CharSequenceHead;
import net.morilib.automata.TextBound;

public final class DFAs {
	
	private DFAs() {
		// do nothing
	}
	
	
	public static final DFAState<Object, Object, Object>
	DEAD_STATE = new DFAState<Object, Object, Object>() {

		public Set<Object> getAccepted() {
			return Collections.emptySet();
		}

		public DFAState<Object, Object, Object> go(Object a) {
			return this;
		}

		public DFAState<Object, Object, Object> goBound(TextBound a) {
			return this;
		}

		public boolean isInitialState() {
			return false;
		}

		public boolean isDead() {
			return true;
		}
		
	};
	
	
	@SuppressWarnings("unchecked")
	public static<T, A, B> DFAState<T, A, B> deadState() {
		return (DFAState<T, A, B>)DEAD_STATE;
	}
	
	
	public static<A, B> Set<A> input(
			DFAState<Integer, A, B> st, CharSequenceHead seq) {
		DFAState<Integer, A, B> s2 = st;
		int c;
		
		while(seq.hasNext()) {
			DFAState<Integer, A, B> s3 = s2;
			EnumSet<TextBound> bs = seq.getBounds();
			
			c = seq.readInt();
			while((s2 = s2.go(c)).isDead()) {
				for(TextBound b : bs) {
					s2 = s3.goBound(b);
					if(!s2.isDead()) {
						s3 = s2;
						break;
					}
				}
				if(s2.isDead()) {
					return Collections.emptySet();
				}
			}
			
		}
		
		loop: while(true) {
			DFAState<Integer, A, B> s3 = s2;
			
			for(TextBound b : seq.getBounds()) {
				s3 = s2.goBound(b);
				if(!s3.isDead()) {
					s2 = s3;
					continue loop;
				}
			}
			break;
		}
		return s2.getAccepted();
	}
	
	
	public static<A, B> Set<A> input(
			DFAState<Integer, A, B> st, CharSequence seq) {
		/*DFAState<Integer, A, B> s2 = st;
		
		for(int i = 0; i < seq.length(); i++) {
			s2 = s2.go((int)seq.charAt(i));
			if(s2.isDead()) {
				return Collections.emptySet();
			}
		}
		return s2.getAccepted();*/
		return input(st, new CharSequenceHead(seq));
	}
	
	
	public static<A, B> Set<A> input(
			DFA<Integer, A, B> dfa, CharSequenceHead seq) {
		return input(dfa.getInitialState(), seq);
	}
	
	
	public static<A, B> Set<A> input(
			DFA<Integer, A, B> dfa, CharSequence seq) {
		return input(dfa, new CharSequenceHead(seq));
	}
	
	
	public static<A, B> Set<A> match(
			DFAState<Integer, A, B> st, CharSequenceHead seq) {
		DFAState<Integer, A, B> s2 = st;
		int c;
		
		while(seq.hasNext()) {
			DFAState<Integer, A, B> s3 = s2;
			EnumSet<TextBound> bs = seq.getBounds();
			
			c = seq.readInt();
			while((s2 = s2.go(c)).isDead()) {
				for(TextBound b : bs) {
					s2 = s3.goBound(b);
					if(!s2.isDead()) {
						s3 = s2;
						break;
					}
				}
				if(s2.isDead()) {
					seq.unread();
					return s3.getAccepted();
				}
			}
			
		}
		
		loop: while(true) {
			DFAState<Integer, A, B> s3 = s2;
			
			for(TextBound b : seq.getBounds()) {
				s3 = s2.goBound(b);
				if(!s3.isDead()) {
					s2 = s3;
					continue loop;
				}
			}
			break;
		}
		return s2.getAccepted();
	}
	
	
	public static<A, B> Set<A> match(
			DFAState<Integer, A, B> st, CharSequence seq) {
		return match(st, new CharSequenceHead(seq));
	}
	
	
	public static<A, B> Set<A> match(
			DFA<Integer, A, B> dfa, CharSequenceHead seq) {
		return match(dfa.getInitialState(), seq);
	}
	
	
	public static<A, B> Set<A> match(
			DFA<Integer, A, B> dfa, CharSequence seq) {
		return match(dfa, new CharSequenceHead(seq));
	}
	
}
