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

import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;

import jp.sourceforge.mergedoc.pleiades.log.Logger;
import jp.sourceforge.mergedoc.pleiades.resource.Mnemonics;
import jp.sourceforge.mergedoc.pleiades.runtime.advice.RuntimeJointPoint;

/**
 * 翻訳ユーティリティーです。
 * <p>
 * このクラスのメソッドは、バイトコード変換されたクラスから
 * 呼び出しやすいように、static メソッドで構成されています。
 * <p>
 * @author cypher256
 */
public class Translations {

	/** ロガー */
	private static final Logger log = Logger.getLogger(Translations.class);

	/** 翻訳辞書キャッシュ */
	private static final IdentityTranslationCache cache = new IdentityTranslationCache();

	/** コンボなどの翻訳値を保持する退避マップ */
	private static final Map<String, String> backupMap = new ConcurrentHashMap<String, String>();

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

	/**
	 * 指定された英語リソース文字列を翻訳します。
	 * <p>
	 * @param en 英語リソース文字列
	 * @param jointPoint ジョイント・ポイント
	 * @return 翻訳後文字列。翻訳できない場合は引数と同じ。
	 */
	public static String translate(String en, RuntimeJointPoint jointPoint) {

		if (en == null) {
			return null;
		}
		return cache.translateMnemonic(en, jointPoint);
	}

	/**
	 * 指定された英語リソース文字列の配列を翻訳します。
	 * <p>
	 * @param ens 英語リソース文字列の配列
	 * @param jointPoint ジョイント・ポイント
	 * @return 翻訳後文字列。翻訳できない場合は引数と同じ。
	 */
	public static String[] translate(String[] ens, RuntimeJointPoint jointPoint) {

		if (ens == null) {
			return null;
		}
		for (int i = 0; i < ens.length; i++) {
			ens[i] = translate(ens[i], jointPoint);
		}
		return ens;
	}

	/**
	 * 指定された英語リソース・オブジェクトを翻訳します。
	 * <p>
	 * @param en 英語リソース・オブジェクト
	 * @param jointPoint ジョイント・ポイント
	 * @return 翻訳後文字列。翻訳できない場合は引数と同じ。
	 */
	public static Object translate(Object en, RuntimeJointPoint jointPoint) {

		if (en == null) {
			return null;
		}
		if (en instanceof String) {
			return translate((String) en, jointPoint);
		}
		if (en instanceof String[]) {
			return translate((String[]) en, jointPoint);
		}
		if (en instanceof Object[]) {
			return translate((Object[]) en, jointPoint);
		}
		return en;
	}

	/**
	 * 指定された英語リソース・オブジェクトを翻訳します。
	 * <p>
	 * @param enObjs 英語リソース・オブジェクト
	 * @param jointPoint ジョイント・ポイント
	 * @return 翻訳後文字列。翻訳できない場合は引数と同じ。
	 */
	public static Object[] translate(Object[] enObjs, RuntimeJointPoint jointPoint) {

		if (enObjs == null) {
			return null;
		}
		if (enObjs instanceof String[]) {
			return translate((String[]) enObjs, jointPoint);
		}
		for (int i = 0; i < enObjs.length; i++) {
			enObjs[i] = translate(enObjs[i], jointPoint);
		}
		return enObjs;
	}

	/**
	 * 指定された英語リソース文字列をメニュー用に翻訳します。
	 * <p>
	 * @param en 英語リソース文字列
	 * @param jointPoint ジョイント・ポイント
	 * @return 翻訳後文字列。翻訳できない場合は引数と同じ。
	 */
	public static String translateMenu(String en, RuntimeJointPoint jointPoint) {

		if (en == null) {
			return null;
		}
		String[] splitsAccelerator = en.split("\t");
		String enMenuText = splitsAccelerator[0];

		// 現在このメソッド呼び出しは限定されているため、先頭 & 除去は不要
		/*
		if (!enMenuText.matches("\\p{ASCII}+")) {
			// 日本語メニューの新規項目先頭に & が付加されてしまうのを回避
			enMenuText = enMenuText.replaceFirst("^&", "");
		}
		*/
		if (splitsAccelerator.length == 1) {
			return cache.translateMnemonic(enMenuText, jointPoint);
		} else {
			return cache.translateMnemonic(enMenuText, jointPoint) + "\t" + splitsAccelerator[1];
		}
	}

	/**
	 * 指定された英語リソース文字列を翻訳します。
	 * ニーモニックは処理されません。
	 * <p>
	 * @param en 英語リソース文字列
	 * @param jointPoint ジョイント・ポイント
	 * @return 翻訳後文字列。翻訳できない場合は引数と同じ。
	 */
	public static String translateIgnoreMnemonic(Object en, RuntimeJointPoint jointPoint) {

		if (en == null) {
			return null;
		}
		if (!(en instanceof String)) {
			log.debug("引数の型が String ではありません。en=%s type=%s", en, en.getClass().getName());
			return String.valueOf(en);
		}
		return cache.translateNoMnemonic((String) en, jointPoint);
	}

