/*
 * Copyright (c) 2005- Shinji Kashihara.
 * All rights reserved. This program are made available under
 * the terms of the Eclipse Public License v1.0 which accompanies
 * this distribution, and is available at epl-v10.html.
 */
package jp.sourceforge.mergedoc.pleiades.resource;


/**
 * ニーモニック・ユーティリティーです。
 * <p>
 * @author cypher256
 */
public class Mnemonics {

	/** ニーモニック文字の正規表現ブロック (数字はサポートできない {@link #hasEn(String)} 参照) */
	public static final String MNEMONIC_CHARS = "[\\w\\.@]";

	/**
	 * インスタンス化できません。
	 */
	private Mnemonics() {
	}

	/**
	 * 指定した値に含まれる (&A) のような日本語ニーモニックを除去します。
	 * ニーモニック対象文字と中括弧も含めて除去されます。
	 * <p>
	 * @param ja 値
	 * @return ニーモニック除去後の値
	 */
	public static String removeJa(String ja) {

		if (hasJa(ja)) {

			// 日本語向けニーモニックの除去
			ja = ja.replaceFirst("\\(\\&" + Mnemonics.MNEMONIC_CHARS + "\\)", "");

			// 日本語向けニーモニックのバグを除去 (&)
			ja = ja.replaceFirst("\\(\\&\\)", "");
		}
		return ja;
	}

	/**
	 * 英語リソース文字列からニーモニック制御文字 & を除去します。
	 * ニーモニック対象文字自体は除去されません。
	 * <p>
	 * @param en 英語リソース文字列
	 * @return ニーモニック制御文字除去後の文字列
	 */
	public static String removeEn(String en) {

		if (hasEn(en)) {
			return en.replaceFirst("\\&(" + Mnemonics.MNEMONIC_CHARS + ")", "$1");
		}
		return en;
	}

	/**
	 * (&A) のような日本用のニーモニックが既に含まれているか判定します。
	 * <p>
	 * @param value リソース文字列
	 * @return 含まれている場合は true
	 */
	public static boolean hasJa(String value) {
		return
			value.contains("(&") && // 高速化のため先に contains で絞り込み
			value.matches("(?s)^.*?\\(\\&" + Mnemonics.MNEMONIC_CHARS + "\\).*$");
	}

	/**
	 * 英語ニーモニックが含まれているか判定します。
	 * &nbsp; などがある場合、ニーモニックが含まれないことを前提とした判定。
	 * これ以外の & は日本語文字列に & が含まれるかを辞書で判断。
	 * <p>
	 * @param value 値
	 * @return 含まれている場合は true
	 */
	public static boolean hasEn(String value) {

		// 高速化のため正規表現を使用せずに絞り込み
		boolean mayMnemo = false;
		char[] chars = value.toCharArray();
		for (int i = 1; i < chars.length; i++) {

			char c = chars[i - 1];
			char next = chars[i];
			if (c == '&') {

				// & の次が空白、&、数字でない場合 (本来は数字のニーモニック可能だが Pleiades ではサポートできない)
				// ex: A resource named "&1" already exists
				// -> ニーモニックかも = ON
				if (next != ' ' && next != '&' && !Character.isDigit(next)) {
					mayMnemo = true;
				}
			}
		}
		if (!mayMnemo) {
			// ニーモニックなし
			return false;
		}

		// &x. で始まる場合は除外 (&A. サムネイル)
		if (chars.length >= 3 && chars[0] == '&' && chars[2] == '.') {
			return false;
		}

		// HTML 文字参照や URL パラメータ判定
		return
			!value.contains("K&R") && // 予約語
			!containsHtmlReference(value) &&
			!containsUrlParameters(value);
	}

	/**
	 * HTML 実体参照文字が含まれているか判定します。
	 * <p>
	 * @param value 値
	 * @return 含まれている場合は true
	 */
	public static boolean containsHtmlReference(String value) {

		if (!value.contains(";")) {
			return false;
		}
		// &nbsp; (HTML) や &shortcut:$aa.bb; (IDEA 参照) など
		return value.matches("(?si).*&[a-z:$\\.]+;.*");
	}

	/**
	 * & を含むパラメーター付きの URL か判定します。
	 * <p>
	 * @param value 値
	 * @return & を含むパラメーター付きの URL の場合は true
	 */
	public static boolean containsUrlParameters(String value) {

		return
			!value.contains("? ") && // 除外 例）&Select (? = any character
			!value.contains("?=") && // 除外 例）(?=any character
			value.contains("?") &&
			value.contains("=") &&
			value.contains("&");
	}

	/**
	 * ニーモニックを英語用から日本用に変換します。<br>
	 * 例）&x → (&X)
	 * <p>
	 * @param enWithMnemonic	英語リソース文字列  （ニーモニック有り）
	 * @param en				英語リソース文字列  （ニーモニック無し）
	 * @param ja				日本語リソース文字列（ニーモニック無し）
	 * @return					日本語リソース文字列（ニーモニック有り）
	 */
	public static String convertEnToJa(String enWithMnemonic, String en, String ja) {

		if (enWithMnemonic.length() == en.length()) {
			return ja;
		}

		// 日本語リソースにニーモニック "&" が含まれる場合は英語側もニーモニックではないと判断。
		// "&&" はニーモニックではなく、"&" そのものを示すため除外。
		// そのまま処理してしまうと "K&R" が "K&R(&R)" のようになる。
		if (ja.contains("&") && !ja.contains("&&")) {
			return ja;
		}

		String mnemonicChar = enWithMnemonic.replaceFirst(
				"(?s)^.*?\\&(" + Mnemonics.MNEMONIC_CHARS + ").*$",
				"$1");

		if (mnemonicChar.length() != 1) {
			StringBuilder sb = new StringBuilder();
			sb.append("Mnemonic invalid length:" + mnemonicChar.length());
			sb.append(", enValue:" + enWithMnemonic);
			sb.append(", enValueNonMnemonic:" + en);
			sb.append(", mnemonicChar:" + mnemonicChar);
			throw new IllegalArgumentException(sb.toString());
		}
		String mnemonicJa = "(&" + mnemonicChar.toUpperCase() + ")";
		String jaWithMnemonic = null;

		// ニーモニックではない @ : set&Up/@Before → setUp/@Before(&U)
		if (ja.contains("/@")) {
			jaWithMnemonic = ja + mnemonicJa;
		}
		// 例）
		// hoge @Ctrl+X		-> hoge (&H)@Ctrl+X
		// '@Override'		-> '@Override'(&O)
		else {
			jaWithMnemonic = ja.replaceFirst("(?s)^('.+?|.+?)(" +
				"(\\s*\\.{3,4}|)@\\p{ASCII}+|" +	// 例）末尾が @Ctrl+Shift+X
				"\\s*'[^']+'\\s*|" +				// 例）末尾が 'aaa'
				"\\.{3,4}(</a>|)\\s*|" +			// 例）末尾が ... or ....
				"\\s*|" +							// 例）末尾が 空白
				":\\s*|" +							// 例）末尾が :空白
				":\\s+\\{[0-9]\\}\\s*|" +			// 例）末尾が :{数字}
				"(:|)\\s+\\(.+\\)\\s*|" +			// 例）末尾が :(文字列)
				"\\s+(-*|)>+\\s*" +					// 例）末尾が > or >> or -->
				")$", "$1" + mnemonicJa + "$2");
		}

		return jaWithMnemonic;
	}
}
