/*
 * 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.unix.regex;

import java.util.List;

import net.morilib.unix.misc.WideString;
import net.morilib.unix.misc.WideStringBuilder;

/**
 * 基本正規表現のマッチ器です。
 *
 *
 * @author MORIGUCHI, Yuichiro 2013/03
 */
public class BasicMatcher {

	private WideString str;
	private BasicPattern pt;
	private WideStringBuilder[] captured;

	BasicMatcher(BasicPattern pt, WideString str) {
		this.pt  = pt;
		this.str = str;
	}

	private void initcap() {
		captured = new WideStringBuilder[10];
	}

	private void putcap(List<Integer> cap, int c) {
		for(Integer i : cap) {
			if(captured[i] == null) {
				captured[i] = new WideStringBuilder();
			}
			captured[i].append(c);
		}
	}

	private boolean delcap(List<Integer> cap) {
		for(Integer i : cap) {
			if(captured[i] != null) {
				if(captured[i].length() <= 0)  return false;
				captured[i].deleteCharAt(captured[i].length() - 1);
			}
		}
		return true;
	}

	private boolean capend(BasicPattern.Elm e, int k) {
		return e.captured > 0 && k >= captured[e.captured].length();
	}

	private boolean isaccepted(int j) {
		BasicPattern.Elm e;
		int bb = 0;

		for(int i = j; i < pt.elements.size(); i++) {
			e = pt.elements.get(i);
			if(e.chars != null) {
				if(e.repeatFrom != 0)  return false;
				for(Integer k : e.capture)  bb |= 1 << k;
			} else if((bb & (1 << e.captured)) == 0) {
				return (captured[e.captured] != null &&
						captured[e.captured].length() == 0);
			}
		}
		return true;
	}

	int matches(WideString s, int n, int t) {
		int c, i = n, j = t, k = 0, l, m = -1, x = -1;
		int f = pt.flags;
		BasicPattern.Elm e = null;

		while((l = i) < s.length()) {
			c = s.charAt(i);
			do {
				if(j >= pt.elements.size())  return i;
				e = pt.elements.get(j);
			} while(capend(e, k) && ++j > 0);

			if(e.capture != null && e.capture.size() > 0) {
				m = Math.max(m, e.capture.get(e.capture.size() - 1));
			}

			if(e.repeatFrom >= 0) {
				for(x = i; i < s.length() && e.isok(i - x); i++) {
					if(!e.chars.contains(c = s.charAt(i), f))  break;
					putcap(e.capture, c);
				}
				if(i - x < e.repeatFrom)  return -1;

				for(; i >= l && (x = matches(s, i, j + 1)) < 0; i--) {
					for(int y = m + 1; y < 10; y++) {
						captured[y] = null;
					}
					if(!delcap(e.capture))  return -1;
				}
				return i < l ? -1 : x;
			} else if(e.chars != null) {
				if(!e.chars.contains(c, f))  break;
				putcap(e.capture, c);
				i++;  j++;
			} else if(captured[e.captured].charAt(k) != c) {
				return -1;
			} else {
				i++;
				k += c > 0xffff ? 2 : 1;
				if(k >= captured[e.captured].length()) {
					j++;
					k = 0;
				}
			}
		}
		return isaccepted(j) ? i : -1;
	}

	/**
	 * マッチ器が部分的に一致するかを調べます。
	 * 
	 * @return 部分一致するときtrue
	 */
	public boolean matches() {
		int z;

		initcap();
		if((pt.flags & BasicPattern.ALL_MATCH) != 0 ||
				pt.isAnchorBoth()) {
			return matchesAll();
		} else if(pt.isAnchorBegin()) {
			return matches(str, 0, 0) >= 0;
		} else {
			for(int i = 0; i <= str.length(); i++) {
				z = matches(str, i, 0);
				if((pt.isAnchorEnd() && z >= str.length()) ||
						(pt.isNoAnchors() && z >= 0)) {
					return true;
				}
				initcap();
			}
			return false;
		}
	}

	/**
	 * マッチ器が全体一致するかを調べます。
	 * 
	 * @return 全体一致するときtrue
	 */
	public boolean matchesAll() {
		initcap();
		return matches(str, 0, 0) >= str.length();
	}

	private boolean iswordend(int z) {
		return (z >= str.length() ||
				(z >= 0 && Character.isWhitespace(str.charAt(z))));
	}

	/**
	 * マッチ器が単語全体に一致するかを調べます。
	 * 
	 * @return 部分一致するときtrue
	 */
	public boolean matchesWholeWord() {
		int z;

		if((pt.flags & BasicPattern.ALL_MATCH) != 0 ||
				pt.isAnchorBoth()) {
			return matchesAll();
		} else if(pt.isAnchorBegin()) {
			return matches(str, 0, 0) >= 0;
		} else {
			for(int i = 0; i <= str.length(); i++) {
				if(i > 0 &&
						!Character.isWhitespace(str.charAt(i - 1))) {
					continue;
				} else {
					initcap();
					z = matches(str, i, 0);
					if((pt.isAnchorEnd() && z >= str.length()) ||
							(pt.isNoAnchors() && iswordend(z))) {
						return true;
					}
				}
			}
			return false;
		}
	}

	private WideString rep2(String r) {
		WideStringBuilder b = new WideStringBuilder();
		final int INI = 10, ESC = 20;
		int c, stat = INI, p;

		for(int i = 0; i < r.length(); i += c > 0xffff ? 2 : 1) {
			c = r.codePointAt(i);
			switch(stat) {
			case INI:
				if(c == '\\') {
					stat = ESC;
				} else {
					b.append(c);
				}
				break;
			case ESC:
				if(c < '1' || c > '9') {
					b.append(c);
				} else {
					p = c - '0';
					if(captured[p] != null)  b.append(captured[p]);
				}
				stat = INI;
				break;
			default:  throw new RuntimeException();
			}
		}
		return b.toWideString();
	}

	/**
	 * マッチした場所を置き換えます。<br />
	 * 引数により何番目のマッチを対称にするかを指定できます。
	 * 
	 * @param replacement 置き換える文字列
	 * @param occurence   置き換える対象のマッチ番号: -1のときすべて
	 * @return 置き換えた結果
	 */
	public String replace(String replacement, int occurence) {
		WideStringBuilder s = new WideStringBuilder();
		WideString r;
		int o = 1, p, q = -1;

		for(int i = 0; i <= str.length();) {
			initcap();
			if((p = matches(str, i, 0)) < 0) {
				if(i < str.length())  s.append(str.charAt(i));
				i++;
			} else if(p > i || q != i) {
				r = rep2(replacement);
				if(occurence <= 0 || occurence == o++) {
					s.append(r);
					if(occurence > 0) {
						if(p < str.length()) {
							s.append(str.substring(p));
						}
						break;
					}
				} else {
					s.append(str.substring(i, p));
				}
				q = i = p;
			} else {
				if(i < str.length())  s.append(str.charAt(i));
				i++;
			}
		}
		return s.toString();
	}

	/**
	 * マッチしたすべての場所を置き換えます。
	 * 
	 * @param replacement 置き換える文字列
	 * @return 置き換えた結果
	 */
	public String replaceAll(String replacement) {
		return replace(replacement, -1);
	}

	/**
	 * マッチした最初の場所を置き換えます。
	 * 
	 * @param replacement 置き換える文字列
	 * @return 置き換えた結果
	 */
	public String replaceFirst(String replacement) {
		return replace(replacement, 1);
	}

}
