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

import java.util.Collection;
import java.util.Map;

import woolpack.utils.Utils;

/**
 * ユーティリティです。
 * 型推論で表記を簡略するためのスタティックメソッドを含みます。
 * 
 * @author nakamura
 *
 */
public final class FnUtils {
	
	/**
	 * プリミティブ型をオブジェクト型に変換する{@link Fn}です。
	 */
	public static final Fn<Class, Class, RuntimeException> TO_WRAPPER = switching(
			Utils.<Class, Class>
			map(boolean.class, Boolean.class)
			.map(char.class, Character.class)
			.map(byte.class, Byte.class)
			.map(short.class, Short.class)
			.map(int.class, Integer.class)
			.map(long.class, Long.class)
			.map(float.class, Float.class)
			.map(double.class, Double.class), FnUtils.<Class>echo());
	
	private FnUtils() {
	}
	
	/**
	 * {@link Class#cast(Object)}を使用してキャストする関数を生成します。
	 * @param <R>
	 * @param clazz キャストする型。
	 * @return 関数。
	 */
	public static <R> Fn<Object, R, RuntimeException> castTo(final Class<R> clazz) {
		return new CastFn<R, RuntimeException>(clazz);
	}
	
	/**
	 * 限定的な汎関数を処理する関数を生成します。
	 * @param <C>
	 * @param <R>
	 * @param <E>
	 * @param fn 委譲先。
	 * @return 関数。
	 */
	public static <C, R, E extends Exception> Fn<C, R, E> exec(
			final Fn<? super C, ? extends Fn<? super C, ? extends R, ? extends E>, ? extends E> fn) {
		return new ExecFn<C, R, E>(fn);
	}
	
	/**
	 * 引数をそのまま返す関数を生成します。
	 * @param <C>
	 * @return 関数。
	 */
	public static <C> Fn<C, C, RuntimeException> echo() {
		return new EchoFn<C, RuntimeException>();
	}
	
	/**
	 * 固定値を返す関数を生成します。
	 * @param <C>
	 * @param <R>
	 * @param <E>
	 * @param value 返却する値。
	 * @return 関数。
	 */
	public static <C, R, E extends Exception> Fn<C, R, E> fixThrows(final R value) {
		return new FixFn<C, R, E>(value);
	}
	
	/**
	 * 固定値を返す関数を生成します。
	 * @param <C>
	 * @param <R>
	 * @param value 返却する値。
	 * @return 関数。
	 */
	public static <C, R> Fn<C, R, RuntimeException> fix(final R value) {
		return new FixFn<C, R, RuntimeException>(value);
	}
	
	/**
	 * 評価結果により委譲先を分岐する関数を生成します。
	 * @param <C>
	 * @param <R>
	 * @param <E>
	 * @param ifFn 評価用の委譲先。
	 * @param trueFn 評価結果が{@link Boolean#TRUE}の場合の委譲先。
	 * @param falseFn 評価結果が{@link Boolean#TRUE}でない場合の委譲先。
	 * @return 関数。
	 */
	public static <C, R, E extends Exception> Fn<C, R, E> ifTrue(
			final Fn<? super C, ?, ? extends E> ifFn,
			final Fn<? super C, ? extends R, ? extends E> trueFn,
			final Fn<? super C, ? extends R, ? extends E> falseFn) {
		return new IfFn<C, R, E>(ifFn, trueFn, falseFn);
	}
	
	/**
	 * ふたつの関数を合成した関数を生成します。
	 * 最初の委譲先の返却値を引数してに次の委譲先を実行しその結果を返します。
	 * @param <A>
	 * @param <B>
	 * @param <C>
	 * @param <E>
	 * @param fn0 最初の委譲先。
	 * @param fn1 ふたつめの委譲先。
	 * @return 関数。
	 */
	public static <A, B, C, E extends Exception> Fn<A, C, E> join(
			final Fn<? super A, ? extends B, ? extends E> fn0,
			final Fn<? super B, ? extends C, ? extends E> fn1) {
		return new JoinFn<A, B, C, E>(fn0, fn1);
	}

