/*
 * 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.generator.nls;

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

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import jp.sourceforge.mergedoc.pleiades.generator.Generator;
import jp.sourceforge.mergedoc.pleiades.generator.Validator;
import jp.sourceforge.mergedoc.pleiades.resource.Files;
import jp.sourceforge.mergedoc.pleiades.resource.HelpHtmlParser.HtmlFragment;
import jp.sourceforge.mergedoc.pleiades.resource.Mnemonics;
import jp.sourceforge.mergedoc.pleiades.resource.Property;
import jp.sourceforge.mergedoc.pleiades.resource.PropertySet;

import org.apache.commons.lang.StringUtils;

/**
 * 言語パックから、ヘルプに関する日本語訳を抽出し、Pleiades 形式の
 * プロパティー・ファイルに出力するクラスです。
 * <p>
 * @author cypher256
 */
public class HelpExtractor extends AbstractExtractor {

	/** nls ディレクトリーのプラグインから再抽出する場合は true */
	private static boolean isReExtractFromNls = true;

	/** ヘルプ対訳検証プロパティー */
	private static final PropertySet vHelpTransProp = new PropertySet(VALID_HELP_TRANS_PROP);

	/** ヘルプ対訳検証 (逆参照) プロパティー */
	private static final PropertySet vHelpTransReverseProp = new PropertySet(VALID_HELP_TRANS_REVERSE_PROP);

	/**
	 * 抽出を開始するための main メソッドです。
	 * <p>
	 * @param args NLS 取得元プラグイン確認デバッグ・ログ出力用の英語リソース文字列
	 * @throws IOException 入出力例外が発生した場合
	 */
	public static void main(String... args) throws IOException {
		new HelpExtractor().run("help", args);
	}

	@Override
	protected void doExtract() throws IOException {
		if (isReExtractFromNls) {
			super.doExtract();
		}
	}

	/**
	 * 指定されたプラグイン・フォルダーに存在する言語パックをロードし、
	 * プロパティーを生成します。
	 * <p>
	 * @param pluginsFolder プラグイン・フォルダー
	 * @throws IOException 入出力例外が発生した場合
	 */
	@Override
	protected void extractPlugins(File pluginsFolder) throws IOException {

		Map<String, PropertySetUnit> pluginTocMap = new HashMap<String, PropertySetUnit>();
		Map<String, HtmlFragmentListUnit> pluginHtmlMap = new HashMap<String, HtmlFragmentListUnit>();
		File[] pluginFiles = pluginsFolder.listFiles();
		Arrays.sort(pluginFiles); // 昇順

		// 日本語と英語のプロパティーをロード
		for (File pluginFile : pluginFiles) {

			// プラグイン・オブジェクト作成
			HelpPlugin plugin = new HelpPlugin(pluginFile);
			String pluginId = plugin.getId();

			// TOC 収集
			PropertySetUnit propUnit = pluginTocMap.get(pluginId);
			if (propUnit == null) {
				propUnit = new PropertySetUnit();
				pluginTocMap.put(pluginId, propUnit);
			}
			propUnit.getEnPropertySet().putAll(plugin.getEnPropertySet());
			propUnit.getJaPropertySet().putAll(plugin.getJaPropertySet());

			// ヘルプ HTML 収集
			HtmlFragmentListUnit htmlUnit = pluginHtmlMap.get(pluginId);
			if (htmlUnit == null) {
				htmlUnit = new HtmlFragmentListUnit();
				pluginHtmlMap.put(pluginId, htmlUnit);
			}
			htmlUnit.enHtmlMap.putAll(plugin.getHtmlListUnit().enHtmlMap);
			htmlUnit.jaHtmlMap.putAll(plugin.getHtmlListUnit().jaHtmlMap);
			log.debug("en:%3d、ja:%3d、%s", htmlUnit.enHtmlMap.size(), htmlUnit.jaHtmlMap.size(), pluginId);
		}
		String pluginPath = Files.relativePath(inNlsFolder, pluginsFolder);

		// 日本語と英語のプロパティーでキーが一致するものからプロパティー作成
		for (Entry<String, PropertySetUnit> entry : pluginTocMap.entrySet()) {

			String pluginId = entry.getKey();

			// TOC をプロパティーに格納
			PropertySetUnit propUnit = entry.getValue();
			PropertySet enProp = propUnit.getEnPropertySet();
			PropertySet jaProp = propUnit.getJaPropertySet();
			PropertySet outProp = new PropertySet();

			for (Property ja : jaProp) {
				String enValue = enProp.get(ja.key);
				if (StringUtils.isNotEmpty(enValue) && !enValue.equals(ja.value)) {
					if (ja.value.matches("\\p{ASCII}+")) {
						continue;
					}
					outProp.put(enValue, ja.value);
				}
			}

			// ヘルプ HTML をプロパティーに格納
			HtmlFragmentListUnit listUnit = pluginHtmlMap.get(pluginId);
			for (Entry<String, HtmlFragmentList> e : listUnit.enHtmlMap.entrySet()) {

				HtmlFragmentList enList = e.getValue();
				HtmlFragmentList jaList = listUnit.jaHtmlMap.get(e.getKey());

				if (jaList == null) {
					log.debug("日本語リストが null です。英語リストのパス: %s", enList.path);
					continue;
				}
				optimizeListSize(enList, jaList);
				if (enList.size() != jaList.size()) {
					continue;
				}

				for (int i = 0; i < enList.size(); i++) {
					outProp.put(
						enList.get(i).getText(),
						jaList.get(i).getText());
				}
			}

			// プロパティー・ファイルへ出力
			int outPropSize = outProp.size();
			if (outPropSize > 0) {
				count += outPropSize;
				String outFileName = pluginPath.replace('\\', '@') + "@" + pluginId + ".properties";
				File outFile = new File(extractTempFolder, outFileName);
				outProp.store(outFile, "eclipse.org 言語パック抽出プロパティー (プラグイン別)");
			}
		}
	}

