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

import java.util.HashMap;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;

/**
 * 文字列を置換するユーティリティクラスです。<br />
 * UNIXのtr(1)コマンド互換の動作をします。
 *
 *
 * @author MORIGUCHI, Yuichiro 2013/02
 */
public class Tr {

	//
	private static class P1 implements Comparable<P1> {

		int bgn, end;

		P1(int b, int e) {
			bgn = b;  end = e;
		}

		public int hashCode() {
			return bgn;
		}

		public boolean equals(Object o) {
			if(o instanceof P1) {
				return bgn == ((P1)o).bgn && end == ((P1)o).end;
			}
			return false;
		}

		public int compareTo(P1 p) {
			if(bgn < p.bgn) {
				return -1;
			} else if(bgn > p.bgn) {
				return 1;
			} else if(end < p.end) {
				return -1;
			} else if(end > p.end) {
				return 1;
			} else {
				return 0;
			}
		}

	}

	//
	private static abstract class T1 {

		abstract int indexOf(String s, int c);

	}

	private static final T1 DF = new T1() {

		@Override
		public int indexOf(String s, int c) {
			return Tr.indexOf(s, c);
		}

	};

	private static final T1 CF = new T1() {

		@Override
		public int indexOf(String s, int c) {
			return Tr.complementIndexOf(s, c);
		}

	};

	private static final T1 ALNUM = new T1() {

		@Override
		public int indexOf(String s, int c) {
			return ALPHA.indexOf(s, c) < 0 ? DIGIT.indexOf(s, c) : 0;
		}

	};

	private static final T1 ALPHA = new T1() {

		@Override
		public int indexOf(String s, int c) {
			return UPPER.indexOf(s, c) < 0 ? LOWER.indexOf(s, c) : 0;
		}

	};

	private static final T1 BLANK = new T1() {

		@Override
		public int indexOf(String s, int c) {
			return " \t　".indexOf(c) >= 0 ? 0 : -1;
		}

	};

	private static final T1 CNTRL = new T1() {

		@Override
		public int indexOf(String s, int c) {
			return Character.isISOControl(c) ? 0 : -1;
		}

	};

	private static final T1 DIGIT = new T1() {

		@Override
		public int indexOf(String s, int c) {
			return Character.isDigit(c) ? 0 : -1;
		}

	};

	private static final T1 GRAPH = new T1() {

		@Override
		public int indexOf(String s, int c) {
			return Character.isISOControl(c) ?
					-1 : BLANKC.indexOf(s, c);
		}

	};

	private static final T1 LOWER = new T1() {

		@Override
		public int indexOf(String s, int c) {
			return Character.isLowerCase(c) ? 0 : -1;
		}

	};

	private static final T1 PRINT = new T1() {

		@Override
		public int indexOf(String s, int c) {
			return Character.isISOControl(c) ? -1 : 0;
		}

	};

	private static final String PNCT =
			"!\"#$%&'()=-~^|\\[]{}*+_?.><,@`;:";
	private static final String PNCTF =
			"！”＃＄％＆’（）＝ー＾〜￥＠｀" +
			"｜｛｝［］：＊；＋＿？／．，．" +
			"「」。、・";

	private static final T1 PUNCT = new T1() {

		@Override
		public int indexOf(String s, int c) {
			if(PNCT.indexOf(c) >= 0 || PNCTF.indexOf(c) >= 0) {
				return 0;
			} else {
				return -1;
			}
		}

	};

	private static final T1 SPACE = new T1() {

		@Override
		public int indexOf(String s, int c) {
			return Character.isWhitespace(c) ? 0 : -1;
		}

	};

	private static final T1 UPPER = new T1() {

		@Override
		public int indexOf(String s, int c) {
			return Character.isUpperCase(c) ? 0 : -1;
		}

	};

	private static final T1 XDIGIT = new T1() {

		@Override
		public int indexOf(String s, int c) {
			return "0123456789ABCDEFabcdef".indexOf(c) >= 0 ? 0 : -1;
		}

	};