	/**
	 * 指定された英語リソース文字列を翻訳します。
	 * ニーモニックは処理されません。
	 * <p>
	 * @param en 英語リソース文字列
	 * @param jointPoint ジョイント・ポイント
	 * @param separator 英語文字列を区切る場合の正規表現 (si 適用あり、デフォルト null)
	 * @return 翻訳後文字列。翻訳できない場合は引数と同じ。
	 */
	public static String translateIgnoreMnemonic(Object en, RuntimeJointPoint jointPoint, String separator) {

		if (jointPoint != null) {
			jointPoint.separator = separator;
		}
		return translateIgnoreMnemonic(en, jointPoint);
	}

	/**
	 * 指定された区切り文字の前の部分を翻訳します。
	 * ニーモニックは処理されません。
	 * <p>
	 * @param en 英語リソース文字列
	 * @param jointPoint ジョイント・ポイント
	 * @param separator 区切り文字 (正規表現不可)
	 * @return 翻訳後文字列。翻訳できない場合は引数と同じ。
	 */
	public static String translateBefore(Object en, RuntimeJointPoint jointPoint, String separator) {

		if (en != null && en instanceof String) {
			String[] s = ((String) en).split(Pattern.quote(separator), 2);
			if (s.length >= 2) {
				return cache.translateNoMnemonic(s[0], jointPoint) + separator + s[1];
			}
		}
		return translateIgnoreMnemonic(en, jointPoint);
	}

	/**
	 * 指定された英語リソース文字列をバックアップし、翻訳します。
	 * <p>
	 * @param en 英語リソース文字列
	 * @param jointPoint ジョイント・ポイント
	 * @return 翻訳後文字列。翻訳できない場合は引数と同じ。
	 */
	public static String translateBackup(String en, RuntimeJointPoint jointPoint) {

		if (en == null) {
			return null;
		}
		String ja = cache.translateMnemonic(en, jointPoint);
		backupMap.put(ja, en);
		return ja;
	}

	/**
	 * 指定された英語リソース文字列をバックアップし、翻訳します。
	 * ニーモニックは処理されません。
	 * <p>
	 * @param en 英語リソース文字列
	 * @param jointPoint ジョイント・ポイント
	 * @return 翻訳後文字列。翻訳できない場合は引数と同じ。
	 */
	public static String translateBackupIgnoreMnemonic(String en, RuntimeJointPoint jointPoint) {

		if (en == null) {
			return null;
		}
		String ja = cache.translateNoMnemonic(en, jointPoint);
		backupMap.put(ja, en);
		return ja;
	}

	/**
	 * 指定された英語リソース文字列配列をバックアップし、翻訳します。
	 * ニーモニックは処理されません。
	 * <p>
	 * @param ens 英語リソース文字列
	 * @param jointPoint ジョイント・ポイント
	 * @return 翻訳後文字列。翻訳できない場合は引数と同じ。
	 */
	public static String[] translateBackupIgnoreMnemonic(String[] ens, RuntimeJointPoint jointPoint) {

		if (ens == null) {
			return null;
		}
		for (int i = 0; i < ens.length; i++) {
			ens[i] = translateBackupIgnoreMnemonic(ens[i], jointPoint);
		}
		return ens;
	}

	/**
	 * translateBackup で翻訳された日本語リソース文字列を英語に戻します。
	 * <p>
	 * @param ja 日本語リソース文字列
	 * @param jointPoint ジョイント・ポイント
	 * @return 英語文字列。戻せない場合は引数と同じ。
	 */
	public static String restoreBackup(String ja, RuntimeJointPoint jointPoint) {

		if (ja == null) {
			return null;
		}
		String en = backupMap.get(ja);
		if (en == null) {
			return ja;
		}
		return en;
	}

	/**
	 * 指定された英語リソース文字列を翻訳します。
	 * 空白が 1 つ以上含まれる場合のみ翻訳されます。
	 * <p>
	 * @param en 英語リソース文字列
	 * @param jointPoint ジョイント・ポイント
	 * @return 翻訳後文字列。翻訳できない場合は引数と同じ。
	 */
	public static String translateSentence(String en, RuntimeJointPoint jointPoint) {

		if (en != null && en.trim().contains(" ")) {
			return cache.translateMnemonic(en, jointPoint);
		}
		return en;
	}

