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

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import woolpack.bool.BoolUtils;
import woolpack.bool.BooleanState;
import woolpack.fn.Fn;
import woolpack.utils.Utils;

/**
 * 値検証と値変換のユーティリティです。
 * 型推論で表記を簡略するためのスタティックメソッドと変数を含みます。
 * 
 * @author nakamura
 * 
 */
public final class ValidatorUtils {
	/**
	 * {@link ValidatorContext#getValue()}を返す関数です。
	 */
	public static final Fn<ValidatorContext, Object, RuntimeException> VALUE = new ValueGetter<RuntimeException>();
	
	/**
	 * {@link ValidatorContext#getInputMap()}を返す関数です。
	 */
	public static final Fn<ValidatorContext, Map<String, List<Object>>, RuntimeException> MAP = new MapGetter<RuntimeException>();
	
	/**
	 * {@link ValidatorContext#getInputMap()}の
	 * {@link ValidatorContext#getKey()}を
	 * キーとした値一覧を返す関数です。
	 */
	public static final Fn<ValidatorContext, List<Object>, RuntimeException> VALUES = new ValuesGetter<RuntimeException>();

	private ValidatorUtils() {
	}

	/**
	 * 引数のキーが全て文字列型・値が全てオブジェクトの一覧とみなして変換します。
	 * サーブレットAPIの request.getParameterMap() を変換するために定義しています。
	 * 返却値を更新しても引数には影響しません。
	 * @param map 変換対象。
	 * @return 変換結果。
	 */
	public static Map<String, List<Object>> convert(final Map map) {
		final Map<String, List<Object>> map1 = new HashMap<String, List<Object>>();
		for (final Object entryObject : map.entrySet()) {
			final Entry entry = (Entry) entryObject;
			final Iterable<?> c = Utils.toIterable(entry.getValue());
			final List<Object> list = new ArrayList<Object>();
			for (final Object o : c) {
				list.add(o);
			}
			map1.put((String) entry.getKey(), list);
		}
		return map1;
	}
	
	/**
	 * プロパティ名で委譲先を分岐する関数を生成します。
	 * プロパティ名に対応する値が存在しない場合は委譲しないため、
	 * ブラウザから送信されない項目を無視する際に使用します。
	 * @param <E>
	 * @param operator 値検証一覧の呼び出し方。
	 * @param map プロパティ名と委譲先の対応表。
	 * @return 関数。
	 */
	public static <E extends Exception> Fn<ValidatorContext, Boolean, E> branchByNameIfExists(
			final Fn<Object, ? extends BooleanState, ? extends RuntimeException> operator,
			final Map<String, ? extends Fn<? super ValidatorContext, Boolean, ? extends E>> map) {
		return new NameBranchIfExists<E>(operator, map);
	}
	
	/**
	 * プロパティ名で委譲先を分岐する関数を生成します。
	 * プロパティ名に対応する値が存在しない場合は委譲しないため、
	 * ブラウザから送信されない項目を無視する際に使用します。
	 * {@link BoolUtils#AND}を使用します。
	 * @param <E>
	 * @param map プロパティ名と委譲先の対応表。
	 * @return 関数。
	 */
	public static <E extends Exception> Fn<ValidatorContext, Boolean, E> branchByNameIfExists(
			final Map<String, ? extends Fn<? super ValidatorContext, Boolean, ? extends E>> map) {
		return new NameBranchIfExists<E>(BoolUtils.AND, map);
	}
	
	/**
	 * プロパティ名で委譲先を分岐する関数を生成します。
	 * プロパティ名に対応する値が存在しない場合も委譲するため、
	 * ブラウザから送信されない項目を必須を検証する際に使用することができます。
	 * @param <E>
	 * @param operator 値検証一覧の呼び出し方。
	 * @param map プロパティ名と委譲先の対応表。
	 * @return 関数。
	 */
	public static <E extends Exception> Fn<ValidatorContext, Boolean, E> branchByName(
			final Fn<Object, ? extends BooleanState, ? extends RuntimeException> operator,
			final Map<String, ? extends Fn<? super ValidatorContext, Boolean, ? extends E>> map) {
		return new NameBranch<E>(operator, map);
	}
	
