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

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

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import jp.sourceforge.mergedoc.pleiades.log.FileLogger;
import jp.sourceforge.mergedoc.pleiades.log.Logger;
import jp.sourceforge.mergedoc.pleiades.log.Logger.Level;
import jp.sourceforge.mergedoc.pleiades.log.SystemOutLogger;
import jp.sourceforge.mergedoc.pleiades.resource.Files;
import jp.sourceforge.mergedoc.pleiades.resource.PatternCache;
import jp.sourceforge.mergedoc.pleiades.resource.Property;
import jp.sourceforge.mergedoc.pleiades.resource.PropertySet;

/**
 * 抽象バリデーターです。
 * <p>
 * @author cypher256
 */
abstract public class AbstractValidator {

	/** 用語検証プロパティー */
	protected static final PropertySet vTermProp = new PropertySet(VALID_TERM_PROP);

	/** 対訳検証セット */
	protected static final PropertySet vTransProp = new PropertySet(VALID_TRANS_PROP);

	/** 対訳検証 (逆参照) セット */
	protected static final PropertySet vTransReverseProp = new PropertySet(VALID_TRANS_REVERSE_PROP);

	/** 翻訳禁止セット */
	protected static final Set<String> vForbiddenTransSet = new PropertySet(VALID_FORBIDDEN_TRANS_PROP).keySet();

	/** 検証無視セット */
	protected static final Set<String> vIgnoreSet = new PropertySet(VALID_IGNORE_PROP).keySet();

	//-------------------------------------------------------------------------

	/** 隣接単語空白パターン・リスト */
	@SuppressWarnings("serial")
	protected static final List<Pattern> ADJACENT_WORD_SPACE_PATTERN_LIST =
		Collections.unmodifiableList(new ArrayList<Pattern>(){
		{
			String IGNORE_CHAR = "\\p{ASCII}（）「」、。…®";
			add(Pattern.compile("([^" + IGNORE_CHAR + "\\(])(\\p{Alnum})"));
			add(Pattern.compile("(\\p{Alnum})([^" + IGNORE_CHAR + "\\)])"));
		}
	});

	//-------------------------------------------------------------------------

	/** ロガー */
	protected final Logger log = Logger.getLogger(getClass());

	/** 検証ロガー */
	protected final Logger validationLog;

	/** 検証ロガー・ファイル名 */
	protected final String validationLogFileName;

	/** エラー数 */
	protected int totalErrorCount;

	/** 警告数 */
	protected int totalWarnCount;

	/** 既存プロパティー */
	protected final PropertySet existsProp = new PropertySet();

	/**
	 * バリデーターを構築します。
	 * 検証結果は標準出力に出力されます。
	 * <p>
	 * @param existsProps 重複チェックに使用する既存プロパティー
	 */
	public AbstractValidator(PropertySet... existsProps) {

		validationLogFileName = "";
		validationLog = Logger.getLogger("Validator",
			SystemOutLogger.class,
			Level.ERROR);

		for (PropertySet ps : existsProps) {
			existsProp.putAll(ps);
		}
	}

	/**
	 * バリデーターを構築します。
	 * <p>
	 * @param logFileName ログ・ファイル名。例) "props/validator.log"
	 * @param existsProps 重複チェックに使用する既存プロパティー
	 */
	public AbstractValidator(String logFileName, PropertySet... existsProps) {

		if (!logFileName.endsWith(".log")) {
			throw new IllegalArgumentException("ログ・ファイルの拡張子が不正です。" + logFileName);
		}
		validationLogFileName = Files.toVcIgnoreName(logFileName);
		validationLog = Logger.getLogger("Validator",
			FileLogger.class,
			getLogFileLogLevel(),
			Files.conf(validationLogFileName));

		for (PropertySet ps : existsProps) {
			existsProp.putAll(ps);
		}
	}

	/**
	 * ログ・ファイルのログ・レベルを取得します。
	 * オーバーライドしない場合のデフォルトでは Level.WARN です。
	 * <p>
	 * @return ログ・ファイルのログ・レベル
	 */
	protected Level getLogFileLogLevel() {
		return Level.WARN;
	}

	/**
	 * 成功か判定します。
	 * @return 成功の場合は true
	 */
	public boolean isSuccess() {
		return totalErrorCount == 0 && totalWarnCount == 0;
	}

	/**
	 * 検証ログ・ファイル名を取得します。
	 * @return 検証ログ・ファイル名
	 */
	public String getLogFileName() {
		return validationLogFileName;
	}