	/**
	 * 指定された日本用ニーモニックを含むリソース文字列を翻訳します。
	 * 引数に含まれる日本用ニーモニックは翻訳前に除去されます。
	 * <p>
	 * @param en 日本用ニーモニックが含まれたリソース文字列
	 * @param jointPoint ジョイント・ポイント
	 * @return 翻訳後文字列。翻訳できない場合は引数と同じ。
	 */
	public static String translateWithoutJaMnemonic(String en, RuntimeJointPoint jointPoint) {

		if (en == null) {
			return null;
		}
		en = Mnemonics.removeJa(en);
		return cache.translateNoMnemonic(en, jointPoint);
	}

	/**
	 * ニーモニックがアンダースコアのリソース文字列を翻訳します。
	 * <p>
	 * @param en 日本用ニーモニックが含まれたリソース文字列
	 * @param jointPoint ジョイント・ポイント
	 * @return 翻訳後文字列。翻訳できない場合は引数と同じ。
	 */
	public static String translateUnderscoreMnemonic(String en, RuntimeJointPoint jointPoint) {

		if (en == null) {
			return null;
		}
		return cache.translateUnderscoreMnemonic(en, jointPoint);
	}

	/**
	 * キャッシュ破棄を指示します。<br>
	 * このメソッドを呼び出すと、次回は -clean で起動します。
	 */
	public static void invalidateCache() {
		RuntimeTranslationDictionary.getInstance().isInvalidateCache = true;
		log.info("キャッシュ破棄を指示しました。次回は -clean で起動します。");
	}

	/**
	 * 指定した値をデバッグ・ログとして出力します。
	 * <p>
	 * @param obj オブジェクト
	 */
	public static void debug(Object obj) {

		StringBuilder sb = new StringBuilder();
		sb.append("Translations#debug(Object) ");
		if (obj != null) {
			sb.append(obj.getClass().getName());
		}
		sb.append("[");
		sb.append(obj);
		sb.append("]");
		log.info(sb.toString());
	}

	/**
	 * 指定した値をデバッグ・ログとして出力します。
	 * <p>
	 * @param en 英語リソース・オブジェクト
	 * @param jointPoint ジョイント・ポイント
	 */
	public static void debug(Object en, RuntimeJointPoint jointPoint) {

		StringBuilder sb = new StringBuilder();
		sb.append("Translations#debug(Object,JointPoint) ");
		if (jointPoint != null) {
			sb.append("(");
			sb.append(jointPoint.getClassName());
			sb.append("#");
			sb.append(jointPoint.getMethodName());
			sb.append(") ");
		}
		if (en != null) {
			sb.append(en.getClass().getName());
		}
		sb.append("[");
		sb.append(en);
		sb.append("] -> [");
		sb.append(translate(en, jointPoint));
		sb.append("]");
		log.info(sb.toString());
	}

	/**
	 * 指定した値をデバッグ・ログとして出力します。
	 * <p>
	 * @param en 英語リソース・オブジェクト
	 * @param jointPoint ジョイント・ポイント
	 * @param key キー
	 */
	public static void debug(Object en, RuntimeJointPoint jointPoint, String key) {

		StringBuilder sb = new StringBuilder();
		sb.append("Translations#debug(Object,JointPoint,String) ");
		if (jointPoint != null) {
			sb.append("(");
			sb.append(jointPoint.getClassName());
			sb.append("#");
			sb.append(jointPoint.getMethodName());
			sb.append(") ");
		}
		if (en != null) {
			sb.append(en.getClass().getName());
		}
		sb.append("[");
		sb.append(en);
		sb.append("] -> [");
		sb.append(translate(en, jointPoint));
		sb.append("]");

		sb.append(" key=");
		sb.append(key);
		log.info(sb.toString());
	}

	/**
	 * 指定した値をデバッグ・ログとして出力します。
	 * <p>
	 * @param ens 英語リソース・オブジェクトの配列
	 * @param jointPoint ジョイント・ポイント
	 */
	public static void debug(String[] ens, RuntimeJointPoint jointPoint) {

		StringBuilder sb = new StringBuilder();
		sb.append("Translations#debug(String[],JointPoint) ");
		if (jointPoint != null) {
			sb.append("(");
			sb.append(jointPoint.getClassName());
			sb.append("#");
			sb.append(jointPoint.getMethodName());
			sb.append(") ");
		}
		if (ens != null) {
			sb.append(ens.getClass().getName());
			sb.append(Arrays.asList(ens));
		}
		sb.append(" -> ");
		sb.append(Arrays.asList(translate(ens, jointPoint)));
		log.info(sb.toString());
	}

	/**
	 * 現在のスレッドのスタックトレースをログに出力します。
	 * <p>
	 * @param obj 英語リソース・オブジェクト
	 * @param jointPoint ジョイント・ポイント
	 */
	public static void printStackTrace(Object obj, RuntimeJointPoint jointPoint) {

		log.info(new Throwable(),
			"Translator#printStackTrace " +
			(obj == null ? "" : obj.getClass().getName()) +
			"[" + obj +
			"] -> [" +
			translate(obj, jointPoint) + "]"
		);
	}
}