	/**
	 * 関数を合成した関数を生成します。
	 * @param <A>
	 * @param <B>
	 * @param <C>
	 * @param <D>
	 * @param <E>
	 * @param fn0
	 * @param fn1
	 * @param fn2
	 * @return 関数。
	 */
	public static <A, B, C, D, E extends Exception> Fn<A, D, E> join(
			final Fn<? super A, ? extends B, ? extends E> fn0,
			final Fn<? super B, ? extends C, ? extends E> fn1,
			final Fn<? super C, ? extends D, ? extends E> fn2) {
		return join(join(fn0, fn1), fn2);
	}

	/**
	 * 関数を合成した関数を生成します。
	 * @param <A>
	 * @param <B>
	 * @param <C>
	 * @param <D>
	 * @param <E>
	 * @param <T>
	 * @param fn0
	 * @param fn1
	 * @param fn2
	 * @param fn3
	 * @return 関数。
	 */
	public static <A, B, C, D, E, T extends Exception> Fn<A, E, T> join(
			final Fn<? super A, ? extends B, ? extends T> fn0,
			final Fn<? super B, ? extends C, ? extends T> fn1,
			final Fn<? super C, ? extends D, ? extends T> fn2,
			final Fn<? super D, ? extends E, ? extends T> fn3) {
		return join(join(fn0, fn1), join(fn2, fn3));
	}

	/**
	 * 関数を合成した関数を生成します。
	 * @param <A>
	 * @param <B>
	 * @param <C>
	 * @param <D>
	 * @param <E>
	 * @param <F>
	 * @param <T>
	 * @param fn0
	 * @param fn1
	 * @param fn2
	 * @param fn3
	 * @param fn4
	 * @return 関数。
	 */
	public static <A, B, C, D, E, F, T extends Exception> Fn<A, F, T> join(
			final Fn<? super A, ? extends B, ? extends T> fn0,
			final Fn<? super B, ? extends C, ? extends T> fn1,
			final Fn<? super C, ? extends D, ? extends T> fn2,
			final Fn<? super D, ? extends E, ? extends T> fn3,
			final Fn<? super E, ? extends F, ? extends T> fn4) {
		return join(join(fn0, fn1, fn2), join(fn3, fn4));
	}
	
	/**
	 * 通過情報を{@link Collection}に記録するテスト用の関数を生成します。
	 * @param <C>
	 * @param <R>
	 * @param <E>
	 * @param fn 委譲先。null の場合は委譲しません。
	 * @param name nameListに追加する名前。
	 * @param nameList name を追加する対象。null の場合は追加しません。
	 * @param contextList コンテキスト役を追加する対象。null の場合は追加しません。
	 * @param returnList 委譲先を追加する対象。null の場合は追加しません。
	 * @return 関数。
	 */
	public static <C, R, E extends Exception> Fn<C, R, E> recode(
			final Fn<? super C, ? extends R, ? extends E> fn,
			final String name,
			final Collection<String> nameList,
			final Collection<? super C> contextList,
			final Collection<? super R> returnList) {
		return new RecodeFn<C, R, E>(fn, name, nameList, contextList, returnList);
	}
	
	/**
	 * 通過情報を{@link Collection}に記録するテスト用の関数を生成します。
	 * @param <C>
	 * @param <R>
	 * @param <E>
	 * @param fn 委譲先。null の場合は委譲しません。
	 * @param name nameListに追加する名前。
	 * @param nameList name を追加する対象。null の場合は追加しません。
	 * @return 関数。
	 */
	public static <C, R, E extends Exception> Fn<C, R, E> recode(
			final Fn<? super C, ? extends R, ? extends E> fn,
			final String name,
			final Collection<String> nameList) {
		return new RecodeFn<C, R, E>(fn, name, nameList, null, null);
	}
	
	/**
	 * 通過情報を{@link Collection}に記録するテスト用の関数を生成します。
	 * @param <C>
	 * @param <R>
	 * @param <E>
	 * @param fn 委譲先。null の場合は委譲しません。
	 * @param contextList コンテキスト役を追加する対象。null の場合は追加しません。
	 * @param returnList 委譲先を追加する対象。null の場合は追加しません。
	 * @return 関数。
	 */
	public static <C, R, E extends Exception> Fn<C, R, E> recode(
			final Fn<? super C, ? extends R, ? extends E> fn,
			final Collection<? super C> contextList,
			final Collection<? super R> returnList) {
		return new RecodeFn<C, R, E>(fn, null, null, contextList, returnList);
	}
	
