/*
 * 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.DomContext;
import woolpack.dom.DomExpression;
import woolpack.utils.EmbeddingResolveable;
import woolpack.utils.OGE;
import woolpack.validator.ValidatorConstants;
import woolpack.validator.ValidatorContext;
import woolpack.validator.ValidatorExpression;

/**
 * 値を検証・加工する{@link DomExpression}の生成器。
 * 関数従属性を考慮すると検証結果表示画面を画面遷移定義に記載するデータモデルが妥当であるが、
 * 値検証対象 id と検証結果表示画面 id の関数従属性を独立したエンティティとして扱うデータモデルが設計上保守性が維持できると判断し、
 * 本実装では値検証対象 id と検証結果表示画面 id の対応を、画面遷移定義と別に{@link Map}で定義している。
 * @author nakamura
 *
 */
public class ValidatorBuilder {
	/**
	 * メッセージの一覧の取得先のデフォルト値。
	 */
	public static final OGE DEFALT_MESSAGES_OGE = new OGE("local[\"woolpack.ee.MESSAGES\"]");
	
	private final OGE messagesOGE;
	private final boolean valueNotFoundIgnoreFlag;
	private final ValidatorExpression validatorExpression;

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

	/**
	 * コンストラクタ。
	 * メッセージ一覧の取得先として{@link #DEFALT_MESSAGES_OGE}を使用する。
	 * {@link DomContext#getConfig()}にメッセージの値が見つからない場合は{@link NullPointerException}を投げる。
	 * @param validatorExpression 委譲先。
	 * @throws StringIndexOutOfBoundsException defaultErrorId が空の場合。
	 * @throws NullPointerException 引数のいずれかが null の場合。
	 */
	public ValidatorBuilder(
			final ValidatorExpression validatorExpression){
		this(validatorExpression, DEFALT_MESSAGES_OGE, false);
	}
	
	/**
	 * メッセージの一覧の取得先を返す。
	 * @return メッセージの一覧の取得先。
	 */
	public OGE getOGE(){
		return messagesOGE;
	}
	
	/**
	 * 委譲先{@link ValidatorExpression}を実行して結果により{@link DomExpression}を分岐する{@link DomExpression}を返す。
	 * 
	 * {@link DomContext}に設定する{@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();
				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{
						messagesOGE.setValue(domContext, validatorContext.getMessageList());
						falseExpression.interpret(domContext);
					}
				}catch(final RuntimeException e){
					throw new ValidatorRuntimeException(validatorContext, e);
				}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){
		resolver.getClass();
		return new DomExpression(){
			public void interpret(final DomContext context) {
				final List messages = (List)messagesOGE.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 NullPointerException("not found.");
							}
						}else{
							messages.set(i, value);
						}
					}
				}
			}
		};
	}
}
