/*
 * 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.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

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

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

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

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

		/** 英語の大文字小文字を区別する最大長 (これを超える場合は小文字として扱う) */
		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 Set<String> transHelpExcludeSet = Collections.synchronizedSet(new HashSet<String>());

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

	/** ヘルプ辞書が読み込み済みの場合は true */
	private volatile boolean isLoadedHelp;

	/** 訳が見つからない場合のログ */
	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));
		transProp.load(validateExists(TRANS_MULTIBYTES_KEY_PROP));

		// ユーザー追加辞書のロード (上書き)
		File addDir = Files.getFile(TRANS_ADDITIONS_DIR);
		if (addDir.exists()) {

			// ユーザー追加辞書ロード (トリム機能を付加してロード)
			// ・TranslationRule は強い最適化によるユーザーの混乱を避けるため使用しない
			PropertySet addProp = new PropertySet(addDir){
				@Override
				public String put(String en, String ja) {
					en = TranslationString.trim(Mnemonics.removeEnMnemonic(en));
					ja = TranslationString.trim(Mnemonics.removeJaMnemonic(ja));
					return super.put(en, ja);
				}
			};
			transProp.putAll(addProp);
		}

		// 変換プロパティーを適用
		File convFile = Files.getFile(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;
	}

	/**
	 * ヘルプ翻訳プロパティー・ファイルをロードします。
	 * このメソッドを呼び出さない限り、ヘルプ翻訳プロパティーはロードされません。
	 * なお、すでにロード済みの場合は、何も行いません。
	 * <p>
	 * @return ロード済みの場合は false
	 */
	protected boolean loadHelp() {

		if (isLoadedHelp) {
			return false;
		}
		synchronized (AbstractTranslationDictionary.class) {

			if (isLoadedHelp) {
				return false;
			}
			for (Property p : new PropertySet(validateExists(TRANS_HELP_UNCHECKED_PROP))) {
				if (!transProp.containsKey(p.key)) {
					transProp.put(p);
				}
			}
			transHelpExcludeSet.addAll(
				new PropertySet(validateExists(TRANS_HELP_EXCLUDE_PROP)).keySet());
			isLoadedHelp = true;
		}
		return true;
	}

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

		File propFile = Files.getFile(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 英語リソース文字列（ニーモニック無し）
	 * @return 日本語リソース文字列（ニーモニック無し）。翻訳できない場合は en をそのまま返す。
	 */
	protected String getValue(String en) {

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

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

		// 正規表現辞書から取得
		TranslationString enTs = new TranslationString(en);
		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();
		if (enPartTsList == null) {
			notFoundLog.println(enTs.trim());
			transProp.put(en, en);
			return en;
		}

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

			String enPartTrim = enPartTs.trim();

			// トリムして取得
			String jaPart = getValueToTrim(enPartTs);
			if (jaPart == null) {
				containsNoTransPart = true;
				notFoundLog.println(enPartTrim);

				// 訳が見つからない場合は復元しない (句点は原文のまま)
				jaPart = enPartTs.toString();
			}
			sb.append(jaPart);
		}

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

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

		// トリム後、空文字
		String enTrim = enTs.trim();
		if (enTrim.length() == 0) {
			return enTs.toString();
		}

		// トリムした値で、日本語訳を取得
		String ja = transProp.get(enTrim);
		if (ja != null) {
			return enTs.revert(ja);
		}

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

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

		// 強制トリムした値で、日本語訳を取得
		String enTrimForce = enTs.trimForce();
		ja = transProp.get(enTrimForce);
		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;
	}

	/**
	 * ヘルプ日本語訳を取得します。
	 * <p>
	 * @param en 英語リソース文字列
	 * @return 日本語リソース文字列。翻訳できない場合は en をそのまま返す。
	 */
	protected String getValueForHelp(String en) {

		if (transHelpExcludeSet.contains(en)) {
			return en;
		}
		loadHelp();
		String ja = getValue(en);
		return ja;
	}
}
