package junkutil.common;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 文字列操作
 * @author Hiroyuki Shiota
 */
public class StringUtil {
	private static StringUtil singleton = new StringUtil();
	private static HashMap repeatedCharsCash = new HashMap();

	/**
	 * コンストラクタ
	 */
	private StringUtil() {
	}
	
	/**
	 * 最初の文字列のみを置き変える
	 * @param target - 置換対象文字列
	 * @param search - 検索文字列
	 * @param replacement - 置換文字列
	 * @return 置換後文字列
	 */
	public static String replaceFirst(String target, String search, String replacement) {
		int pos = target.indexOf(search);
		return (pos == -1)
					? target
					: target.substring(0, pos) + replacement + target.substring(pos + search.length());
	}

	/**
	 * 全ての文字列を置き換える
	 * @param strValue - 置換対象文字列
	 * @param strOld - 検索文字列
	 * @param strNew - 置換文字列
	 * @return 置換後文字列
	 */
	public static String replaceAll(String strValue, String strOld, String strNew) {
		int pos = 0;
		String strTemp = strValue;
		while (true) {
			pos = strTemp.indexOf(strOld, pos);
			if (pos == -1) return strTemp;
			strTemp = strTemp.substring(0, pos) + strNew + strTemp.substring(pos + strOld.length());
			pos += strOld.length();
		}
	}

	/**
	 * 文字列を連結する
	 * @param args
	 * @param delim
	 * @return
	 */
	public static String join(String [] args, String delim) {
		StringBuffer sb = new StringBuffer();
		for (int i = 0; i < args.length; i++) {
			if (i > 0) sb.append(delim);
			sb.append(args[i]);
		}
		return new String(sb);
	}
	
	/**
	 * 文字列を分割する
	 * @param args
	 * @param delim
	 * @return
	 */
	public static String[] split(String args, String delim) {
		List list = new ArrayList();
		int idx = 0;
		int pos = 0;
		String strTemp = args;
		while (true) {
			idx = strTemp.indexOf(delim, pos);
			if (idx == -1) {
				list.add(args.substring(pos));
				String[] retStrs = new String[list.size()];
				for (int i = 0; i < list.size(); i++) {
					retStrs[i] = (String)list.get(i);
				}
				return retStrs;
			}
			list.add(args.substring(pos, idx));
			//System.out.println(strTemp+","+idx+","+pos+"+"+list);
			pos = idx+delim.length();
			//return null;
		}
	}
	
	/**
	 * 左のみトリムする
	 * @param str
	 * @return trimed string
	 */
	public static String trimLeft(String str) {
		int len = str.length();
		int index = 0;
		for (int i = 0; i < len; i++) {
			char c = str.charAt(i);
			if (c != ' ') {
				index = i;
				break;
			}
		}
		return str.substring(index, len);
	}
	
	/**
	 * 右のみトリムする
	 * @param str
	 * @return trimed string
	 */
	public static String trimRight(String str) {
		int len = str.length();
		int index = 0;
		for (int i = len - 1; i >= 0; i--) {
			char c = str.charAt(i);
			if (c != ' ') {
				index = i+1;
				break;
			}
		}
		return str.substring(0, index);
	}
	
	/**
	 * 左に詰めものをする
	 * @param str
	 * @param len
	 * @return
	 */
	public static String padLeft(String str, int len) {
		return padLeft(str, len, ' ');
	}

	/**
	 * 左に詰めものをする
	 * @param str
	 * @param len
	 * @param pad
	 * @return
	 */
	public static String padLeft(String str, int len, char pad) {
		int padding_len = len - str.length();
		if (padding_len > 0) {
			return getRepeatedChars(pad, padding_len) + str;
		} else {
			return str;
		}
	}
	
	/**
	 * 右に詰めものをする
	 * @param str
	 * @param len
	 * @return
	 */
	public static String padRight(String str, int len) {
		return padRight(str, len, ' ');
	}

	/**
	 * 右に詰めものをする
	 * @param str
	 * @param len
	 * @param pad
	 * @return
	 */
	public static String padRight(String str, int len, char pad) {
		int padding_len = len - str.length();
		if (padding_len > 0) {
			return str + getRepeatedChars(pad, padding_len);
		} else {
			return str;
		}
	}
	
	/**
	 * ブラケットで囲む
	 * @param str
	 * @param both
	 * @return
	 */
	public static String wrapBracket(String str, String both) {
		return both + str + both;
	}
	
	/**
	 * ブラケットで囲む
	 * @param str
	 * @param left
	 * @param right
	 * @return
	 */
	public static String wrapBracket(String str, String left, String right) {
		return left + str + right;
	}
	