	/**
	 * プロパティ名で委譲先を分岐する関数を生成します。
	 * プロパティ名に対応する値が存在しない場合も委譲するため、
	 * ブラウザから送信されない項目を必須を検証する際に使用することができます。
	 * {@link BoolUtils#AND}を使用します。
	 * @param <E>
	 * @param map プロパティ名と委譲先の対応表。
	 * @return 関数。
	 */
	public static <E extends Exception> Fn<ValidatorContext, Boolean, E> branchByName(
			final Map<String, ? extends Fn<? super ValidatorContext, Boolean, ? extends E>> map) {
		return new NameBranch<E>(BoolUtils.AND, map);
	}
	
	/**
	 * 値の変換を委譲する関数を生成します。
	 * @param <E>
	 * @param fn 値を変換する委譲先。
	 * @return 関数。
	 */
	public static <E extends Exception> Fn<ValidatorContext, Boolean, E> convertValue(final Fn<Object, ?, ? extends E> fn) {
		return new ConvertValidator<E>(fn);
	}
	
	/**
	 * メッセージを追加する関数です。
	 * @param message メッセージ。
	 * @return 関数。
	 */
	public static Fn<ValidatorContext, Boolean, RuntimeException> message(final String message) {
		return new MessageValidator<RuntimeException>(message);
	}
	
	/**
	 * インデックス値を一時的に設定して委譲し、
	 * 委譲先から復帰したときに呼び出し時の状態に初期化する関数を生成します。
	 * @param <E>
	 * @param tmpIndex 一時的に設定するインデックス。
	 * @param fn 委譲先。
	 * @return 関数。
	 */
	public static <E extends Exception> Fn<ValidatorContext, Boolean, E> localIndex(
			final int tmpIndex,
			final Fn<? super ValidatorContext, Boolean, ? extends E> fn) {
		return new LocalIndexValidator<E>(tmpIndex, fn);
	}
	
	/**
	 * キー値を一時的に設定して委譲し、
	 * 委譲先から復帰したときに呼び出し時の状態に初期化する関数を生成します。
	 * @param <E>
	 * @param tmpKey
	 * @param fn 委譲先。
	 * @return 関数。
	 */
	public static <E extends Exception> Fn<ValidatorContext, Boolean, E> localKey(
			final String tmpKey,
			final Fn<? super ValidatorContext, Boolean, ? extends E> fn) {
		return new LocalKeyValidator<E>(tmpKey, fn);
	}
	
	/**
	 * {@link ValidatorContext#getInputMap()}の
	 * {@link ValidatorContext#getKey()}をキーとした
	 * 値一覧に対し順次委譲先を実行する関数を生成します。
	 * @param <E>
	 * @param operator 値検証一覧の呼び出し方。
	 * @param fn 委譲先。
	 * @return 関数。
	 */
	public static <E extends Exception> Fn<ValidatorContext, Boolean, E> loopValue(
			final Fn<Object, ? extends BooleanState, ? extends RuntimeException> operator,
			final Fn<? super ValidatorContext, Boolean, ? extends E> fn) {
		return new ValueLoopValidator<E>(operator, fn);
	}
	
	/**
	 * {@link ValidatorContext#getInputMap()}の
	 * {@link ValidatorContext#getKey()}をキーとした
	 * 値一覧に対し順次委譲先を実行する関数を生成します。
	 * {@link BoolUtils#AND}を使用します。
	 * @param <E>
	 * @param fn 委譲先。
	 * @return 関数。
	 */
	public static <E extends Exception> Fn<ValidatorContext, Boolean, E> loopValue(
			final Fn<? super ValidatorContext, Boolean, ? extends E> fn) {
		return new ValueLoopValidator<E>(BoolUtils.AND, fn);
	}
}
