/*
 * 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 static jp.sourceforge.mergedoc.pleiades.resource.FileNames.*;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import jp.sourceforge.mergedoc.org.apache.commons.lang.StringUtils;
import jp.sourceforge.mergedoc.pleiades.log.Logger;
import jp.sourceforge.mergedoc.pleiades.resource.AbstractTranslationDictionary;
import jp.sourceforge.mergedoc.pleiades.resource.Property;
import jp.sourceforge.mergedoc.pleiades.resource.PropertySet;

/**
 * 翻訳用の正規表現辞書です。
 * <p>
 * @author cypher256
 */
public class RegexDictionary {

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

	/** 翻訳正規表現置換リスト初期容量 */
	private static final int REGEX_LIST_CAPACITY = 1000;

	/** 翻訳スルー正規表現リスト初期容量 */
	private static final int REGEX_THROUGH_LIST_CAPACITY = 100;

	/** このクラスのシングルトン・インスタンス */
	private static final RegexDictionary singleton = new RegexDictionary();

	/**
	 * 正規表現辞書オブジェクトを取得します。
	 * <p>
	 * @return 翻訳正規表現プロパティー・オブジェクト
	 */
	public static RegexDictionary getInstance() {
		return singleton;
	}

	/** 正規表現置換エントリー */
	private static class RegexEntry {
		public Pattern pattern;
		public String replacement;
	}

	/** 翻訳正規表現置換マップ (高速化のため最初・最後の文字をキーとしたマップにより正規表現判定前に絞り込む) */
    private final Map<String, List<RegexEntry>> firstCharMap = new HashMap<String, List<RegexEntry>>();
    private final Map<String, List<RegexEntry>> lastCharMap = new HashMap<String, List<RegexEntry>>();
    private final List<RegexEntry> otherList = new ArrayList<RegexEntry>();

	/** 翻訳スルー正規表現 */
	private Pattern throughPattern;

	/**
	 * 正規表現辞書を構築します。
	 */
	private RegexDictionary() {

		File file = AbstractTranslationDictionary.validateExists(TRANS_REGEX_PROP);
		for (Property p : new PropertySet(REGEX_LIST_CAPACITY).load(file)) {
			put(p.key, p.value);
		}
		file = AbstractTranslationDictionary.validateExists(TRANS_REGEX_THROUGH_PROP);
		Set<String> throughSet = new PropertySet(REGEX_THROUGH_LIST_CAPACITY).load(file).keySet();
		throughPattern = Pattern.compile("(?s)(" + StringUtils.join(throughSet, "|") + ")");
	}

	/**
	 * 正規表現エントリーを追加します。
	 * @param enPattern 英語正規表現パターン
	 * @param jaReplacement 日本語リプレースメント
	 */
	public void put(String enPattern, String jaReplacement) {

		RegexEntry regexEntry = new RegexEntry();
		regexEntry.pattern = Pattern.compile("(?s)" + enPattern);
		regexEntry.replacement = jaReplacement;

        String firstChar = getFirstChar(enPattern);
        if (firstChar != null) {
            put(firstCharMap, firstChar, regexEntry);
        } else {
            String lastChar = getLastChar(enPattern);
            if (lastChar != null) {
                put(lastCharMap, lastChar, regexEntry);
            } else {
                otherList.add(regexEntry);
            }
        }
	}

    /**
     * 正規表現エントリーマップにエントリーを追加します。
     * @param regexListMap 正規表現エントリーマップ
     * @param key キー
     * @param regexEntry 正規表現エントリー
     */
	private void put(Map<String, List<RegexEntry>> regexListMap, String key, RegexEntry regexEntry) {

        List<RegexEntry> regexList = regexListMap.get(key);
        if (regexList == null) {
            regexList = new ArrayList<RegexEntry>();
            regexListMap.put(key, regexList);
        }
        regexList.add(regexEntry);
    }

    /**
     * 先頭文字が正規表現ではない場合は先頭文字を取得します。
     * @param enPattern 英語正規表現パターン
     * @return 先頭文字。正規表現の可能性がある場合は null。
     */
    private String getFirstChar(String enPattern) {

        char c = enPattern.charAt(0);
        if (c == '(') { // 先頭で使用可能な正規表現はかっこのみであることが前提
            return null;
        }
        if (enPattern.startsWith("\\[")) {
            return "[";
        }
        return String.valueOf(c);
    }

    /**
     * 末尾文字が正規表現ではない場合は末尾文字を取得します。
     * @param enPattern 英語正規表現パターン
     * @return 末尾文字。正規表現の可能性がある場合は null。
     */
    private String getLastChar(String enPattern) {

        char c = enPattern.charAt(enPattern.length() - 1);
        if (c == ')') { // 末尾で使用可能な正規表現はかっこのみであることが前提
            return null;
        }
        if (!enPattern.endsWith("\\]") && enPattern.endsWith("]")) {
            return null;
        }
        return String.valueOf(c);
    }

	/**
	 * 翻訳します。
	 * <p>
	 * @param en 英語文字列 (TranslationString#trim された値)
	 * @return 翻訳後日本語文字列。翻訳できない場合は null。
	 */
	public String lookup(String en) {

		if (en == null || en.isEmpty()) {
			return null;
		}
        String ja = replaceToJa(en, firstCharMap.get(en.substring(0, 1)));
        if (ja == null) {
            ja = replaceToJa(en, lastCharMap.get(en.substring(en.length() - 1)));
            if (ja == null) {
                ja = replaceToJa(en, otherList);
            }
        }
		if (ja != null) {
			return ja;
		}
		if (throughPattern.matcher(en).matches()) {
			return en;
		}
		return null;
	}

	/**
	 * 指定された文字列を対象となるすべての正規表現でスキャンして置換します。
	 * @param en 対象英字文字列
     * @param regexList 正規表現エントリーリスト
	 * @return 正規表現適用結果となる日本語文字列。適用できなかった場合は null。
	 */
    private String replaceToJa(String en, List<RegexEntry> regexList) {

        if (regexList != null) {
            for (RegexEntry entry : regexList) {
                Matcher matcher = entry.pattern.matcher(en);
                try {
                    if (matcher.matches()) {
                        return matcher.replaceFirst(entry.replacement);
                    }
                } catch (RuntimeException e) {
                    log.error(e, "正規表現翻訳でエラーが発生しました。en:" + en + ", pattern:" + entry.pattern);
                    break;
                }
            }
        }
        return null;
    }

	/**
	 * 全辞書フルスキャンパフォーマンス確認 main メソッドです。
	 * @param args
	 */
	public static void main(String[] args) {

		RegexDictionary instance = getInstance();
		PropertySet prop = new PropertySet(TRANS_PROP);
		long s = System.currentTimeMillis();

		for (Property p : prop) {
			instance.lookup(p.key);
		}
		log.info("%.3f ms", (System.currentTimeMillis() - s) / 1000d);
	}
}