	/**
	 * ブラケットを外す
	 * @param str
	 * @param both
	 * @return
	 */
	public static String unwrapBracket(String str, String both) {
		String result = str;
		if (result.startsWith(both)) {
			result = result.substring(both.length(), result.length());
		} else {
			return str;
		}
		if (result.endsWith(both)) {
			result = result.substring(0, result.length() - both.length());
		} else {
			return str;
		}
		return result;
	}
	
	/**
	 * ブラケットを外す
	 * @param str
	 * @param left
	 * @param right
	 * @return
	 */
	public static String unwrapBracket(String str, String left, String right) {
		String result = str;
		if (result.startsWith(left)) {
			result = result.substring(left.length(), result.length());
		} else {
			return str;
		}
		if (result.endsWith(right)) {
			result = result.substring(0, result.length() - right.length());
		} else {
			return str;
		}
		return result;
	}
	
	/**
	 * タブをスペースに置き換える
	 * @param str
	 * @param spaceLen
	 * @return
	 */
	public static String tabToSpace(String str, int spaceLen) {
		return str.replaceAll("\t", getRepeatedSpaces(spaceLen));
	}
	
	/**
	 * スペースをタブに置き換える
	 * @param str
	 * @param spaceLen
	 * @return
	 */
	public static String spaceToTab(String str, int spaceLen) {
		return str.replaceAll(getRepeatedSpaces(spaceLen), "\t");
	}
	
	/**
	 * 任意の長さのスペースを取得する
	 * @param len
	 * @return
	 */
	public static String getRepeatedSpaces(int len) {
		return getRepeatedChars(' ', len);
	}
	
	/**
	 * 任意の長さの文字列を取得する
	 * @param s
	 * @param len
	 * @return
	 */
	public static String getRepeatedChars(String s, int len) {
		if (s.length() != 1) throw new IllegalArgumentException(s);
		return getRepeatedChars(s.charAt(0), len);
	}
	
	/**
	 * g任意の長さの文字列を取得する
	 * @param c
	 * @param len
	 * @return
	 */
	public static String getRepeatedChars(char c, int len) {
		Character cobj = new Character(c);
		StringBuffer sb = null;
		if (repeatedCharsCash.containsKey(cobj)) {
			sb = (StringBuffer)repeatedCharsCash.get(cobj);
		} else {
			sb = new StringBuffer();
			repeatedCharsCash.put(cobj, sb);
		}
		int buffer_len = sb.length();
		if (buffer_len >= len) {
			return sb.substring(0, len);
		} else {
			for (int i = buffer_len; i < len; i++) {
				sb.append(c);
			}
			return sb.substring(0, len);
		}
	}
	
	/**
	 * キャッシュをクリアする
	 */
	public static void clearRepeatedCharsCash() {
		repeatedCharsCash.clear();
		repeatedCharsCash = new HashMap();
	}
	
	/**
	 * n文字の差異を許容するequalsメソッド
	 * @param str1
	 * @param str2
	 * @param err
	 * @return
	 */
	public static boolean fuzzyEquals(String str1, String str2, int err) {
		if (err<0) throw new IllegalArgumentException(); //例外
		if (str1.equals(str2)) return true; //完全一致
		if (Math.abs(str1.length()-str2.length()) > err) return false; //長さが違いすぎ
		int noise = noiseCount(str1,str2, err, 0);
		//System.out.println("str1=[" + str1 + "] str2=[" + str2 + "] noise=" + noise);
		return (noise <= err);
	}
	
	/**
	 * 差異（ノイズ）の数を数える
	 * @param str1
	 * @param str2
	 * @param err
	 * @param noise
	 * @return
	 */
	private static int noiseCount(String str1, String str2, int err, int noise) {
		int maxlen = Math.max(str1.length(), str2.length());
		// 一致部分を削除する
		int heads = equalHeads(str1, str2);
		if (maxlen==heads) return noise; //完全一致なら終了
		str1 = str1.substring(heads);
		str2 = str2.substring(heads);
		// 片方が余っていたらエラー数に加算
		if (str1.length() == 0) return noise+str2.length();
		if (str2.length() == 0) return noise+str1.length();
		//System.out.println("str1=[" + str1 + "] str2=[" + str2 + "]");
		int minErr = maxlen;
		int retErr = 0;
		for (int i = -Math.min(err, str1.length()); i <= Math.min(err, str2.length()); i++) {
			if (i==0) continue;
			int slide=Math.abs(i);
			String tempstr;
			if (i<0) {
				if ((noise+slide) > err) continue;
				tempstr=str1.substring(slide);
				retErr = noiseCount(tempstr, str2, err, slide);
			} else {
				if ((noise+slide) > err) continue;
				tempstr=str2.substring(slide);
				retErr = noiseCount(str1, tempstr, err, slide);
			}
			if (retErr<minErr) {
				minErr = retErr;
			}
			//if (noise==0) System.out.print(retErr+",");
		}
		return noise+minErr;
	}
	