	private static final T1 ALNUMC = new T1() {

		@Override
		public int indexOf(String s, int c) {
			return ALNUM.indexOf(s, c) < 0 ? 0 : -1;
		}

	};

	private static final T1 ALPHAC = new T1() {

		@Override
		public int indexOf(String s, int c) {
			return ALPHA.indexOf(s, c) < 0 ? 0 : -1;
		}

	};

	private static final T1 BLANKC = new T1() {

		@Override
		public int indexOf(String s, int c) {
			return BLANK.indexOf(s, c) < 0 ? 0 : -1;
		}

	};

	private static final T1 CNTRLC = new T1() {

		@Override
		public int indexOf(String s, int c) {
			return CNTRL.indexOf(s, c) < 0 ? 0 : -1;
		}

	};

	private static final T1 DIGITC = new T1() {

		@Override
		public int indexOf(String s, int c) {
			return DIGIT.indexOf(s, c) < 0 ? 0 : -1;
		}

	};

	private static final T1 GRAPHC = new T1() {

		@Override
		public int indexOf(String s, int c) {
			return GRAPH.indexOf(s, c) < 0 ? 0 : -1;
		}

	};

	private static final T1 LOWERC = new T1() {

		@Override
		public int indexOf(String s, int c) {
			return LOWER.indexOf(s, c) < 0 ? 0 : -1;
		}

	};

	private static final T1 PRINTC = new T1() {

		@Override
		public int indexOf(String s, int c) {
			return PRINT.indexOf(s, c) < 0 ? 0 : -1;
		}

	};

	private static final T1 PUNCTC = new T1() {

		@Override
		public int indexOf(String s, int c) {
			return PUNCT.indexOf(s, c) < 0 ? 0 : -1;
		}

	};

	private static final T1 SPACEC = new T1() {

		@Override
		public int indexOf(String s, int c) {
			return SPACE.indexOf(s, c) < 0 ? 0 : -1;
		}

	};

	private static final T1 UPPERC = new T1() {

		@Override
		public int indexOf(String s, int c) {
			return UPPER.indexOf(s, c) < 0 ? 0 : -1;
		}

	};

	private static final T1 XDIGITC = new T1() {

		@Override
		public int indexOf(String s, int c) {
			return XDIGIT.indexOf(s, c) < 0 ? 0 : -1;
		}

	};

	private static Map<String, T1> classe, classec;

	static {
		classe = new HashMap<String, T1>();
		classe.put("[:alnum:]", ALNUM);
		classe.put("[:alpha:]", ALPHA);
		classe.put("[:blank:]", BLANK);
		classe.put("[:cntrl:]", CNTRL);
		classe.put("[:digit:]", DIGIT);
		classe.put("[:graph:]", GRAPH);
		classe.put("[:lower:]", LOWER);
		classe.put("[:print:]", PRINT);
		classe.put("[:punct:]", PUNCT);
		classe.put("[:space:]", SPACE);
		classe.put("[:upper:]", UPPER);
		classe.put("[:xdigit:]", XDIGIT);

		classec = new HashMap<String, T1>();
		classec.put("[:alnum:]", ALNUMC);
		classec.put("[:alpha:]", ALPHAC);
		classec.put("[:blank:]", BLANKC);
		classec.put("[:cntrl:]", CNTRLC);
		classec.put("[:digit:]", DIGITC);
		classec.put("[:graph:]", GRAPHC);
		classec.put("[:lower:]", LOWERC);
		classec.put("[:print:]", PRINTC);
		classec.put("[:punct:]", PUNCTC);
		classec.put("[:space:]", SPACEC);
		classec.put("[:upper:]", UPPERC);
		classec.put("[:xdigit:]", XDIGITC);
	}

	//
	static int _get16(String s, int[] i) {
		int y;

		if(i[0] >= s.length()) {
			return -1;
		} else if((y = s.charAt(i[0]++)) >= '0' && y <= '9') {
			return y - '0';
		} else if(y >= 'a' && y <= 'f') {
			return y - 'a' + 10;
		} else if(y >= 'A' && y <= 'F') {
			return y - 'A' + 10;
		} else {
			i[0]--;
			return -1;
		}
	}