	@Override
	protected void afterExtract(PropertySet extractedProp) throws IOException {

		// バリデート
		createBeforeValidator(extractPropPath + "_before_apply_rule_validate.log")
			.validate(extractedProp);

		// 分割と翻訳ルール適用
		PropertySet existsProp = new PropertySet(TRANS_PROP);
		PropertySet prop = new HelpTranslationRule(extractPropPath + "_rule.log", existsProp)
			.apply(extractedProp);

		// ヘルプ・プロパティーを振り分け
		PropertySet helpTextProp = new PropertySet();
		PropertySet helpHtmlProp = new PropertySet();
		Validator uiValidator = new Validator(extractPropPath + "_after_apply_rule_validate.log");

		for (Property p : prop) {
			if (isValidUiProperty(p, uiValidator)) {
				helpTextProp.put(p.key, p.value);
			} else {
				helpHtmlProp.put(p.key, p.value);
			}
		}

		// 保管
		Generator.storeHistory(helpTextProp, TEMP_NLS_HELP_CHECKED_PROP,
			"ヘルプ・テキスト辞書プロパティー (翻訳ルール適用済み、バリデーター検証 OK)\n\n" +
			"  言語パックのヘルプから抽出したもので、translation.properties にマージされます。\n" +
			"  句点解析によりエントリーは可能な限り文単位に分割されています。\n" +
			"  入力元ファイル：" + extractPropPath);

		Generator.storeHistory(helpHtmlProp, TEMP_NLS_HELP_UNCHECKED_PROP,
			"ヘルプ HTML 辞書プロパティー (翻訳ルール適用済み、バリデーター検証 NG)\n\n" +
			"  言語パックのヘルプから抽出したものです。\n" +
			"  句点解析によりエントリーは可能な限り文単位に分割されています。\n" +
			"  入力元ファイル：" + extractPropPath);
	}

	/**
	 * 事前バリデーターを作成します。
	 * @param logFileName ログ・ファイル名。例) "props/validator.log"
	 */
	protected Validator createBeforeValidator(String logFileName) {
		return new Validator(logFileName);
	}