	/**
	 * ログに終了メッセージを出力します。
	 * @param message メッセージ
	 */
	public void logEndMessage(String message) {

		String msg = message + " エラー:" + totalErrorCount +
		"、警告:" + totalWarnCount;

		log.info(msg + "、conf/" + validationLogFileName);

		if (isSuccess()) {
			validationLog.info(msg);
		} else {
			validationLog.error(msg);
		}
	}

	/**
	 * 検証コンテキストを作成します。
	 * @param en 英語
	 * @param ja 日本語
	 * @param name 識別子
	 * @return
	 */
	protected ValidationContext createValidationContext(String en, String ja, String name) {
		return new ValidationContext(en, ja, name);
	}

	/**
	 * 検証コンテキスト・クラスです。
	 */
	protected class ValidationContext extends ValidationResult {

		protected final String en;
		protected final String ja;
		protected final String name;

		/**
		 * 検証コンテキスト・クラスを構築します。
		 * @param en 英語
		 * @param ja 日本語
		 * @param name 識別子
		 */
		public ValidationContext(String en, String ja, String name) {
			this.en = en;
			this.ja = ja;
			this.name = name;
		}

		/**
		 * 情報ログを出力します。
		 * @param msg メッセージ
		 */
		protected void info(String msg) {
			validationLog.info(formatMessage(msg));
		}

		/**
		 * 警告ログを出力します。
		 * @param msg メッセージ
		 */
		protected void warn(String msg) {
			validationLog.warn(formatMessage(msg));
			warnCount++;
			totalWarnCount++;
		}

		/**
		 * エラーログを出力します。
		 * @param msg メッセージ
		 */
		protected void error(String msg) {
			validationLog.error(formatMessage(msg));
			errorCount++;
			totalErrorCount++;
		}

		/**
		 * メッセージをフォーマットします。
		 * @param msg メッセージ
		 */
		protected String formatMessage(String msg) {

			String m = "\n" +
				"英: " + Property.escapeKey(en) + "\n" +
				"日: " + Property.escapeValue(ja) + "\n" +
				msg + "\n";

			if (name != null && name.length() > 0) {
				m = name + m;
			}
			String indent = "      ";
			m = m.replace("\n", "\n" + indent);
			return m;
		}
	}

	/**
	 * 対訳正規表現プロパティー・ファイルを参照し、
	 * 翻訳が不正な場合は、その対訳正規表現プロパティーを取得します。
	 * <p>
	 * @param en 英語
	 * @param ja 日本語
	 * @return 正しい場合は null。不正な場合は対訳正規表現プロパティー。
	 */
	protected Property getIllegalTranslationProperty(String en, String ja) {

		for (Property vTrans : vTransProp) {

			String enPart = "(?s)" + vTrans.key;
			String jaPart = "(?s)" + vTrans.value;

			if (en.matches(enPart) && !ja.matches(jaPart)) {
				return vTrans;
			}
		}
		for (Property vTrans : vTransReverseProp) {

			String jaPart = "(?s)" + vTrans.key;
			String enPart = "(?s)" + vTrans.value;

			if (ja.matches(jaPart) && !en.matches(enPart)) {
				return vTrans;
			}
		}
		return null;
	}

	/**
	 * 日本語の {0} などの埋め込み引数を検査し、英語に含まれない場合はその埋め込み引数を取得します。
	 * 英語にあって、日本語に無いものはチェックされません。
	 * <p>
	 * @param en 英語
	 * @param ja 日本語
	 * @return 正しい場合は null。不正な場合は埋め込み引数。
	 */
	protected String getIllegalBindParameter(String en, String ja) {

		Matcher mat = PatternCache.get("\\{[0-9]\\}").matcher(ja);
		while (mat.find()) {
			String group = mat.group();
			if (!en.contains(group)) {
				return group;
			}
		}
		return null;
	}

	/**
	 * 翻訳が禁止されているか判定します。
	 * <p>
	 * @param en 英語
	 * @return 禁止の場合は true
	 */
	public boolean isForbidden(String en) {

		if (vForbiddenTransSet.contains(en)) {
			return true;
		}
		// 数値で構成されているもの
		// 例) {0}.
		// 例) 2.2, 2.3, 2.4 ← WTP で Tomcat サーバー構成追加でエラーとなる
		if (en.matches("[\\d+\\{\\}\\.,'\"\\s]+")) {
			return true;
		}
		return false;
	}
}