	//
	static int _add16(int dgts, String s, int[] i) {
		int x, r = 0;

		if(i[0] >= s.length())  return -1;
		for(int j = 0; j < dgts; j++) {
			if((x = _get16(s, i)) < 0)  return r;
			r = r * 16 + x;
		}
		return r;
	}

	//
	static int _get8(String s, int[] i) {
		int y;

		if(i[0] >= s.length()) {
			return -1;
		} else if((y = s.charAt(i[0]++)) >= '0' && y <= '7') {
			return y - '0';
		} else {
			i[0]--;
			return -1;
		}
	}

	//
	static int _add8(int dgts, String s, int[] i) {
		int x, r = 0;

		if(i[0] >= s.length())  return -1;
		for(int j = 0; j < dgts; j++) {
			if((x = _get8(s, i)) < 0)  return r;
			r = r * 8 + x;
		}
		return r;
	}

	//
	static int _getch(String s, int[] i) {
		int x;

		x = s.codePointAt(i[0]);
		i[0] += x > 0xffff ? 2 : 1;
		if(x == '\\' && i[0] < s.length()) {
			switch(x = s.charAt(i[0]++)) {
			case 'a':   return '\u0007';
			case 'b':   return '\u0008';
			case 'f':   return '\u000b';
			case 'n':   return '\n';
			case 'r':   return '\r';
			case 't':   return '\u0009';
			case 'v':   return '\u000b';
			case '\\':  return '\\';
			case 'u':
				i[0]++;
				x = _add16(4, s, i);
				return x < 0 ? 'u' : x;
			case '0':  case '1':  case '2':  case '3':
			case '4':  case '5':  case '6':  case '7':
				x = _add8(3, s, i);
				return x < 0 ? '0' : x;
			default:  return x;
			}
		}
		return x;
	}

	//
	static int indexOf(String s, int c) {
		int[] i = new int[1];
		int x, y;

		i[0] = 0;
		for(int j = 0; i[0] < s.length(); j++) {
			if(c == (x = _getch(s, i))) {
				return j;
			} else if(i[0] < s.length() &&
					s.codePointAt(i[0]) == '-') {
				if(c <= (y = s.codePointAt(++i[0])) && c > x) {
					return j + c - x;
				}
				i[0] += y > 0xffff ? 2 : 1;
				j    += y - x;
			}
		}
		return -1;
	}

	//
	static int complementIndexOf(String s, int c) {
		SortedSet<P1> l = new TreeSet<P1>();
		int[] i = new int[1];
		int x, y;

		for(i[0] = 0; i[0] < s.length();) {
			x = _getch(s, i);
			if(i[0] < s.length() && s.codePointAt(i[0]) == '-') {
				y = s.codePointAt(++i[0]);
				i[0] += y > 0xffff ? 2 : 1;
				l.add(new P1(x, y));
			} else {
				l.add(new P1(x, x));
			}
		}

		x = y = 0;
		for(P1 p : l) {
			if(c < p.bgn) {
				return y + c - x;
			} else if(c <= p.end) {
				return -1;
			} else {
				y = p.bgn < x ? y : y + p.bgn - x;
				x = p.end + 1;
			}
		}
		return y + c - x;
	}

	//
	static int codePointAt(String s, int index) {
		int[] i = new int[1];
		int x, y;

		i[0] = 0;
		for(int j = 0; i[0] < s.length(); j++) {
			x = _getch(s, i);
			if(index == j) {
				return x;
			} else if(i[0] < s.length() &&
					s.codePointAt(i[0]) == '-') {
				if(index - j <= (y = s.codePointAt(++i[0])) - x) {
					return x + index - j;
				}
				i[0] += y > 0xffff ? 2 : 1;
				j += y - x;
			}
		}
		return -1;
	}

