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

import java.util.Locale;

import woolpack.fn.Fn;
import woolpack.fn.FnUtils;
import woolpack.fn.SwitchFn;

/**
 * 多雑な部品のユーティリティです。
 * 型推論で表記を簡略するためのスタティックメソッドを含みます。
 * 
 * @author nakamura
 *
 */
public final class MiscUtils {
	private MiscUtils() {
	}
	
	/**
	 * 委譲先の実行時間を測定する関数を生成します。
	 * @param <C>
	 * @param <R>
	 * @param <E>
	 * @param fn 委譲先。
	 * @param reportFn 結果の送付先。
	 * @return 関数。
	 */
	public static <C, R, E extends Exception> Fn<C, R, E> lapTime(
			final Fn<? super C, ? extends R, ? extends E> fn,
			final Fn<? super Long, ?, ? extends E> reportFn) {
		return new LapTimeFn<C, R, E>(fn, reportFn);
	}
	
	/**
	 * 複数のスレッドで同時に実行することができない{@link Fn}を、
	 * 複数生成することにより並行に実行するようにみせる関数を生成します。
	 * @param <C>
	 * @param <R>
	 * @param <E>
	 * @param <E1>
	 * @param factory 複数のスレッドで同時に実行できない{@link Fn}のファクトリ。
	 * @param length 並行実行数。
	 * @return 関数。
	 * @throws IllegalArgumentException length がゼロ以下の場合。
	 * @throws Exception {@link Fn}の生成に失敗した場合。
	 */
	public static <C, R, E extends Exception, E1 extends Exception> Fn<C, R, E> loadBalancer(
			final Fn<Object, ? extends Fn<? super C, ? extends R, ? extends E>, ? extends E1> factory,
			final int length) throws Exception {
		return new LoadBalancer<C, R, E, E1>(factory, length);
	}
	
	/**
	 * {@link Fn}に委譲する{@link Runnable}を生成します。
	 * @param <C>
	 * @param fn 委譲先。
	 * @param c 委譲先に渡す値。
	 * @return ランナブル。
	 */
	public static <C> FnRunnable<C> fnRunnable(final Fn<? super C, ?, ? extends RuntimeException> fn, final C c) {
		return new FnRunnable<C>(fn, c);
	}
	
	/**
	 * {@link Runnable}に委譲する関数を生成します。
	 * {@link Fn#exec(Object)}は null を返します。
	 * @param <R>
	 * @param runnable ランナブル。
	 * @return 関数。
	 */
	public static <R> Fn<Object, R, RuntimeException> runnableFn(final Runnable runnable) {
		return new RunnableFn<R, RuntimeException>(runnable);
	}
	
	/**
	 * スリープする関数を生成します。
	 * @param <C>
	 * @param <R>
	 * @param sleepMillis スリープ時間。
	 * @return 関数。
	 */
	public static <C, R> Fn<C, R, InterruptedException> sleep(final long sleepMillis) {
		return new SleepFn<C, R>(sleepMillis);
	}
	
	/**
	 * 一覧から指定されたロケールに最も近いロケールをキーとして値を返す関数を生成します。
	 * @param <R>
	 * @param <E>
	 * @param switching ロケールをキーとする{@link SwitchFn}。
	 * @return 関数。
	 */
	public static <R, E extends Exception> Fn<Locale, R, E> switchNearLocale(final SwitchFn<Locale, ? extends R, ? extends E> switching) {
		return new SwitchNearLocale<R, E>(switching);
	}
	
	/**
	 * {@link ThreadLocal#get()}を返す関数を生成します。
	 * @param <R>
	 * @param threadLocal スレッドローカル。
	 * @return 関数。
	 */
	public static <R> Fn<Object, R, RuntimeException> threadLocal(final ThreadLocal<? extends R> threadLocal) {
		return new ThreadLocalGetter<R, RuntimeException>(threadLocal);
	}
	
	/**
	 * {@link Locale}を元に id に接尾辞を付加して成功するまで委譲する関数を生成します。
	 * このクラスは LSP(The Liskov Substitution Principle) を満たしません。
	 * @param <R>
	 * @param <E>
	 * @param fn 委譲先。　
	 * @param threadLocal 現在のスレッドの{@link Locale}を保持するスレッドローカル。
	 * @param errorFn エラー発生時の委譲先。
	 * @param finalFn 全て成功しなかった場合の委譲先。
	 * @return 関数。
	 */
	public static <R, E extends Exception> Fn<String, R, E> tryLocales(
			final Fn<? super String, ? extends R, ? extends Exception> fn,
			final ThreadLocal<Locale> threadLocal,
			final Fn<? super Exception, ?, ? extends E> errorFn,
			final Fn<? super Exception, ? extends R, ? extends E> finalFn) {
		return new TryLocales<R, E>(fn, threadLocal, errorFn, finalFn);
	}

	/**
	 * {@link Locale}を元に id に接尾辞を付加して成功するまで委譲する関数を生成します。
	 * このクラスは LSP(The Liskov Substitution Principle) を満たしません。
	 * 全て成功しなかった場合は{@link IllegalStateException}を投げます。
	 * @param <R>
	 * @param fn 委譲先。　
	 * @param threadLocal 現在のスレッドの{@link Locale}を保持するスレッドローカル。
	 * @return 関数。
	 */
	public static <R> Fn<String, R, IllegalStateException> tryLocales(
			final Fn<? super String, ? extends R, ? extends Exception> fn,
			final ThreadLocal<Locale> threadLocal) {
		return MiscUtils.<R, IllegalStateException>tryLocales(
				fn,
				threadLocal,
				FnUtils.<Exception, R, IllegalStateException>fixThrows(null),
				new Fn<Exception, R, IllegalStateException>() {
					public R exec(final Exception c) throws IllegalStateException {
						throw new IllegalStateException(c);
					}
				});
	}
	
	/**
	 * 成功するまでリトライする関数を生成します。
	 * @param <C>
	 * @param <R>
	 * @param <E>
	 * @param fn 委譲先。
	 * @param count リトライ回数。
	 * @param errorFn エラー発生時の委譲先。
	 * @param finalFn 全て成功しなかった場合の委譲先。
	 * @return 関数。
	 */
	public static <C, R, E extends Exception> Fn<C, R, E> retry(
			final Fn<? super C, ? extends R, ? extends Exception> fn,
			final int count,
			final Fn<? super Exception, ?, ? extends E> errorFn,
			final Fn<? super Exception, ? extends R, ? extends E> finalFn) {
		return new RetryFn<C, R, E>(fn, count, errorFn, finalFn);
	}
	
	/**
	 * 成功するまでリトライする関数を生成します。
	 * 全て成功しなかった場合は{@link IllegalStateException}を投げます。
	 * @param <C>
	 * @param <R>
	 * @param fn 委譲先。
	 * @param count リトライ回数。
	 * @return 関数。
	 */
	public static <C, R> Fn<C, R, IllegalStateException> retry(
			final Fn<? super C, ? extends R, ? extends Exception> fn,
			final int count) {
		return new RetryFn<C, R, IllegalStateException>(
				fn,
				count,
				FnUtils.<Exception, R, IllegalStateException>fixThrows(null),
				new Fn<Exception, R, IllegalStateException>() {
					public R exec(final Exception c) throws IllegalStateException {
						throw new IllegalStateException(c);
					}
				});
	}
}