	/**
	 * 委譲先を順次実行し、最後に実行した結果を返す関数を生成します。
	 * @param <C>
	 * @param <R>
	 * @param <E>
	 * @param iterable 委譲先の一覧。
	 * @return 関数。
	 */
	public static <C, R, E extends Exception> Fn<C, R, E> seq(
			final Iterable<? extends Fn<? super C, ? extends R, ? extends E>> iterable) {
		return new SeqFn<C, R, E>(iterable);
	}
	
	/**
	 * {@link Map}を使用して、キーに対する値を返す関数を生成します。
	 * 引数に対する値が null の場合はデフォルトの処理に委譲します。
	 * @param <C>
	 * @param <R>
	 * @param <E>
	 * @param map マップ。
	 * @param defaultFn 引数に対応する値が null の場合の委譲先。
	 * @return 関数。
	 */
	public static <C, R, E extends Exception> SwitchFn<C, R, E> switching(
			final Map<? super C, ? extends R> map,
			final Fn<? super C, ? extends R, ? extends E> defaultFn) {
		return new SwitchFn<C, R, E>(map, defaultFn);
	}
	
	/**
	 * {@link Map}を使用して、キーに対する値を返す関数を生成します。
	 * 引数に対する値が null の場合はデフォルトの処理に委譲します。
	 * @param <C>
	 * @param <R>
	 * @param map マップ。
	 * @param defaultValue 引数に対応する値が null の場合の値。
	 * @return 関数。
	 */
	public static <C, R> SwitchFn<C, R, RuntimeException> switching(
			final Map<? super C, ? extends R> map,
			final R defaultValue) {
		return switching(map, FnUtils.<C, R>fix(defaultValue));
	}
	
	/**
	 * {@link Map}を使用して、キーに対する値を返す関数を生成します。
	 * 引数に対する値が null の場合はデフォルトの処理に委譲します。
	 * 引数に対応する値が null の場合はnullを返します。
	 * @param <C>
	 * @param <R>
	 * @param map マップ。
	 * @return 関数。
	 */
	public static <C, R> SwitchFn<C, R, RuntimeException> switching(
			final Map<? super C, ? extends R> map) {
		return switching(map, FnUtils.<C, R>fix(null));
	}
	
	/**
	 * 例外を投げる関数を生成します。
	 * @param <C>
	 * @param <R>
	 * @param <E>
	 * @param exception 投げる例外。
	 * @return 関数。
	 */
	public static <C, R, E extends Exception> Fn<C, R, E> throwing(final E exception) {
		return new ThrowFn<C, R, E>(exception);
	}
	
	/**
	 * Java の try-catch-finally のそれぞれのブロックで委譲する関数を生成します。
	 * @param <C>
	 * @param <R>
	 * @param <E>
	 * @param fn try ブロックにおける委譲先。
	 * @param reportFn catch ブロックにおける委譲先。
	 * @param finallyFn finally ブロックにおける委譲先。
	 * @return 関数。
	 */
	public static <C, R, E extends Exception> Fn<C, R, E> trying(
			final Fn<? super C, ? extends R, ? extends Exception> fn,
			final Fn<? super Exception, ? extends R, ? extends E> reportFn,
			final Fn<? super C, ?, ? extends E> finallyFn) {
		return new TryFn<C, R, E>(fn, reportFn, finallyFn);
	}
	
	/**
	 * 委譲するだけの関数を生成します。
	 * @param <C>
	 * @param <R>
	 * @param <E>
	 * @param fn 委譲先。
	 * @return 関数。
	 */
	public static <C, R, E extends Exception> Delegator<C, R, E> delegate(
			final Fn<? super C, ? extends R, ? extends E> fn) {
		return new Delegator<C, R, E>(fn);
	}
	
	/**
	 * 委譲するだけの関数を生成します。
	 * @param <C>
	 * @param <R>
	 * @param <E>
	 * @return 委譲先。
	 */
	public static <C, R, E extends Exception> Delegator<C, R, E> delegate() {
		return new Delegator<C, R, E>(null);
	}
	
	/**
	 * 引数がnullの場合は委譲をスキップしてnullを返す関数を生成します。
	 * @param <C>
	 * @param <R>
	 * @param <E>
	 * @param fn 委譲先。
	 * @return 委譲先。
	 */
	public static <C, R, E extends Exception> Fn<C, R, E> maybe(
			final Fn<? super C, ? extends R, ? extends E> fn) {
		return new MaybeFn<C, R, E>(fn);
	}
}