	//
	static int length(String s) {
		int[] i = new int[1];
		int x, y, j = 0;

		for(i[0] = 0; i[0] < s.length(); j++) {
			x = _getch(s, i);
			if(i[0] < s.length() && s.codePointAt(i[0]) == '-') {
				y = s.codePointAt(++i[0]);
				i[0] += y > 0xffff ? 2 : 1;
				j += y - x;
			}
		}
		return j;
	}

	//
	static int tr(T1 t1, String source, String dest, int c,
			boolean trunc) {
		int x, d;

		if(source.length() == 0) {
			return c;
		} else if(dest.length() == 0) {
			return c;
		} else if((x = t1.indexOf(source, c)) < 0 ||
				(trunc && x >= length(dest))) {
			return c;
		} else if((d = codePointAt(dest, x)) >= 0) {
			return d;
		} else {
			return codePointAt(dest, length(dest) - 1);
		}
	}

	//
	static String tr(T1 t1, String source, String dest, String s,
			boolean tranc) {
		StringBuffer b = new StringBuffer();
		int c;

		for(int i = 0; i < s.length(); i += c > 0xffff ? 2 : 1) {
			b.appendCodePoint(tr(
					t1, source, dest, c = s.codePointAt(i), tranc));
		}
		return b.toString();
	}

	/**
	 * 文字を入れ替えます。
	 * 
	 * @param source 入替前の文字パターン
	 * @param dest   入替後の文字パターン
	 * @param c      対象の文字
	 * @param cmp    sourceの補集合をとるときtrue
	 * @param cmp    sourceをdestの長さに切り捨てるときtrue
	 * @return       入れ替えた結果の文字
	 */
	public static int tr(String source, String dest, int c,
			boolean cmp, boolean trunc) {
		Map<String, T1> m = cmp ? classec : classe;
		T1 t;

		if(source.equals(dest)) {
			return c;
		} else if(source.equals("[:upper:]") &&
				dest.equals("[:lower:]")) {
			return Character.toLowerCase(c);
		} else if(source.equals("[:lower:]") &&
				dest.equals("[:upper:]")) {
			return Character.toUpperCase(c);
		} else if((t = m.get(source)) != null) {
			return tr(t, source, dest, c, trunc);
		} else if(cmp) {
			return tr(CF, source, dest, c, trunc);
		} else {
			return tr(DF, source, dest, c, trunc);
		}
	}

	/**
	 * 文字列を入れ替えます。
	 * 
	 * @param source 入替前の文字パターン
	 * @param dest   入替後の文字パターン
	 * @param s      対象の文字列
	 * @param cmp    sourceの補集合をとるときtrue
	 * @return       入れ替えた結果の文字
	 */
	public static String tr(String source, String dest, String s,
			boolean cmp, boolean trunc) {
		StringBuffer b = new StringBuffer();
		int c;

		for(int i = 0; i < s.length(); i += c > 0xffff ? 2 : 1) {
			b.appendCodePoint(tr(
					source, dest, c = s.codePointAt(i), cmp, trunc));
		}
		return b.toString();
	}

	/**
	 * 文字列を入れ替えます。
	 * 
	 * @param source 入替前の文字パターン
	 * @param dest   入替後の文字パターン
	 * @param s      対象の文字列
	 * @return       入れ替えた結果の文字
	 */
	public static String tr(String source, String dest, String s) {
		StringBuffer b = new StringBuffer();
		int c;

		for(int i = 0; i < s.length(); i += c > 0xffff ? 2 : 1) {
			b.appendCodePoint(tr(
					DF, source, dest, c = s.codePointAt(i), false));
		}
		return b.toString();
	}

	/**
	 * 集合にある文字のときインデックス番号を得ます。
	 * 
	 * @param source 文字パターン
	 * @param c      対象の文字
	 * @param cmp    sourceの補集合をとるときtrue
	 * @return       インデックス番号:見つからないとき-1
	 */
	public static int indexOf(String source, int c, boolean cmp) {
		Map<String, T1> m = cmp ? classec : classe;
		T1 t;

		if((t = m.get(source)) == null)  t = cmp ? CF : DF;
		return t.indexOf(source, c);
	}

}