	/**
	 * プロパティーが UI 向けとして正しいか判定します。
	 * @param p プロパティー
	 * @return 正しい場合は true
	 */
	private boolean isValidUiProperty(Property p, Validator uiValidator) {

		String s = p.key + p.value;
		if (s.contains("<") ||
			s.contains(">") ||
			s.contains(" alt=") ||
			s.contains(" ALT =") ||
			Mnemonics.containsHtmlReference(s)) {

			return false;
		}

		if (uiValidator.validate(p).isSuccess()) {
			return true;
		}

		return false;
	}

	/**
	 * 英語リストと日本語リストのサイズが異なる場合、翻訳比較しサイズを調整します。
	 * @param enList 英語リスト
	 * @param jaList 日本語リスト
	 * @return メッセージ
	 */
	public String optimizeListSize(HtmlFragmentList enList, HtmlFragmentList jaList) {

		StringBuilder msg = new StringBuilder();

		if (enList.size() == jaList.size() || enList.size() == 0 || jaList.size() == 0) {
			return msg.toString();
		}
		int enListSizeOld = enList.size();
		int jaListSizeOld = jaList.size();

		try {
			//-----------------------------------------------------------------
			// 日本語リスト・サイズのほうが大きい場合
			//-----------------------------------------------------------------
			if (enList.size() < jaList.size()) {

				for (int i = 0; i < enList.size(); i++) {

					HtmlFragment en = enList.get(i);
					HtmlFragment ja = jaList.get(i);

					for (Property vTrans : vHelpTransProp) {

						String enPart = vTrans.key;
						String jaPart = vTrans.value;

						if (en.getText().matches(enPart)) {

							while (!ja.getText().matches(jaPart)) {
								HtmlFragment jaOld = jaList.remove(i);
								ja = jaList.get(i);
								msg.append("★日本語読み飛ばし " + enPart + "\n" +
									en + "\n────────────────────\n" +
									jaOld + "\n" +
									ja + "\n───────────────────────\n");
							}
						}
					}
				}
				return msg.toString();
			}

			//-----------------------------------------------------------------
			// 英語リスト・サイズのほうが大きい場合
			//-----------------------------------------------------------------
			final String altPattern = "(?s)(?i).+?\\s(alt|title)=\".*";

			for (int i = 0; i < jaList.size(); i++) {

				HtmlFragment en = enList.get(i);
				HtmlFragment ja = jaList.get(i);

				if (en == null) {
					break;
				}
				if (en.getStarts().matches(altPattern) && !ja.getStarts().matches(altPattern)) {
					HtmlFragment enOld = enList.remove(i);
					en = enList.get(i);
					msg.append("●英語読み飛ばし (先頭 alt なし)\n" +
						ja + "\n────────────────────\n" +
						enOld + "\n" +
						en + "\n───────────────────────\n");
					continue;
				}

				if (en.getText().matches(altPattern) && !ja.getText().matches(altPattern)) {
					HtmlFragment enOld = enList.remove(i);
					en = enList.get(i);
					msg.append("●英語読み飛ばし (テキスト alt なし)\n" +
						ja + "\n────────────────────\n" +
						enOld + "\n" +
						en + "\n───────────────────────\n");
					continue;
				}

				for (Property vTrans : vHelpTransReverseProp) {

					String jaPart = vTrans.key;
					String enPart = vTrans.value;

					if (ja.getText().matches(jaPart)) {

						while (!en.getText().matches(enPart)) {
							HtmlFragment enOld = enList.remove(i);
							en = enList.get(i);
							msg.append("●英語読み飛ばし " + enPart + "\n" +
								ja + "\n────────────────────\n" +
								enOld + "\n" +
								en + "\n───────────────────────\n");
						}
					}
				}
			}

			if (enList.size() == jaList.size() + 1) {
				HtmlFragment en = enList.getLast();
				if (en.getStarts().matches(altPattern)) {
					enList.remove(en);
					msg.append("●英語末尾要素カット (先頭 alt なし)\n" + en + "\n");
				}
			}

			return msg.toString();

		} finally {

			if (enList.size() != jaList.size()) {
				log.warn("リスト・サイズ不正。英語：%3d -> %3d、日本語：%3d -> %3d、%s",
					enListSizeOld, enList.size(), jaListSizeOld, jaList.size(), enList.path);
			}
		}
	}
}
