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

import static jp.sourceforge.mergedoc.pleiades.resource.FileNames.*;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.List;

import jp.sourceforge.mergedoc.pleiades.Pleiades;
import jp.sourceforge.mergedoc.pleiades.log.Logger;
import jp.sourceforge.mergedoc.pleiades.runtime.resource.RegexDictionary;
import jp.sourceforge.mergedoc.pleiades.runtime.util.ConcurrentPropertySet;

/**
 * 抽象翻訳辞書クラスです。
 * <p>
 * @author cypher256
 */
abstract public class AbstractTranslationDictionary {

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

	/** 正規表現キープレフィックス */
	private static final String REGEX_KEY_PREFIX = "%REGEX%";

	/** 翻訳プロパティー */
	@SuppressWarnings("serial")
	private final PropertySet transProp = new ConcurrentPropertySet(200000) {

		/** 英語の大文字小文字を区別する最大長 (これを超える場合は小文字として扱う) */
		private static final int VALID_CASE_MAX_LENGTH = 10;

		@Override
		public String get(Object keyObj) {
			String key = (String) keyObj;
			if (key.length() > VALID_CASE_MAX_LENGTH) {
				key = key.toLowerCase();
			}
			return super.get(key);
		}

		@Override
		public String put(String key, String value) {
			if (key.length() > VALID_CASE_MAX_LENGTH) {
				key = key.toLowerCase();
			}
			return super.put(key, value);
		}
	};

	/** 翻訳上書きプロパティー */
	protected final PropertySet transOverrideProp = new PropertySet();

	/** 最初に正規表現プロパティから解決する場合は true */
	protected volatile boolean isResolveFirstRegex = true;

	/** 翻訳辞書が読み込み済みの場合は true */
	protected volatile boolean isLoadedDefault;

	/** 訳が見つからない場合のログ */
	private final TranslationNotFoundProperties notFoundLog = TranslationNotFoundProperties.getInstance();

	/**
	 * デフォルト辞書を構築します。
	 */
	protected AbstractTranslationDictionary() {
		load();
	}

	/**
	 * 翻訳プロパティー・ファイルをロードします。
	 * すでにロード済みの場合は、何も行いません。
	 * <p>
	 * @return ロード済みの場合は false
	 */
	@SuppressWarnings("serial")
	protected synchronized boolean load() {

		if (isLoadedDefault) {
			return false;
		}

		// 翻訳プロパティーのロード
		transProp.load(validateExists(TRANS_PROP));

		// 優先プロパティーのロード (上書き用)
		transOverrideProp.load(validateExists(TRANS_FIRST_PROP));

		// アプリケーション別プロパティーのロード (上書き用)
		String overrideProp = Pleiades.getPleiadesOption().appOverrideProp;
		if (overrideProp != null) {
			transOverrideProp.load(overrideProp);
		}

		// ユーザー追加辞書が存在すればロード (上書き用)
		File additionsDir = Files.conf(TRANS_ADDITIONS_DIR);
		if (additionsDir.exists()) {

			new PropertySet(additionsDir){
				@Override
				public String put(String en, String ja) {

					// プレフィックス付きの場合は正規表現辞書へ追加
					if (en.startsWith(REGEX_KEY_PREFIX)) {
						String enTrim = en.replaceFirst("^" + REGEX_KEY_PREFIX, "").trim();
						String jaTrim = ja.trim();
						RegexDictionary.getInstance().put(enTrim, jaTrim);
						return null;
					}
					// 通常辞書追加 (TranslationRule の強い最適化はユーザーの混乱を避けるため使用しない)
					else {
						String enTrim = TranslationString.trim(Mnemonics.removeEn(en));
						String jaTrim = TranslationString.trim(Mnemonics.removeJa(ja));
						return transOverrideProp.put(enTrim, jaTrim);
					}
				}
			};
		}

		// 上書き用を上書き
		transProp.putAll(transOverrideProp);

		// 変換プロパティーが存在すれば適用 (時間がかかる処理)
		File convFile = Files.conf(TRANS_CONVERTER_PROP);
		if (convFile.exists()) {

			PropertySet convMap = new PropertySet(convFile);

			for (Property p : transProp) {

				String resultValue = p.value;

				// 置換（例：本当に → ほんまに）
				for (Property conv : convMap) {
					resultValue = resultValue.replaceAll(conv.key, conv.value);
				}
				if (!p.value.equals(resultValue)) {
					transProp.put(p.key, resultValue);
				}
			}
		}

		isLoadedDefault = true;
		return true;
	}

	/**
	 * ファイルの存在チェックを行い、存在すれば File オブジェクトを返します。
	 * 存在しない場合は Pleiades.abort で強制終了します。
	 * <p>
	 * @param fileName ファイル名
	 * @return ファイル
	 */
	public static File validateExists(String fileName) {

		File propFile = Files.conf(fileName);
		if (!propFile.exists()) {
			String msg = propFile.getName() + " が見つかりません。";
			log.fatal(msg);
			Exception e = new FileNotFoundException(propFile.getPath());
			throw new IllegalStateException(msg, e);
		}
		return propFile;
	}