	private static int equalHeads(String str1, String str2) {
		int minlen = (str1.length() < str2.length()) ? str1.length() : str2.length();
		for (int i = 0; i < minlen; i++) {
			if (str1.charAt(i) != str2.charAt(i)) return i+1;
		}
		return minlen;
	}
	
	/**
	 * 配列を文字列に変換する
	 * @param args
	 * @return
	 */
	public static String toString(String [] args) {
		StringBuffer buf = new StringBuffer();
		buf.append("[");
		for (int i = 0; i < args.length; i++) {
			buf.append(args[i]);
			if (args.length == i+1) {
				buf.append("]");
			} else {
				buf.append(", ");
			}
		}
		return new String(buf);
	}
	
	/**
	 * 配列と配列の比較
	 * @param arg1
	 * @param arg2
	 * @return
	 */
	public static boolean equals(String[] arg1, String[] arg2) {
		if (arg1 == null) {
			if (arg2 == null) return true;
			else return false;
		} else if (arg2 == null) {
			return false;
		}
		if (arg1.length != arg2.length) return false;
		for (int i = 0; i < arg1.length; i++) {
			if (arg1[i] == null && arg2[i] != null) return false;
			if (arg1[i] != null && arg2[i] == null) return false;
			if (!arg1[i].equals(arg2[i])) return false;
		}
		return true;
	}
	
	/**
	 * URLエンコード
	 * @param str
	 * @deprecated {@link StringUtil#encodeURL(String str)}
	 * @return
	 */
	public static String urlEncode(String str) {
		return encodeURL(str, "utf-8");
	}
	
	/**
	 * URLエンコード
	 * @param str
	 * @param encoding
	 * @deprecated {@link StringUtil#encodeURL(String str, String encoding)}
	 * @return
	 */
	public static String urlEncode(String str, String encoding) {
		return encodeURL(str, encoding);
	}
	
	/**
	 * URLデコード
	 * @param str
	 * @deprecated {@link StringUtil#decodeURL(String str)}
	 * @return
	 */
	public static String urlDecode(String str) {
		return decodeURL(str, "utf-8");
	}
	
	/**
	 * URLデコード
	 * @param str
	 * @param encoding
	 * @deprecated {@link StringUtil#decodeURL(String str, String encoding)}
	 * @return
	 */
	public static String urlDecode(String str, String encoding) {
		return decodeURL(str, encoding);
	}
	
	/**
	 * URLエンコード
	 * @param str
	 * @return
	 * @since 1.0.7
	 */
	public static String encodeURL(String str) {
		return encodeURL(str, "utf-8");
	}
	
	/**
	 * URLエンコード
	 * @param str
	 * @param encoding
	 * @return
	 * @since 1.0.7
	 */
	public static String encodeURL(String str, String encoding) {
		try {
			return URLEncoder.encode(str, encoding);
		} catch (UnsupportedEncodingException e) {
			return null;
		}
	}
	
	/**
	 * URLデコード
	 * @param str
	 * @return
	 * @since 1.0.7
	 */
	public static String decodeURL(String str) {
		return decodeURL(str, "utf-8");
	}
	
	/**
	 * URLデコード
	 * @param str
	 * @param encoding
	 * @return
	 * @since 1.0.7
	 */
	public static String decodeURL(String str, String encoding) {
		try {
			if (encoding == null) encoding = "utf-8";
			return URLDecoder.decode(str, encoding);
		} catch (UnsupportedEncodingException e) {
			return null;
		}
	}
	
	/**
	 * HTMLエスケープ
	 * @param str
	 * @return
	 */
	public static String escapeHtml(String str) {
		str = replaceAll(str, "&",  "&amp;");
		str = replaceAll(str, "<",  "&lt;");
		str = replaceAll(str, ">",  "&gt;");
		str = replaceAll(str, "\"", "&quot;");
		str = replaceAll(str, "'", "&#39;");
		return str;
	}

	/**
	 * HTMLアンエスケープ
	 * @param str
	 * @return
	 */
	public static String unescapeHtml(String str) {
		str = replaceAll(str, "&lt;",   "<");
		str = replaceAll(str, "&gt;",   ">");
		str = replaceAll(str, "&quot;", "\"");
		str = replaceAll(str, "&#39;",  "'");
		str = replaceAll(str, "&amp;",  "&");
		return str;
	}

	/**
	 * 部分マッチ
	 * @param str
	 * @param pattern
	 * @return
	 */
	public static boolean match(String str, Pattern pattern) {
		Matcher match = pattern.matcher(str);
		return match.find();
	}
	
	/**
	 * 部分マッチ
	 * @param str
	 * @param re
	 * @return
	 */
	public static boolean match(String str, String re) {
		Pattern pattern = Pattern.compile(re);
		Matcher match = pattern.matcher(str);
		return match.find();
	}
}
