/*
 * Copyright 2006 Takahiro Nakamura.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package woolpack.ee;

import java.util.List;
import java.util.Map;

import woolpack.dom.DomConstants;
import woolpack.dom.DomContext;
import woolpack.dom.DomExpression;
import woolpack.el.ArrayPathEL;
import woolpack.el.EL;
import woolpack.el.MapEL;
import woolpack.utils.CheckUtils;
import woolpack.utils.EmbeddingResolveable;
import woolpack.validator.SimpleMessageCollector;
import woolpack.validator.ValidatorConstants;
import woolpack.validator.ValidatorContext;
import woolpack.validator.ValidatorExpression;

/**
 * 値を検証/加工する{@link DomExpression}のビルダ。 関数従属性を考慮した場合、値検証結果を表示する id
 * を画面遷移定義に管理するデータモデルが妥当であるが、 値検証結果を表示する id
 * と画面遷移定義は別のエンティティで管理するほうが保守性が維持できると判断した。 適用しているパターン：Adapter, Hook Operation。
 * 
 * @author nakamura
 * 
 */
public class ValidatorBuilder {
	/**
	 * メッセージの一覧の取得先のデフォルト値。
	 */
	public static final EL DEFALT_MESSAGES_EL = new ArrayPathEL(
			DomConstants.LOCAL_EL, new MapEL("woolpack.ee.MESSAGES"));

	private final EL messagesEL;

	private final boolean valueNotFoundIgnoreFlag;

	private final ValidatorExpression validatorExpression;

	/**
	 * コンストラクタ。
	 * 
	 * @param validatorExpression
	 *            委譲先。
	 * @param messagesEL
	 *            メッセージ一覧の取得先への参照。
	 * @param valueNotFoundIgnoreFlag
	 *            {@link DomContext#getConfig()}にメッセージの値が
	 *            見つからない場合に無視する(置き換えないだけ)場合は
	 *            true。NullPointerException を投げる場合はfalse。
	 * @throws StringIndexOutOfBoundsException
	 *             defaultErrorId が空の場合。
	 * @throws NullPointerException
	 *             引数のいずれかが null の場合。
	 */
	public ValidatorBuilder(final ValidatorExpression validatorExpression,
			final EL messagesEL, final boolean valueNotFoundIgnoreFlag) {
		CheckUtils.checkNotNull(validatorExpression);
		CheckUtils.checkNotNull(messagesEL);

		this.validatorExpression = validatorExpression;
		this.messagesEL = messagesEL;
		this.valueNotFoundIgnoreFlag = valueNotFoundIgnoreFlag;
	}

	/**
	 * コンストラクタ。 {@link DomContext#getConfig()}にメッセージの値が
	 * 見つからない場合は{@link NullPointerException}を投げる。
	 * 
	 * @param validatorExpression
	 *            委譲先。
	 * @throws StringIndexOutOfBoundsException
	 *             defaultErrorId が空の場合。
	 * @throws NullPointerException
	 *             引数のいずれかが null の場合。
	 */
	public ValidatorBuilder(final ValidatorExpression validatorExpression) {
		this(validatorExpression, DEFALT_MESSAGES_EL, false);
	}

	/**
	 * メッセージの一覧の取得先を返す。
	 * 
	 * @return メッセージの一覧の取得先。
	 */
	public EL getMessageEL() {
		return messagesEL;
	}

	/**
	 * 委譲先{@link ValidatorExpression}を実行して結果により
	 * {@link DomExpression}を分岐する{@link DomExpression}を返す。
	 * {@link DomExpression#interpret(DomContext)}
	 * では引数または{@link DomContext#getId()}
	 * または{@link DomContext#getInput()}が null の
	 * 場合に{@link NullPointerException}を投げる。
	 * 
	 * @param trueExpression
	 *            値の検証結果が true の場合の委譲先。
	 * @param falseExpression
	 *            値の検証結果が false の場合の委譲先。
	 * @return 委譲先{@link ValidatorExpression}を実行して
	 * {@link DomContext}に設定する{@link DomExpression}。
	 */
	public DomExpression getCheckExpression(final DomExpression trueExpression,
			final DomExpression falseExpression) {
		return new DomExpression() {
			public void interpret(final DomContext domContext) {
				final ValidatorContext validatorContext
				= new ValidatorContext();
				final SimpleMessageCollector collector
				= new SimpleMessageCollector();
				validatorContext.setCollectable(collector);
				validatorContext.setId(domContext.getId());
				validatorContext.setInputMap(ValidatorConstants
						.convert(domContext.getInput()));
				final Map tmpMap = domContext.getInput();
				try {
					domContext.setInput(validatorContext.getInputMap());
					if (validatorExpression.interpret(validatorContext)) {
						trueExpression.interpret(domContext);
					} else {
						messagesEL.setValue(domContext, collector.getList());
						falseExpression.interpret(domContext);
					}
				} finally {
					domContext.setInput(tmpMap);
				}
			}
		};
	}

	/**
	 * メッセージの値を{@link DomContext#getConfig()}から取得し置き換える{@link DomExpression}を返す。
	 * {@link DomExpression#interpret(DomContext)}
	 * では引数または{@link DomContext#getConfig()}が
	 * null の場合に{@link NullPointerException}を投げる。
	 * 
	 * @return メッセージの値を{@link DomContext#getConfig()}
	 * から取得し置き換える{@link DomExpression}。
	 */
	public DomExpression getReplaceExpression() {
		return getResolveEmbeddingExpression(new EmbeddingResolveable() {
			public void resolve(final Map<String, Object> map) {
			} // カバレージがここを通過してはいけない

			public String resolve(final Map<String, Object> map,
					final String value) {
				return (String) map.get(value);
			}
		});
	}

	/**
	 * メッセージの値を{@link DomContext#getConfig()}
	 * から取得し埋め込みを解決して置き換える{@link DomExpression}を返す。
	 * valueNotFoundIgnoreFlag が false の場合、メッセージ一覧の原始性は保証されない。
	 * {@link DomExpression#interpret(DomContext)}
	 * では引数または{@link DomContext#getConfig()}が
	 * null の場合に{@link NullPointerException}を投げる。
	 * 
	 * @param resolver
	 *            メッセージの埋め込みを解決するリゾルバ。
	 * @return メッセージの値を{@link DomContext#getConfig()}
	 * から取得し埋め込みを解決して置き換える{@link DomExpression}。
	 * @throws NullPointerException
	 *             引数が null の場合。
	 */
	public DomExpression getResolveEmbeddingExpression(
			final EmbeddingResolveable resolver) {
		CheckUtils.checkNotNull(resolver);
		return new DomExpression() {
			public void interpret(final DomContext context) {
				final List messages = (List) messagesEL.getValue(context);
				if (messages != null) {
					final int size = messages.size();
					final Map<String, Object> config = context.getConfig();
					for (int i = 0; i < size; i++) {
						final String key = (String) messages.get(i);
						final Object value = resolver.resolve(config, key);
						if (value == null) {
							if (!valueNotFoundIgnoreFlag) {
								throw new IllegalStateException("not found.");
							}
						} else {
							messages.set(i, value);
						}
					}
				}
			}
		};
	}
}
