/*
 * 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.sh.misc;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.IdentityHashMap;
import java.util.Map;

/**
 *
 *
 * @author MORIGUCHI, Yuichiro 2013/06/02
 */
public class KeywordMatcher {

	IntTrie trie;
	Map<IntTrie, IntTrie> recover;
	boolean ci;

	private KeywordMatcher() {}

	//
	void _compile1(boolean ci, IntTrie t, String s) {
		IntTrie x = t, y;
		int c, d;

		for(int i = 0; i < s.length(); i++) {
			d = s.charAt(i);
			c = ci ? Character.toUpperCase(d) : d;
			if(x.contains(c)) {
				x = x.go(c);
			} else {
				recover.put(y = new IntTrie(), null);
				x.edges.put(c, y);
				y.parent = x;
				y.moji = d;
				x = y;
			}
		}
		x.accept = true;
	}

	//
	void _recover(boolean ci, IntTrie t) {
		IntTrie x;
		String v;
		int c;

		v = t.getKeyword();
		outer: for(int i = 1; i < v.length(); i++) {
			x = trie;
			for(int j = i; j < v.length(); j++) {
				c = v.charAt(j);
				if(ci)  c = Character.toUpperCase(c);
				if(!x.contains(c))  continue outer;
				x = x.go(c);
			}
			recover.put(t, x);
			return;
		}
	}

	//
	void _compile(boolean ci, String... patterns) {
		trie = new IntTrie();
		recover = new IdentityHashMap<IntTrie, IntTrie>();
		for(String s : patterns)  _compile1(ci, trie, s);
		for(IntTrie t : recover.keySet())  _recover(ci, t);
	}

	/**
	 * 
	 * @param patterns
	 * @return
	 */
	public static KeywordMatcher compile(boolean caseInsensitive,
			String... patterns) {
		KeywordMatcher p = new KeywordMatcher();

		p.ci = caseInsensitive;
		p._compile(p.ci, patterns);
		return p;
	}

	private IntTrie _next(int d, IntTrie t) {
		int c;

		c = ci ? Character.toUpperCase(d) : d;
		if(t.contains(c)) {
			return t.go(c);
		} else if((t = recover.get(t)) == null) {
			t = trie;
		}
		return t.contains(c) ? t.go(c) : trie;
	}

	private IntTrie _nextnotrecover(int d, IntTrie t) {
		int c;

		c = ci ? Character.toUpperCase(d) : d;
		return t.contains(c) ? t.go(c) : null;
	}

	/**
	 * 
	 * @param r
	 * @return
	 * @throws IOException
	 */
	public String find(Reader r) throws IOException {
		IntTrie t = trie;
		String s = null;
		int c;

		while((c = r.read()) >= 0) {
			if(t.isAccepted())  s = t.getKeyword();
			if(t.isDead())  return s;
			t = _next(c, t);
		}
		return t.isAccepted() ? t.getKeyword() : s;
	}

	/**
	 * 
	 * @param s
	 * @return
	 */
	public String find(String s) {
		try {
			return find(new StringReader(s));
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 
	 * @param r
	 * @return
	 * @throws IOException
	 */
	public String matches(Reader r) throws IOException {
		IntTrie t = trie;
		int c;

		while((c = r.read()) >= 0) {
			if((t = _nextnotrecover(c, t)) == null)  return null;
		}
		return t.isAccepted() ? t.getKeyword() : null;
	}

	/**
	 * 
	 * @param s
	 * @return
	 */
	public String matches(String s) {
		try {
			return matches(new StringReader(s));
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 
	 * @param r
	 * @return
	 * @throws IOException
	 */
	public String findWholeWord(Reader r) throws IOException {
		boolean x = true, y = false;
		IntTrie t = trie;
		String s = null;
		int c;

		while((c = r.read()) >= 0) {
			if(x && y && Character.isWhitespace(c))  return s;
			x = t.contains(c) ? x : Character.isWhitespace(c);
			t = _next(c, t);
			if(y = t.isAccepted())  s = t.getKeyword();
		}
		return x && t.isAccepted() ? t.getKeyword() : null;
	}

	/**
	 * 
	 * @param s
	 * @return
	 */
	public String findWholeWord(String s) {
		try {
			return findWholeWord(new StringReader(s));
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

}