	/**
	 * 日本語訳を取得します。
	 * <p>
	 * @param en 英語リソース文字列（ニーモニック無し）
	 * @param separator 英語文字列を区切る場合の正規表現 (デフォルト null)
	 * @return 日本語リソース文字列（ニーモニック無し）。翻訳できない場合は en をそのまま返す。
	 */
	protected String getValue(String en, String separator) {

		// もし辞書に不正があり、空のキーが存在した場合に、空文字を翻訳して
		// しまうとメニュー・バーに不具合が発生するため回避
		if (en.isEmpty()) {
			return en;
		}

		// 翻訳プロパティーから取得
		String ja = transProp.get(en);
		if (ja != null) {
			return ja;
		}

		// 正規表現辞書から取得
		TranslationString enTs = new TranslationString(en);
		if (isResolveFirstRegex) {
			ja = getValueByRegex(enTs);
			if (ja != null) {
				transProp.put(en, ja);
				return ja;
			}
		}

		// トリムした値で翻訳プロパティーから取得
		ja = getValueToTrim(enTs);
		if (ja != null) {
			transProp.put(en, ja);
			return ja;
		}

		// 句点分割
		List<TranslationString> enPartTsList = enTs.split(separator);
		if (enPartTsList == null) {
			notFoundLog.put(enTs);
			transProp.put(en, en);
			return en;
		}

		boolean containsNoTransPart = false;
		StringBuilder sb = new StringBuilder();
		for (TranslationString enPartTs : enPartTsList) {

			// トリムして取得
			String jaPart = getValueToTrim(enPartTs);

			// 正規表現プロパティから取得
			// →	注意：getValueToTrim で trimForce されているため、getValueByRegex 内の trim は
			//		trimForce 相当になっている。(TranslationString#trim の仕様)
			if (jaPart == null) {
				jaPart = getValueByRegex(enPartTs);
			}

			// 分割して括弧トリム後の再帰分割 例 xxx. (Note: yyy.)
			if (jaPart == null && enPartTs.trim().contains(": ")) {
				jaPart = getValue(enPartTs.toString(), null); // 再帰
				if (jaPart.equals(enPartTs.toString())) {
					containsNoTransPart = true;
				}
			}

			// 句点分割でも解決できない場合
			if (jaPart == null) {

				// 訳が見つからない場合は復元しない (句点を原文のままにするため)
				jaPart = enPartTs.toString();
				containsNoTransPart = true;
				notFoundLog.put(enPartTs);
			}
			sb.append(jaPart);
		}

		// 前後スペースを復元
		ja = enTs.revert(sb.toString());
		if (containsNoTransPart) {
			notFoundLog.put(enTs);
		}
		transProp.put(en, ja);
		return ja;
	}

	/**
	 * trim して日本語訳を取得します。
	 * <p>
	 * @param enTs 英語翻訳文字列
	 * @return 日本語リソース文字列（ニーモニック無し）。翻訳できない場合は null。
	 */
	protected String getValueToTrim(TranslationString enTs) {

		// トリムした値で、日本語訳を取得
		String enTrim = enTs.trim();
		if (enTrim.isEmpty()) {
			return enTs.toString();
		}
		String ja = getValueRemoveS(enTs, enTrim);
		if (ja != null) {
			return ja;
		}

		// アンカーテキストの ' 囲みを除去し、日本語訳を取得
		if (enTrim.contains(">'")) {
			enTrim = enTrim.replaceAll("(?s)(<a[^>]*>)'+", "$1");
			enTrim = enTrim.replaceAll("(?s)'+(</a>)", "$1");

			ja = getValueRemoveS(enTs, enTrim);
			if (ja != null) {
				return ja;
			}
		}

		// 強制トリムした値で、日本語訳を取得
		enTrim = enTs.trimForce();
		ja = getValueRemoveS(enTs, enTrim);
		if (ja != null) {
			return ja;
		}
		return null;
	}

	/**
	 * 日本語訳を取得します。
	 * <p>
	 * @param enTs 英語翻訳文字列
	 * @param enTrim 英語トリム後文字列
	 * @return 日本語リソース文字列（ニーモニック無し）。翻訳できない場合は null。
	 */
	protected String getValueRemoveS(TranslationString enTs, String enTrim) {

		String ja = transProp.get(enTrim);
		if (ja != null) {
			return enTs.revert(ja);
		}

		// 複数形を示す可能性がある (s) を除去して、日本語訳を取得
		enTrim = TranslationString.removeSorNull(enTrim);
		if (enTrim != null) {
			ja = transProp.get(enTrim);
			if (ja != null) {
				return enTs.revert(ja);
			}
		}
		return null;
	}

	/**
	 * 正規表現辞書から日本語訳を取得します。
	 * <p>
	 * @param enTs 英語翻訳文字列
	 * @return 日本語リソース文字列（ニーモニック無し）。翻訳できない場合は null。
	 */
	protected String getValueByRegex(TranslationString enTs) {

		String ja = RegexDictionary.getInstance().lookup(enTs.trim());
		if (ja != null) {
			return enTs.revert(ja);
		}
		return null;
	}
}
