/*
 * 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.el.ArrayPathEL;
import woolpack.el.EL;
import woolpack.el.MapEL;
import woolpack.fn.Fn;
import woolpack.validator.SimpleMessageCollector;
import woolpack.validator.ValidatorContext;
import woolpack.validator.ValidatorUtils;

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

	private EL messagesEL;
	private boolean valueNotFoundIgnoreFlag;
	private Fn<? super ValidatorContext, Boolean, ? extends E> validatorFn;

	/**
	 * @param validatorFn
	 *            委譲先。
	 * @param messagesEL
	 *            メッセージ一覧の取得先への参照。
	 * @param valueNotFoundIgnoreFlag
	 *            {@link EEContext#getConfig()}にメッセージの値が
	 *            見つからない場合に無視する(置き換えないだけ)場合は
	 *            true。NullPointerException を投げる場合はfalse。
	 */
	public ValidatorBuilder(
			final Fn<? super ValidatorContext, Boolean, ? extends E> validatorFn,
			final EL messagesEL,
			final boolean valueNotFoundIgnoreFlag) {
		this.validatorFn = validatorFn;
		this.messagesEL = messagesEL;
		this.valueNotFoundIgnoreFlag = valueNotFoundIgnoreFlag;
	}

	/**
	 * {@link EEContext#getConfig()}にメッセージの値が
	 * 見つからない場合は{@link NullPointerException}を投げます。
	 * 
	 * @param validatorFn 委譲先。
	 */
	public ValidatorBuilder(final Fn<? super ValidatorContext, Boolean, ? extends E> validatorFn) {
		this(validatorFn, DEFALT_MESSAGES_EL, false);
	}

	/**
	 * 値検証委譲先を実行して結果により委譲先を分岐する{@link Fn}を返します。
	 * 
	 * @param trueFn 値の検証結果が true の場合の委譲先。
	 * @param falseFn 値の検証結果が false の場合の委譲先。
	 * @return 値検証委譲先を実行して結果により委譲先を分岐する{@link Fn}。
	 */
	public Fn<EEContext, Void, E> getCheckExpression(
			final Fn<EEContext, Void, ? extends E> trueFn,
			final Fn<EEContext, Void, ? extends E> falseFn) {
		return new Fn<EEContext, Void, E>() {
			public Void exec(final EEContext domContext) throws E {
				final ValidatorContext validatorContext = new ValidatorContext();
				final SimpleMessageCollector collector = new SimpleMessageCollector();
				validatorContext.setCollectable(collector);
				validatorContext.setId(domContext.getId());
				validatorContext.setInputMap(ValidatorUtils.convert(domContext.getInput()));
				final Map tmpMap = domContext.getInput();
				try {
					domContext.setInput(validatorContext.getInputMap());
					if (validatorFn.exec(validatorContext)) {
						trueFn.exec(domContext);
					} else {
						messagesEL.setValue(domContext, collector.getList());
						falseFn.exec(domContext);
					}
				} finally {
					domContext.setInput(tmpMap);
				}
				return null;
			}
		};
	}

	/**
	 * メッセージの値を{@link EEContext#getConfig()}から取得し置き換える{@link Fn}を返します。
	 * 
	 * @return メッセージの値を{@link EEContext#getConfig()}から取得し置き換える{@link Fn}。
	 */
	public Fn<EEContext, Void, E> getReplaceExpression() {
		return new Fn<EEContext, Void, E>() {
			public Void exec(final EEContext 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 = config.get(key);
						if (value == null) {
							if (!valueNotFoundIgnoreFlag) {
								throw new IllegalStateException("not found for key : " + key);
							}
						} else {
							messages.set(i, value);
						}
					}
				}
				return null;
			}
		};
	}

	public EL getMessagesEL() {
		return messagesEL;
	}
	public void setMessagesEL(final EL messagesEL) {
		this.messagesEL = messagesEL;
	}
	public Fn<? super ValidatorContext, Boolean, ? extends E> getValidatorFn() {
		return validatorFn;
	}
	public void setValidatorFn(
			final Fn<? super ValidatorContext, Boolean, ? extends E> validatorFn) {
		this.validatorFn = validatorFn;
	}
	public boolean isValueNotFoundIgnoreFlag() {
		return valueNotFoundIgnoreFlag;
	}
	public void setValueNotFoundIgnoreFlag(final boolean valueNotFoundIgnoreFlag) {
		this.valueNotFoundIgnoreFlag = valueNotFoundIgnoreFlag;
	}
}
