/*
 * Copyright (c) 2011-2017 Pivotal Software Inc, All Rights Reserved.
 *
 * 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
 *
 *       https://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 reactor.util.function;

import java.util.Collection;
import java.util.function.Function;

/**
 * A {@literal Tuples} is an immutable {@link Collection} of objects, each of which can be of an arbitrary type.
 *
 * @author Jon Brisbin
 * @author Stephane Maldini
 */
@SuppressWarnings({"rawtypes"})
public abstract class Tuples implements Function {

	/**
	 * Create a {@link Tuple2} with the given array if it is small
	 * enough to fit inside a {@link Tuple2} to {@link Tuple8}.
	 *
	 * @param list the content of the Tuple (size 1 to 8)
	 * @return The new {@link Tuple2}.
	 * @throws IllegalArgumentException if the array is not of length 1-8
	 */
	public static Tuple2 fromArray(Object[] list) {
		//noinspection ConstantConditions
		if (list == null || list.length < 2) {
			throw new IllegalArgumentException("null or too small array, need between 2 and 8 values");
		}

		switch (list.length){
			case 2:
				return of(list[0], list[1]);
			case 3:
				return of(list[0], list[1], list[2]);
			case 4:
				return of(list[0], list[1], list[2], list[3]);
			case 5:
				return of(list[0], list[1], list[2], list[3], list[4]);
			case 6:
				return of(list[0], list[1], list[2], list[3], list[4], list[5]);
			case 7:
				return of(list[0], list[1], list[2], list[3], list[4], list[5], list[6]);
			case 8:
				return of(list[0], list[1], list[2], list[3], list[4], list[5], list[6], list[7]);
		}
		throw new IllegalArgumentException("too many arguments ("+ list.length + "), need between 2 and 8 values");
	}

	/**
	 * Create a {@link Tuple2} with the given objects.
	 *
	 * @param t1   The first value in the tuple. Not null.
	 * @param t2   The second value in the tuple. Not null.
	 * @param <T1> The type of the first value.
	 * @param <T2> The type of the second value.
	 * @return The new {@link Tuple2}.
	 */
	public static <T1, T2> Tuple2<T1, T2> of(T1 t1, T2 t2) {
		return new Tuple2<>(t1, t2);
	}

	/**
	 * Create a {@link Tuple3} with the given objects.
	 *
	 * @param t1   The first value in the tuple. Not null.
	 * @param t2   The second value in the tuple. Not null.
	 * @param t3   The third value in the tuple. Not null.
	 * @param <T1> The type of the first value.
	 * @param <T2> The type of the second value.
	 * @param <T3> The type of the third value.
	 * @return The new {@link Tuple3}.
	 */
	public static <T1, T2, T3> Tuple3<T1, T2, T3> of(T1 t1, T2 t2, T3 t3) {
		return new Tuple3<>(t1, t2, t3);
	}

	/**
	 * Create a {@link Tuple4} with the given objects.
	 *
	 * @param t1   The first value in the tuple. Not null.
	 * @param t2   The second value in the tuple. Not null.
	 * @param t3   The third value in the tuple. Not null.
	 * @param t4   The fourth value in the tuple. Not null.
	 * @param <T1> The type of the first value.
	 * @param <T2> The type of the second value.
	 * @param <T3> The type of the third value.
	 * @param <T4> The type of the fourth value.
	 * @return The new {@link Tuple4}.
	 */
	public static <T1, T2, T3, T4> Tuple4<T1, T2, T3, T4> of(T1 t1, T2 t2, T3 t3, T4 t4) {
		return new Tuple4<>(t1, t2, t3, t4);
	}

	/**
	 * Create a {@link Tuple5} with the given objects.
	 *
	 * @param t1   The first value in the tuple. Not null.
	 * @param t2   The second value in the tuple. Not null.
	 * @param t3   The third value in the tuple. Not null.
	 * @param t4   The fourth value in the tuple. Not null.
	 * @param t5   The fifth value in the tuple. Not null.
	 * @param <T1> The type of the first value.
	 * @param <T2> The type of the second value.
	 * @param <T3> The type of the third value.
	 * @param <T4> The type of the fourth value.
	 * @param <T5> The type of the fifth value.
	 * @return The new {@link Tuple5}.
	 */
	public static <T1, T2, T3, T4, T5> Tuple5<T1, T2, T3, T4, T5> of(
			T1 t1,
			T2 t2,
			T3 t3,
			T4 t4,
			T5 t5) {
		return new Tuple5<>(t1, t2, t3, t4, t5);
	}

	/**
	 * Create a {@link Tuple6} with the given objects.
	 *
	 * @param t1   The first value in the tuple. Not null.
	 * @param t2   The second value in the tuple. Not null.
	 * @param t3   The third value in the tuple. Not null.
	 * @param t4   The fourth value in the tuple. Not null.
	 * @param t5   The fifth value in the tuple. Not null.
	 * @param t6   The sixth value in the tuple. Not null.
	 * @param <T1> The type of the first value.
	 * @param <T2> The type of the second value.
	 * @param <T3> The type of the third value.
	 * @param <T4> The type of the fourth value.
	 * @param <T5> The type of the fifth value.
	 * @param <T6> The type of the sixth value.
	 * @return The new {@link Tuple6}.
	 */
	public static <T1, T2, T3, T4, T5, T6> Tuple6<T1, T2, T3, T4, T5, T6> of(
			T1 t1,
			T2 t2,
			T3 t3,
			T4 t4,
			T5 t5,
			T6 t6) {
		return new Tuple6<>(t1, t2, t3, t4, t5, t6);
	}

	/**
	 * Create a {@link Tuple7} with the given objects.
	 *
	 * @param t1   The first value in the tuple. Not null.
	 * @param t2   The second value in the tuple. Not null.
	 * @param t3   The third value in the tuple. Not null.
	 * @param t4   The fourth value in the tuple. Not null.
	 * @param t5   The fifth value in the tuple. Not null.
	 * @param t6   The sixth value in the tuple. Not null.
	 * @param t7   The seventh value in the tuple. Not null.
	 * @param <T1> The type of the first value.
	 * @param <T2> The type of the second value.
	 * @param <T3> The type of the third value.
	 * @param <T4> The type of the fourth value.
	 * @param <T5> The type of the fifth value.
	 * @param <T6> The type of the sixth value.
	 * @param <T7> The type of the seventh value.
	 * @return The new {@link Tuple7}.
	 */
	public static <T1, T2, T3, T4, T5, T6, T7> Tuple7<T1, T2, T3, T4, T5, T6, T7> of(
			T1 t1,
			T2 t2,
			T3 t3,
			T4 t4,
			T5 t5,
			T6 t6,
			T7 t7) {
		return new Tuple7<>(t1, t2, t3, t4, t5, t6, t7);
	}

	/**
	 * Create a {@link Tuple8} with the given objects.
	 *
	 * @param t1   The first value in the tuple. Not Null.
	 * @param t2   The second value in the tuple.Not Null.
	 * @param t3   The third value in the tuple. Not Null.
	 * @param t4   The fourth value in the tuple. Not Null.
	 * @param t5   The fifth value in the tuple. Not Null.
	 * @param t6   The sixth value in the tuple. Not Null.
	 * @param t7   The seventh value in the tuple. Not Null.
	 * @param t8   The eighth value in the tuple. Not Null.
	 * @param <T1> The type of the first value.
	 * @param <T2> The type of the second value.
	 * @param <T3> The type of the third value.
	 * @param <T4> The type of the fourth value.
	 * @param <T5> The type of the fifth value.
	 * @param <T6> The type of the sixth value.
	 * @param <T7> The type of the seventh value.
	 * @param <T8> The type of the eighth value.
	 * @return The new {@link Tuple8}.
	 */
	public static <T1, T2, T3, T4, T5, T6, T7, T8> Tuple8<T1, T2, T3, T4, T5, T6, T7, T8> of(
			T1 t1,
			T2 t2,
			T3 t3,
			T4 t4,
			T5 t5,
			T6 t6,
			T7 t7,
			T8 t8) {
		return new Tuple8<>(t1, t2, t3, t4, t5, t6, t7, t8);
	}

	/**
	 * A converting function from Object array to {@link Tuples}
	 *
	 * @return The unchecked conversion function to {@link Tuples}.
	 */
	@SuppressWarnings("unchecked")
	public static Function<Object[], Tuple2> fnAny() {
		return empty;
	}

	/**
	 * A converting function from Object array to {@link Tuples} to R.
	 *
	 * @param <R> The type of the return value.
	 * @param delegate the function to delegate to
	 *
	 * @return The unchecked conversion function to R.
	 */
	public static <R> Function<Object[], R> fnAny(final Function<Tuple2, R> delegate) {
		return objects -> delegate.apply(Tuples.fnAny().apply(objects));
	}

	/**
	 * A converting function from Object array to {@link Tuple2}
	 *
	 * @param <T1> The type of the first value.
	 * @param <T2> The type of the second value.
	 *
	 * @return The unchecked conversion function to {@link Tuple2}.
	 */
	@SuppressWarnings("unchecked")
	public static <T1, T2> Function<Object[], Tuple2<T1, T2>> fn2() {
		return empty;
	}

	/**
	 * A converting function from Object array to {@link Tuple3}
	 *
	 * @param <T1> The type of the first value.
	 * @param <T2> The type of the second value.
	 * @param <T3> The type of the third value.
	 *
	 * @return The unchecked conversion function to {@link Tuple3}.
	 */
	@SuppressWarnings("unchecked")
	public static <T1, T2, T3> Function<Object[], Tuple3<T1, T2, T3>> fn3() {
		return empty;
	}

	/**
	 * A converting function from Object array to {@link Tuple3} to R.
	 *
	 * @param <T1> The type of the first value.
	 * @param <T2> The type of the second value.
	 * @param <T3> The type of the third value.
	 * @param <R> The type of the return value.
     * @param delegate the function to delegate to
	 *
	 * @return The unchecked conversion function to R.
	 */
	public static <T1, T2, T3, R> Function<Object[], R> fn3(final Function<Tuple3<T1, T2, T3>, R> delegate) {
		return objects -> delegate.apply(Tuples.<T1, T2, T3>fn3().apply(objects));
	}

	/**
	 * A converting function from Object array to {@link Tuple4}
	 *
	 * @param <T1> The type of the first value.
	 * @param <T2> The type of the second value.
	 * @param <T3> The type of the third value.
	 * @param <T4> The type of the fourth value.
	 *
	 * @return The unchecked conversion function to {@link Tuple4}.
	 */
	@SuppressWarnings("unchecked")
	public static <T1, T2, T3, T4> Function<Object[], Tuple4<T1, T2, T3, T4>> fn4() {
		return empty;
	}

	/**
	 * A converting function from Object array to {@link Tuple4} to R.
	 *
	 * @param <T1> The type of the first value.
	 * @param <T2> The type of the second value.
	 * @param <T3> The type of the third value.
	 * @param <T4> The type of the fourth value.
	 * @param <R> The type of the return value.
     * @param delegate the function to delegate to
	 *
	 * @return The unchecked conversion function to R.
	 */
	public static <T1, T2, T3, T4, R> Function<Object[], R> fn4(final Function<Tuple4<T1, T2, T3, T4>, R> delegate) {
		return objects -> delegate.apply(Tuples.<T1, T2, T3, T4>fn4().apply(objects));
	}

	/**
	 * A converting function from Object array to {@link Tuple5}
	 *
	 * @param <T1> The type of the first value.
	 * @param <T2> The type of the second value.
	 * @param <T3> The type of the third value.
	 * @param <T4> The type of the fourth value.
	 * @param <T5> The type of the fifth value.
	 *
	 * @return The unchecked conversion function to {@link Tuple5}.
	 */
	@SuppressWarnings("unchecked")
	public static <T1, T2, T3, T4, T5> Function<Object[], Tuple5<T1, T2, T3, T4, T5>> fn5() {
		return empty;
	}

	/**
	 * A converting function from Object array to {@link Tuple4} to R.
	 *
	 * @param <T1> The type of the first value.
	 * @param <T2> The type of the second value.
	 * @param <T3> The type of the third value.
	 * @param <T4> The type of the fourth value.
	 * @param <T5> The type of the fifth value.
	 * @param <R> The type of the return value.
     * @param delegate the function to delegate to
	 *
	 * @return The unchecked conversion function to R.
	 */
	public static <T1, T2, T3, T4, T5, R> Function<Object[], R> fn5(final Function<Tuple5<T1, T2, T3, T4, T5>, R> delegate) {
		return objects -> delegate.apply(Tuples.<T1, T2, T3, T4, T5>fn5().apply(objects));
	}

	/**
	 * A converting function from Object array to {@link Tuple6}
	 *
	 * @param <T1> The type of the first value.
	 * @param <T2> The type of the second value.
	 * @param <T3> The type of the third value.
	 * @param <T4> The type of the fourth value.
	 * @param <T5> The type of the fifth value.
	 * @param <T6> The type of the sixth value.
	 *
	 * @return The unchecked conversion function to {@link Tuple6}.
	 */
	@SuppressWarnings("unchecked")
	public static <T1, T2, T3, T4, T5, T6> Function<Object[], Tuple6<T1, T2, T3, T4, T5, T6>> fn6() {
		return empty;
	}

	/**
	 * A converting function from Object array to {@link Tuple6} to R.
	 *
	 * @param <T1> The type of the first value.
	 * @param <T2> The type of the second value.
	 * @param <T3> The type of the third value.
	 * @param <T4> The type of the fourth value.
	 * @param <T5> The type of the fifth value.
	 * @param <T6> The type of the sixth value.
	 * @param <R> The type of the return value.
     * @param delegate the function to delegate to
	 *
	 * @return The unchecked conversion function to R.
	 */
	public static <T1, T2, T3, T4, T5, T6, R> Function<Object[], R> fn6(final Function<Tuple6<T1, T2, T3, T4, T5, T6>, R> delegate) {
		return objects -> delegate.apply(Tuples.<T1, T2, T3, T4, T5, T6>fn6().apply(objects));
	}

	/**
	 * A converting function from Object array to {@link Tuple7}
	 *
	 * @param <T1> The type of the first value.
	 * @param <T2> The type of the second value.
	 * @param <T3> The type of the third value.
	 * @param <T4> The type of the fourth value.
	 * @param <T5> The type of the fifth value.
	 * @param <T6> The type of the sixth value.
	 * @param <T7> The type of the seventh value.
	 *
	 * @return The unchecked conversion function to {@link Tuple7}.
	 */
	@SuppressWarnings("unchecked")
	public static <T1, T2, T3, T4, T5, T6, T7> Function<Object[], Tuple7<T1, T2, T3, T4, T5, T6, T7>> fn7() {
		return empty;
	}

	/**
	 * A converting function from Object array to {@link Tuple7} to R.
	 *
	 * @param <T1> The type of the first value.
	 * @param <T2> The type of the second value.
	 * @param <T3> The type of the third value.
	 * @param <T4> The type of the fourth value.
	 * @param <T5> The type of the fifth value.
	 * @param <T6> The type of the sixth value.
	 * @param <T7> The type of the seventh value.
	 * @param <R> The type of the return value.
     * @param delegate the function to delegate to
	 *
	 * @return The unchecked conversion function to R.
	 */
	public static <T1, T2, T3, T4, T5, T6, T7, R> Function<Object[], R> fn7(final Function<Tuple7<T1, T2, T3, T4, T5, T6, T7>, R> delegate) {
		return objects -> delegate.apply(Tuples.<T1, T2, T3, T4, T5, T6, T7>fn7().apply(objects));
	}

	/**
	 * A converting function from Object array to {@link Tuple8}
	 *
	 * @param <T1> The type of the first value.
	 * @param <T2> The type of the second value.
	 * @param <T3> The type of the third value.
	 * @param <T4> The type of the fourth value.
	 * @param <T5> The type of the fifth value.
	 * @param <T6> The type of the sixth value.
	 * @param <T7> The type of the seventh value.
	 * @param <T8> The type of the eighth value.
	 *
	 * @return The unchecked conversion function to {@link Tuple8}.
	 */
	@SuppressWarnings("unchecked")
	public static <T1, T2, T3, T4, T5, T6, T7, T8> Function<Object[], Tuple8<T1, T2, T3, T4, T5, T6, T7, T8>> fn8() {
		return empty;
	}

	/**
	 * A converting function from Object array to {@link Tuple8}
	 *
	 * @param <T1> The type of the first value.
	 * @param <T2> The type of the second value.
	 * @param <T3> The type of the third value.
	 * @param <T4> The type of the fourth value.
	 * @param <T5> The type of the fifth value.
	 * @param <T6> The type of the sixth value.
	 * @param <T7> The type of the seventh value.
     * @param <T8> The type of the eighth value.
	 * @param <R> The type of the return value.
     * @param delegate the function to delegate to
	 *
	 * @return The unchecked conversion function to {@link Tuple8}.
	 */
	public static <T1, T2, T3, T4, T5, T6, T7, T8, R> Function<Object[], R> fn8(final Function<Tuple8<T1, T2, T3, T4, T5, T6, T7, T8>, R> delegate) {
		return objects -> delegate.apply(Tuples.<T1, T2, T3, T4, T5, T6, T7, T8>fn8().apply(objects));
	}

	@Override
	public Tuple2 apply(Object o) {
		return fromArray((Object[])o);
	}

	/**
	 * Prepare a string representation of the values suitable for a Tuple of any
	 * size by accepting an array of elements. This builds a {@link StringBuilder}
	 * containing the String representation of each object, comma separated. It manages
	 * nulls as well by putting an empty string and the comma.
	 *
	 * @param values the values of the tuple to represent
	 * @return a {@link StringBuilder} initialized with the string representation of the
	 * values in the Tuple.
	 */
	static StringBuilder tupleStringRepresentation(Object... values) {
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < values.length; i++) {
			Object t = values[i];
			if (i != 0) {
				sb.append(',');
			}
			if (t != null) {
				sb.append(t);
			}
		}
		return sb;
	}

	static final Tuples   empty            = new Tuples(){};

	Tuples(){}
}


/*
 * Copyright (c) 2011-2017 Pivotal Software Inc, All Rights Reserved.
 *
 * 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
 *
 *       https://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 reactor.core.scheduler;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;

import reactor.core.Disposable;
import reactor.core.Exceptions;
import reactor.core.Scannable;
import reactor.util.Logger;
import reactor.util.Loggers;
import reactor.util.Metrics;
import reactor.util.annotation.Nullable;

import static reactor.core.Exceptions.unwrap;

/**
 * {@link Schedulers} provides various {@link Scheduler} factories useable by {@link
 * reactor.core.publisher.Flux#publishOn publishOn} or {@link reactor.core.publisher.Mono#subscribeOn
 * subscribeOn} :
 *

 *

{@link #fromExecutorService(ExecutorService)}}.

{@link #newParallel}
 * : Optimized for fast {@link Runnable} executions

{@link #single} : Optimized
 * for low-latency {@link Runnable} executions

{@link #immediate}.

 *

 * Factories prefixed with {@code new} return a new instance of their flavor of {@link Scheduler},
 * while other factories like {@link #elastic()} return a shared instance, that is the one
 * used by operators requiring that flavor as their default Scheduler.
 *
 * @author Stephane Maldini
 */
public abstract class Schedulers {

	/**
	 * Default pool size, initialized by system property `reactor.schedulers.defaultPoolSize`
	 * and falls back to the number of processors available to the runtime on init.
	 *
	 * @see Runtime#availableProcessors()
	 */
	public static final int DEFAULT_POOL_SIZE =
			Optional.ofNullable(System.getProperty("reactor.schedulers.defaultPoolSize"))
					.map(Integer::parseInt)
					.orElseGet(() -> Runtime.getRuntime().availableProcessors());

	static volatile BiConsumer<Thread, ? super Throwable> onHandleErrorHook;

	/**
	 * Create a {@link Scheduler} which uses a backing {@link Executor} to schedule
	 * Runnables for async operators.
	 *
	 *
Tasks scheduled with workers of this Scheduler are not guaranteed to run in FIFO
	 * order and strictly non-concurrently.
	 * If FIFO order is desired, use trampoline parameter of {@link Schedulers#fromExecutor(Executor, boolean)}
	 *
	 * @param executor an {@link Executor}
	 *
	 * @return a new {@link Scheduler}
	 */
	public static Scheduler fromExecutor(Executor executor) {
		return fromExecutor(executor, false);
	}

	/**
	 * Create a {@link Scheduler} which uses a backing {@link Executor} to schedule
	 * Runnables for async operators.
	 *
	 * Trampolining here means tasks submitted in a burst are queued by the Worker itself,
	 * which acts as a sole task from the perspective of the {@link ExecutorService},
	 * so no reordering (but also no threading).
	 *
	 * @param executor an {@link Executor}
	 * @param trampoline set to false if this {@link Scheduler} is used by "operators"
	 * that already conflate {@link Runnable} executions (publishOn, subscribeOn...)
	 *
	 * @return a new {@link Scheduler}
	 */
	public static Scheduler fromExecutor(Executor executor, boolean trampoline) {
		if(!trampoline && executor instanceof ExecutorService){
			return fromExecutorService((ExecutorService) executor);
		}
		return new ExecutorScheduler(executor, trampoline);
	}

	/**
	 * Create a {@link Scheduler} which uses a backing {@link ExecutorService} to schedule
	 * Runnables for async operators.
	 *
	 * @param executorService an {@link ExecutorService}
	 *
	 * @return a new {@link Scheduler}
	 */
	public static Scheduler fromExecutorService(ExecutorService executorService) {
		return new DelegateServiceScheduler(executorService);
	}

	/**
	 * {@link Scheduler} that dynamically creates ExecutorService-based Workers and caches
	 * the thread pools, reusing them once the Workers have been shut down.
	 *

	 * The maximum number of created thread pools is unbounded.
	 *

	 * The default time-to-live for unused thread pools is 60 seconds, use the appropriate
	 * factory to set a different value.
	 *

	 * This scheduler is not restartable.
	 *
	 * @return default instance of a {@link Scheduler} that hosts a fixed pool of single-threaded
	 * ExecutorService-based workers and is suited for parallel work
	 */
	public static Scheduler elastic() {
		return cache(CACHED_ELASTIC, ELASTIC, ELASTIC_SUPPLIER);
	}

	/**
	 * {@link Scheduler} that hosts a fixed pool of single-threaded ExecutorService-based
	 * workers and is suited for parallel work.
	 *
	 * @return default instance of a {@link Scheduler} that hosts a fixed pool of single-threaded
	 * ExecutorService-based workers
	 */
	public static Scheduler parallel() {
		return cache(CACHED_PARALLEL, PARALLEL, PARALLEL_SUPPLIER);
	}

	/**
	 * Executes tasks on the caller's thread immediately.
	 *
	 * @return a reusable {@link Scheduler}
	 */
	public static Scheduler immediate() {
		return ImmediateScheduler.instance();
	}

	/**
	 * {@link Scheduler} that dynamically creates ExecutorService-based Workers and caches
	 * the thread pools, reusing them once the Workers have been shut down.
	 *

	 * The maximum number of created thread pools is unbounded.
	 *

	 * The default time-to-live for unused thread pools is 60 seconds, use the appropriate
	 * factory to set a different value.
	 *

	 * This scheduler is not restartable.
	 *
	 * @param name Thread prefix
	 *
	 * @return a new {@link Scheduler} that hosts a fixed pool of single-threaded
	 * ExecutorService-based workers and is suited for parallel work
	 */
	public static Scheduler newElastic(String name) {
		return newElastic(name, ElasticScheduler.DEFAULT_TTL_SECONDS);
	}

	/**
	 * {@link Scheduler} that dynamically creates ExecutorService-based Workers and caches
	 * the thread pools, reusing them once the Workers have been shut down.
	 *

	 * The maximum number of created thread pools is unbounded.
	 *

	 * This scheduler is not restartable.
	 *
	 * @param name Thread prefix
	 * @param ttlSeconds Time-to-live for an idle {@link reactor.core.scheduler.Scheduler.Worker}
	 *
	 * @return a new {@link Scheduler} that hosts a fixed pool of single-threaded
	 * ExecutorService-based workers and is suited for parallel work
	 */
	public static Scheduler newElastic(String name, int ttlSeconds) {
		return newElastic(name, ttlSeconds, false);
	}

	/**
	 * {@link Scheduler} that dynamically creates ExecutorService-based Workers and caches
	 * the thread pools, reusing them once the Workers have been shut down.
	 *

	 * The maximum number of created thread pools is unbounded.
	 *

	 * This scheduler is not restartable.
	 *
	 * @param name Thread prefix
	 * @param ttlSeconds Time-to-live for an idle {@link reactor.core.scheduler.Scheduler.Worker}
	 * @param daemon false if the {@link Scheduler} requires an explicit {@link
	 * Scheduler#dispose()} to exit the VM.
	 *
	 * @return a new {@link Scheduler} that hosts a fixed pool of single-threaded
	 * ExecutorService-based workers and is suited for parallel work
	 */
	public static Scheduler newElastic(String name, int ttlSeconds, boolean daemon) {
		return newElastic(ttlSeconds,
				new ReactorThreadFactory(name, ElasticScheduler.COUNTER, daemon, false,
						Schedulers::defaultUncaughtException));
	}

	/**
	 * {@link Scheduler} that dynamically creates ExecutorService-based Workers and caches
	 * the thread pools, reusing them once the Workers have been shut down.
	 *

	 * The maximum number of created thread pools is unbounded.
	 *

	 * This scheduler is not restartable.
	 *
	 * @param ttlSeconds Time-to-live for an idle {@link reactor.core.scheduler.Scheduler.Worker}
	 * @param threadFactory a {@link ThreadFactory} to use each thread initialization
	 *
	 * @return a new {@link Scheduler} that dynamically creates ExecutorService-based
	 * Workers and caches the thread pools, reusing them once the Workers have been shut
	 * down.
	 */
	public static Scheduler newElastic(int ttlSeconds, ThreadFactory threadFactory) {
		return factory.newElastic(ttlSeconds, threadFactory);
	}

	/**
	 * {@link Scheduler} that hosts a fixed pool of single-threaded ExecutorService-based
	 * workers and is suited for parallel work. This type of {@link Scheduler} detects and
	 * rejects usage of blocking Reactor APIs.
	 *
	 * @param name Thread prefix
	 *
	 * @return a new {@link Scheduler} that hosts a fixed pool of single-threaded
	 * ExecutorService-based workers and is suited for parallel work
	 */
	public static Scheduler newParallel(String name) {
		return newParallel(name, DEFAULT_POOL_SIZE);
	}

	/**
	 * {@link Scheduler} that hosts a fixed pool of single-threaded ExecutorService-based
	 * workers and is suited for parallel work. This type of {@link Scheduler} detects and
	 * rejects usage of blocking Reactor APIs.
	 *
	 * @param name Thread prefix
	 * @param parallelism Number of pooled workers.
	 *
	 * @return a new {@link Scheduler} that hosts a fixed pool of single-threaded
	 * ExecutorService-based workers and is suited for parallel work
	 */
	public static Scheduler newParallel(String name, int parallelism) {
		return newParallel(name, parallelism, false);
	}

	/**
	 * {@link Scheduler} that hosts a fixed pool of single-threaded ExecutorService-based
	 * workers and is suited for parallel work. This type of {@link Scheduler} detects and
	 * rejects usage of blocking Reactor APIs.
	 *
	 * @param name Thread prefix
	 * @param parallelism Number of pooled workers.
	 * @param daemon false if the {@link Scheduler} requires an explicit {@link
	 * Scheduler#dispose()} to exit the VM.
	 *
	 * @return a new {@link Scheduler} that hosts a fixed pool of single-threaded
	 * ExecutorService-based workers and is suited for parallel work
	 */
	public static Scheduler newParallel(String name, int parallelism, boolean daemon) {
		return newParallel(parallelism,
				new ReactorThreadFactory(name, ParallelScheduler.COUNTER, daemon,
						true, Schedulers::defaultUncaughtException));
	}

	/**
	 * {@link Scheduler} that hosts a fixed pool of single-threaded ExecutorService-based
	 * workers and is suited for parallel work.
	 *
	 * @param parallelism Number of pooled workers.
	 * @param threadFactory a {@link ThreadFactory} to use for the fixed initialized
	 * number of {@link Thread}
	 *
	 * @return a new {@link Scheduler} that hosts a fixed pool of single-threaded
	 * ExecutorService-based workers and is suited for parallel work
	 */
	public static Scheduler newParallel(int parallelism, ThreadFactory threadFactory) {
		return factory.newParallel(parallelism, threadFactory);
	}

	/**
	 * {@link Scheduler} that hosts a single-threaded ExecutorService-based worker and is
	 * suited for parallel work. This type of {@link Scheduler} detects and rejects usage
	 * 	 * of blocking Reactor APIs.
	 *
	 * @param name Component and thread name prefix
	 *
	 * @return a new {@link Scheduler} that hosts a single-threaded ExecutorService-based
	 * worker
	 */
	public static Scheduler newSingle(String name) {
		return newSingle(name, false);
	}

	/**
	 * {@link Scheduler} that hosts a single-threaded ExecutorService-based worker and is
	 * suited for parallel work. This type of {@link Scheduler} detects and rejects usage
	 * of blocking Reactor APIs.
	 *
	 * @param name Component and thread name prefix
	 * @param daemon false if the {@link Scheduler} requires an explicit {@link
	 * Scheduler#dispose()} to exit the VM.
	 *
	 * @return a new {@link Scheduler} that hosts a single-threaded ExecutorService-based
	 * worker
	 */
	public static Scheduler newSingle(String name, boolean daemon) {
		return newSingle(new ReactorThreadFactory(name, SingleScheduler.COUNTER, daemon,
				true, Schedulers::defaultUncaughtException));
	}

	/**
	 * {@link Scheduler} that hosts a single-threaded ExecutorService-based worker and is
	 * suited for parallel work.
	 *
	 * @param threadFactory a {@link ThreadFactory} to use for the unique thread of the
	 * {@link Scheduler}
	 *
	 * @return a new {@link Scheduler} that hosts a single-threaded ExecutorService-based
	 * worker
	 */
	public static Scheduler newSingle(ThreadFactory threadFactory) {
		return factory.newSingle(threadFactory);
	}

	/**
	 * Define a hook that is executed when a {@link Scheduler} has
	 * {@link #handleError(Throwable) handled an error}. Note that it is executed after
	 * the error has been passed to the thread uncaughtErrorHandler, which is not the
	 * case when a fatal error occurs (see {@link Exceptions#throwIfJvmFatal(Throwable)}).
	 *
	 * @param c the new hook to set.
	 */
	public static void onHandleError(BiConsumer<Thread, ? super Throwable> c) {
		if (log.isDebugEnabled()) {
			log.debug("Hooking new default: onHandleError");
		}
		onHandleErrorHook = Objects.requireNonNull(c, "onHandleError");
	}

	/**
	 * Check if calling a Reactor blocking API in the current {@link Thread} is forbidden
	 * or not, by checking if the thread implements {@link NonBlocking} (in which case it is
	 * forbidden and this method returns {@code true}).
	 *
	 * @return {@code true} if blocking is forbidden in this thread, {@code false} otherwise
	 */
	public static boolean isInNonBlockingThread() {
		return Thread.currentThread() instanceof NonBlocking;
	}

	/**
	 * Check if calling a Reactor blocking API in the given {@link Thread} is forbidden
	 * or not, by checking if the thread implements {@link NonBlocking} (in which case it is
	 * forbidden and this method returns {@code true}).
	 *
	 * @return {@code true} if blocking is forbidden in that thread, {@code false} otherwise
	 */
	public static boolean isNonBlockingThread(Thread t) {
		return t instanceof NonBlocking;
	}

	/**
	 * If Micrometer is available, set-up a decorator that will instrument any
	 * {@link ExecutorService} that backs a {@link Scheduler}.
	 * No-op if Micrometer isn't available.
	 *
	 * This instrumentation sends data to the Micrometer Global Registry.
	 *
	 * @implNote Note that this is added as a decorator via Schedulers when enabling metrics for schedulers, which doesn't change the Factory.
	 */
	public static void enableMetrics() {
		if (Metrics.isInstrumentationAvailable()) {
			addExecutorServiceDecorator(SchedulerMetricDecorator.METRICS_DECORATOR_KEY, new SchedulerMetricDecorator());
		}
	}

	/**
	 * If {@link #enableMetrics()} has been previously called, removes the decorator.
	 * No-op if {@link #enableMetrics()} hasn't been called.
	 */
	public static void disableMetrics() {
		removeExecutorServiceDecorator(SchedulerMetricDecorator.METRICS_DECORATOR_KEY);
	}

	/**
	 * Re-apply default factory to {@link Schedulers}
	 */
	public static void resetFactory(){
		setFactory(DEFAULT);
	}

	/**
	 * Reset the {@link #onHandleError(BiConsumer)} hook to the default no-op behavior.
	 */
	public static void resetOnHandleError() {
		if (log.isDebugEnabled()) {
			log.debug("Reset to factory defaults: onHandleError");
		}
		onHandleErrorHook = null;
	}

	/**
	 * Replace {@link Schedulers} factories ({@link #newParallel(String) newParallel},
	 * {@link #newSingle(String) newSingle} and {@link #newElastic(String) newElastic}). Also
	 * shutdown Schedulers from the cached factories (like {@link #single()}) in order to
	 * also use these replacements, re-creating the shared schedulers from the new factory
	 * upon next use.
	 *

	 * This method should be called safely and with caution, typically on app startup.
	 *
	 * @param factoryInstance an arbitrary {@link Factory} instance.
	 */
	public static void setFactory(Factory factoryInstance) {
		Objects.requireNonNull(factoryInstance, "factoryInstance");
		shutdownNow();
		factory = factoryInstance;
	}

	/**
	 * Set up an additional {@link ScheduledExecutorService} decorator for a given key
	 * only if that key is not already present. Note that the {@link Factory}'s legacy
	 * {@link Factory#decorateExecutorService(String, Supplier)} method will always be
	 * invoked last after applying the decorators added via this method.
	 *

	 * The decorator is a {@link BiFunction} taking the Scheduler and the backing
	 * {@link ScheduledExecutorService} as second argument. It returns the
	 * decorated {@link ScheduledExecutorService}.
	 *
	 * @param key the key under which to set up the decorator
	 * @param decorator the executor service decorator to add, if key not already present.
	 * @return true if the decorator was added, false if a decorator was already present
	 * for this key.
	 * @see #setExecutorServiceDecorator(String, BiFunction)
	 * @see #removeExecutorServiceDecorator(String)
	 * @see Schedulers#onScheduleHook(String, Function)
	 */
	public static boolean addExecutorServiceDecorator(String key, BiFunction<Scheduler, ScheduledExecutorService, ScheduledExecutorService> decorator) {
		synchronized (DECORATORS) {
			return DECORATORS.putIfAbsent(key, decorator) == null;
		}
	}

	/**
	 * Set up an additional {@link ScheduledExecutorService} decorator for a given key,
	 * even if that key is already present. Note that the {@link Factory}'s legacy
	 * {@link Factory#decorateExecutorService(String, Supplier)} method will always be
	 * invoked last after applying the decorators added via this method.
	 *

	 * The decorator is a {@link BiFunction} taking the Scheduler and the backing
	 * {@link ScheduledExecutorService} as second argument. It returns the
	 * decorated {@link ScheduledExecutorService}.
	 *
	 * @param key the key under which to set up the decorator
	 * @param decorator the executor service decorator to add, if key not already present.
	 * @see #addExecutorServiceDecorator(String, BiFunction)
	 * @see #removeExecutorServiceDecorator(String)
	 * @see Schedulers#onScheduleHook(String, Function)
	 */
	public static void setExecutorServiceDecorator(String key, BiFunction<Scheduler, ScheduledExecutorService, ScheduledExecutorService> decorator) {
		synchronized (DECORATORS) {
			DECORATORS.put(key, decorator);
		}
	}

	/**
	 * Remove an existing {@link ScheduledExecutorService} decorator if it has been set up
	 * via {@link #addExecutorServiceDecorator(String, BiFunction)}. Note that the {@link Factory}'s
	 * legacy {@link Factory#decorateExecutorService(String, Supplier)} method is always
	 * applied last, even if all other decorators have been removed via this method.
	 *

	 * In case the decorator implements {@link Disposable}, it is also
	 * {@link Disposable#dispose() disposed}.
	 *
	 * @param key the key for the executor service decorator to remove
	 * @return the removed decorator, or null if none was set for that key
	 * @see #addExecutorServiceDecorator(String, BiFunction)
	 * @see #setExecutorServiceDecorator(String, BiFunction)
	 */
	public static BiFunction<Scheduler, ScheduledExecutorService, ScheduledExecutorService> removeExecutorServiceDecorator(String key) {
		BiFunction<Scheduler, ScheduledExecutorService, ScheduledExecutorService> removed;
		synchronized (DECORATORS) {
			removed = DECORATORS.remove(key);
		}
		if (removed instanceof Disposable) {
			((Disposable) removed).dispose();
		}
		return removed;
	}

	/**
	 * This method is aimed at {@link Scheduler} implementors, enabling custom implementations
	 * that are backed by a {@link ScheduledExecutorService} to also have said executors
	 * decorated (ie. for instrumentation purposes).
	 *

	 * It applies the decorators added via
	 * {@link #addExecutorServiceDecorator(String, BiFunction)}, so it shouldn't be added
	 * as a decorator. Note also that decorators are not guaranteed to be idempotent, so
	 * this method should be called only once per executor.
	 *
	 * @param owner a {@link Scheduler} that owns the {@link ScheduledExecutorService}
	 * @param original the {@link ScheduledExecutorService} that the {@link Scheduler}
	 * wants to use originally
	 * @return the decorated {@link ScheduledExecutorService}, or the original if no decorator is set up
	 * @see #addExecutorServiceDecorator(String, BiFunction)
	 * @see #removeExecutorServiceDecorator(String)
	 */
	public static ScheduledExecutorService decorateExecutorService(Scheduler owner, ScheduledExecutorService original) {
		synchronized (DECORATORS) {
			for (BiFunction<Scheduler, ScheduledExecutorService, ScheduledExecutorService> decorator : DECORATORS.values()) {
				original = decorator.apply(owner, original);
			}
		}

		final ScheduledExecutorService beforeFactory = original;

		// Backward compatibility
		final String schedulerType;
		if (owner instanceof SingleScheduler) {
			schedulerType = Schedulers.SINGLE;
		}
		else if (owner instanceof ParallelScheduler) {
			schedulerType = Schedulers.PARALLEL;
		}
		else if (owner instanceof ElasticScheduler) {
			schedulerType = Schedulers.ELASTIC;
		}
		else if (owner instanceof DelegateServiceScheduler) {
			schedulerType = "ExecutorService";
		}
		else {
			schedulerType = owner.getClass().getName();
		}

		return factory.decorateExecutorService(schedulerType, () -> beforeFactory);
	}

	/**
	 * Add or replace a named scheduling {@link Function decorator}. With subsequent calls
	 * to this method, the onScheduleHook hook can be a composite of several sub-hooks, each
	 * with a different key.
	 *

	 * The sub-hook is a {@link Function} taking the scheduled {@link Runnable}.
	 * It returns the decorated {@link Runnable}.
	 *
	 * @param key the key under which to set up the onScheduleHook sub-hook
	 * @param decorator the {@link Runnable} decorator to add (or replace, if key is already present)
	 * @see #resetOnScheduleHook(String)
	 * @see #resetOnScheduleHooks()
	 */
	public static void onScheduleHook(String key, Function<Runnable, Runnable> decorator) {
		synchronized (onScheduleHooks) {
			onScheduleHooks.put(key, decorator);
			Function<Runnable, Runnable> newHook = null;
			for (Function<Runnable, Runnable> function : onScheduleHooks.values()) {
				if (newHook == null) {
					newHook = function;
				}
				else {
					newHook = newHook.andThen(function);
				}
			}
			onScheduleHook = newHook;
		}
	}

	/**
	 * Reset a specific onScheduleHook {@link Function sub-hook} if it has been set up
	 * via {@link #onScheduleHook(String, Function)}.
	 *
	 * @param key the key for onScheduleHook sub-hook to remove
	 * @see #onScheduleHook(String, Function)
	 * @see #resetOnScheduleHooks()
	 */
	public static void resetOnScheduleHook(String key) {
		synchronized (onScheduleHooks) {
			onScheduleHooks.remove(key);
			if (onScheduleHooks.isEmpty()) {
				onScheduleHook = Function.identity();
			}
			else {
				Function<Runnable, Runnable> newHook = null;
				for (Function<Runnable, Runnable> function : onScheduleHooks.values()) {
					if (newHook == null) {
						newHook = function;
					}
					else {
						newHook = newHook.andThen(function);
					}
				}
				onScheduleHook = newHook;
			}
		}
	}

	/**
	 * Remove all onScheduleHook {@link Function sub-hooks}.
	 *
	 * @see #onScheduleHook(String, Function)
	 * @see #resetOnScheduleHook(String)
	 */
	public static void resetOnScheduleHooks() {
		synchronized (onScheduleHooks) {
			onScheduleHooks.clear();
			onScheduleHook = null;
		}
	}

	/**
	 * Applies the hooks registered with {@link Schedulers#onScheduleHook(String, Function)}.
	 *
	 * @param runnable a {@link Runnable} submitted to a {@link Scheduler}
	 * @return decorated {@link Runnable} if any hook is registered, the original otherwise.
	 */
	public static Runnable onSchedule(Runnable runnable) {
		Function<Runnable, Runnable> hook = onScheduleHook;
		if (hook != null) {
			return hook.apply(runnable);
		}
		else {
			return runnable;
		}
	}

	/**
	 * Clear any cached {@link Scheduler} and call dispose on them.
	 */
	public static void shutdownNow() {
		CachedScheduler oldElastic = CACHED_ELASTIC.getAndSet(null);
		CachedScheduler oldParallel = CACHED_PARALLEL.getAndSet(null);
		CachedScheduler oldSingle = CACHED_SINGLE.getAndSet(null);

		if (oldElastic != null) oldElastic._dispose();
		if (oldParallel != null) oldParallel._dispose();
		if (oldSingle != null) oldSingle._dispose();
	}

	/**
	 * {@link Scheduler} that hosts a single-threaded ExecutorService-based worker and is
	 * suited for parallel work. Will cache the returned schedulers for subsequent calls until dispose.
	 *
	 * @return default instance of a {@link Scheduler} that hosts a single-threaded
	 * ExecutorService-based worker
	 */
	public static Scheduler single() {
		return cache(CACHED_SINGLE, SINGLE, SINGLE_SUPPLIER);
	}

	/**
	 * Wraps a single {@link reactor.core.scheduler.Scheduler.Worker} from some other
	 * {@link Scheduler} and provides {@link reactor.core.scheduler.Scheduler.Worker}
	 * services on top of it.
	 *

	 * Use the {@link Scheduler#dispose()} to release the wrapped worker.
	 *
	 * @param original a {@link Scheduler} to call upon to get the single {@link
	 * reactor.core.scheduler.Scheduler.Worker}
	 *
	 * @return a wrapping {@link Scheduler} consistently returning a same worker from a
	 * source {@link Scheduler}
	 */
	public static Scheduler single(Scheduler original) {
		return new SingleWorkerScheduler(original);
	}

	/**
	 * Public factory hook to override Schedulers behavior globally
	 */
	public interface Factory {

		/**
		 * Override this method to decorate {@link ScheduledExecutorService} internally used by
		 * Reactor's various {@link Scheduler} implementations, allowing to tune the
		 * {@link ScheduledExecutorService} backing implementation.
		 *
		 * @param schedulerType a name hinting at the flavor of Scheduler being tuned.
		 * @param actual the default backing implementation, provided lazily as a Supplier
		 * so that you can bypass instantiation completely if you want to replace it.
		 * @return the internal {@link ScheduledExecutorService} instance to use.
		 * @deprecated use {@link Schedulers#addExecutorServiceDecorator(String, BiFunction)} and
		 * {@link Schedulers#removeExecutorServiceDecorator(String)} instead, to compose
		 * multiple decorators in addition to the one from the current
		 * {@link Schedulers#setFactory(Factory) Factory}
		 */
		@Deprecated
		default ScheduledExecutorService decorateExecutorService(String schedulerType,
				Supplier<? extends ScheduledExecutorService> actual) {
			return actual.get();
		}

		/**
		 * {@link Scheduler} that dynamically creates Workers resources and caches
		 * eventually, reusing them once the Workers have been shut down.
		 *

		 * The maximum number of created workers is unbounded.
		 *
		 * @param ttlSeconds Time-to-live for an idle {@link reactor.core.scheduler.Scheduler.Worker}
		 * @param threadFactory a {@link ThreadFactory} to use
		 *
		 * @return a new {@link Scheduler} that dynamically creates Workers resources and
		 * caches eventually, reusing them once the Workers have been shut down.
		 */
		default Scheduler newElastic(int ttlSeconds, ThreadFactory threadFactory) {
			return new ElasticScheduler(threadFactory, ttlSeconds);
		}

		/**
		 * {@link Scheduler} that hosts a fixed pool of workers and is suited for parallel
		 * work.
		 *
		 * @param parallelism Number of pooled workers.
		 * @param threadFactory a {@link ThreadFactory} to use for the fixed initialized
		 * number of {@link Thread}
		 *
		 * @return a new {@link Scheduler} that hosts a fixed pool of workers and is
		 * suited for parallel work
		 */
		default Scheduler newParallel(int parallelism, ThreadFactory threadFactory) {
			return new ParallelScheduler(parallelism, threadFactory);
		}

		/**
		 * {@link Scheduler} that hosts a single worker and is suited for non-blocking
		 * work.
		 *
		 * @param threadFactory a {@link ThreadFactory} to use for the unique resource of
		 * the {@link Scheduler}
		 *
		 * @return a new {@link Scheduler} that hosts a single worker
		 */
		default Scheduler newSingle(ThreadFactory threadFactory) {
			return new SingleScheduler(threadFactory);
		}
	}

	// Internals
	static final String ELASTIC               = "elastic"; // IO stuff
	static final String PARALLEL              = "parallel"; //scale up common tasks
	static final String SINGLE                = "single"; //non blocking tasks
	static final String IMMEDIATE             = "immediate";
	static final String FROM_EXECUTOR         = "fromExecutor";
	static final String FROM_EXECUTOR_SERVICE = "fromExecutorService";

	// Cached schedulers in atomic references:
	static AtomicReference<CachedScheduler> CACHED_ELASTIC  = new AtomicReference<>();
	static AtomicReference<CachedScheduler> CACHED_PARALLEL = new AtomicReference<>();
	static AtomicReference<CachedScheduler> CACHED_SINGLE   = new AtomicReference<>();

	static final Supplier<Scheduler> ELASTIC_SUPPLIER =
			() -> newElastic(ELASTIC, ElasticScheduler.DEFAULT_TTL_SECONDS, true);

	static final Supplier<Scheduler> PARALLEL_SUPPLIER =
			() -> newParallel(PARALLEL, DEFAULT_POOL_SIZE, true);

	static final Supplier<Scheduler> SINGLE_SUPPLIER = () -> newSingle(SINGLE, true);

	static final Factory DEFAULT = new Factory() { };

	static final Map<String, BiFunction<Scheduler, ScheduledExecutorService, ScheduledExecutorService>>
			DECORATORS = new LinkedHashMap<>();

	static volatile Factory factory = DEFAULT;

	private static final LinkedHashMap<String, Function<Runnable, Runnable>> onScheduleHooks = new LinkedHashMap<>(1);

	@Nullable
	private static Function<Runnable, Runnable> onScheduleHook;

	/**
	 * Get a {@link CachedScheduler} out of the {@code reference} or create one using the
	 * {@link Supplier} if the reference is empty, effectively creating a single instance
	 * to be reused as a default scheduler for the given {@code key} category.
	 *
	 * @param reference the cache reference that holds the scheduler
	 * @param key the "name" for the Scheduler's category/type
	 * @param supplier the {@link Scheduler} generator to use and wrap into a {@link CachedScheduler}.
	 * Note that in case of a race, an extraneous Scheduler can be created, but it'll get
	 * immediately {@link Scheduler#dispose() disposed}.
	 * @return a {@link CachedScheduler} to be reused, either pre-existing or created
	 */
	static CachedScheduler cache(AtomicReference<CachedScheduler> reference, String key, Supplier<Scheduler> supplier) {
		CachedScheduler s = reference.get();
		if (s != null) {
			return s;
		}
		s = new CachedScheduler(key, supplier.get());
		if (reference.compareAndSet(null, s)) {
			return s;
		}
		//the reference was updated in the meantime with a cached scheduler
		//fallback to it and dispose the extraneous one
		s._dispose();
		return reference.get();
	}

	static final Logger log = Loggers.getLogger(Schedulers.class);

	static final void defaultUncaughtException(Thread t, Throwable e) {
		Schedulers.log.error("Scheduler worker in group " + t.getThreadGroup().getName()
				+ " failed with an uncaught exception", e);
	}

	static void handleError(Throwable ex) {
		Thread thread = Thread.currentThread();
		Throwable t = unwrap(ex);
		Thread.UncaughtExceptionHandler x = thread.getUncaughtExceptionHandler();
		if (x != null) {
			x.uncaughtException(thread, t);
		}
		else {
			log.error("Scheduler worker failed with an uncaught exception", t);
		}
		if (onHandleErrorHook != null) {
			onHandleErrorHook.accept(thread, t);
		}
	}

	static class CachedScheduler implements Scheduler, Supplier<Scheduler>, Scannable {

		final Scheduler cached;
		final String    key;

		CachedScheduler(String key, Scheduler cached) {
			this.cached = cached;
			this.key = key;
		}

		@Override
		public Disposable schedule(Runnable task) {
			return cached.schedule(task);
		}

		@Override
		public Disposable schedule(Runnable task, long delay, TimeUnit unit) {
			return cached.schedule(task, delay, unit);
		}

		@Override
		public Disposable schedulePeriodically(Runnable task,
				long initialDelay,
				long period,
				TimeUnit unit) {
			return cached.schedulePeriodically(task, initialDelay, period, unit);
		}

		@Override
		public Worker createWorker() {
			return cached.createWorker();
		}

		@Override
		public long now(TimeUnit unit) {
			return cached.now(unit);
		}

		@Override
		public void start() {
			cached.start();
		}

		@Override
		public void dispose() {
		}

		@Override
		public boolean isDisposed() {
			return cached.isDisposed();
		}

		@Override
		public String toString() {
			return cached.toString();
		}

		@Override
		public Object scanUnsafe(Attr key) {
			return Scannable.from(cached).scanUnsafe(key);
		}

		/**
		 * Get the {@link Scheduler} that is cached and wrapped inside this
		 * {@link CachedScheduler}.
		 *
		 * @return the cached Scheduler
		 */
		@Override
		public Scheduler get() {
			return cached;
		}

		void _dispose() {
			cached.dispose();
		}
	}

	static Disposable directSchedule(ScheduledExecutorService exec,
			Runnable task,
			long delay,
			TimeUnit unit) {
		task = onSchedule(task);
		SchedulerTask sr = new SchedulerTask(task);
		Future<?> f;
		if (delay <= 0L) {
			f = exec.submit((Callable<?>) sr);
		}
		else {
			f = exec.schedule((Callable<?>) sr, delay, unit);
		}
		sr.setFuture(f);

		return sr;
	}

	static Disposable directSchedulePeriodically(ScheduledExecutorService exec,
			Runnable task,
			long initialDelay,
			long period,
			TimeUnit unit) {
		task = onSchedule(task);

		if (period <= 0L) {
			InstantPeriodicWorkerTask isr =
					new InstantPeriodicWorkerTask(task, exec);
			Future<?> f;
			if (initialDelay <= 0L) {
				f = exec.submit(isr);
			}
			else {
				f = exec.schedule(isr, initialDelay, unit);
			}
			isr.setFirst(f);

			return isr;
		}
		else {
			PeriodicSchedulerTask sr = new PeriodicSchedulerTask(task);
			Future<?> f = exec.scheduleAtFixedRate(sr, initialDelay, period, unit);
			sr.setFuture(f);

			return sr;
		}
	}

	static Disposable workerSchedule(ScheduledExecutorService exec,
			Disposable.Composite tasks,
			Runnable task,
			long delay,
			TimeUnit unit) {
		task = onSchedule(task);

		WorkerTask sr = new WorkerTask(task, tasks);
		if (!tasks.add(sr)) {
			throw Exceptions.failWithRejected();
		}

		try {
			Future<?> f;
			if (delay <= 0L) {
				f = exec.submit((Callable<?>) sr);
			}
			else {
				f = exec.schedule((Callable<?>) sr, delay, unit);
			}
			sr.setFuture(f);
		}
		catch (RejectedExecutionException ex) {
			sr.dispose();
			//RejectedExecutionException are propagated up
			throw ex;
		}

		return sr;
	}

	static Disposable workerSchedulePeriodically(ScheduledExecutorService exec,
			Disposable.Composite tasks,
			Runnable task,
			long initialDelay,
			long period,
			TimeUnit unit) {
		task = onSchedule(task);

		if (period <= 0L) {
			InstantPeriodicWorkerTask isr =
					new InstantPeriodicWorkerTask(task, exec, tasks);
			if (!tasks.add(isr)) {
			  throw Exceptions.failWithRejected();
			}
			try {
				Future<?> f;
				if (initialDelay <= 0L) {
					f = exec.submit(isr);
				}
				else {
					f = exec.schedule(isr, initialDelay, unit);
				}
				isr.setFirst(f);
			}
			catch (RejectedExecutionException ex) {
				isr.dispose();
				//RejectedExecutionException are propagated up
				throw ex;
			}
			catch (IllegalArgumentException | NullPointerException ex) {
				isr.dispose();
				//IllegalArgumentException are wrapped into RejectedExecutionException and propagated up
				throw new RejectedExecutionException(ex);
			}

			return isr;
		}

		PeriodicWorkerTask sr = new PeriodicWorkerTask(task, tasks);
		if (!tasks.add(sr)) {
			throw Exceptions.failWithRejected();
		}

		try {
			Future<?> f = exec.scheduleAtFixedRate(sr, initialDelay, period, unit);
			sr.setFuture(f);
		}
		catch (RejectedExecutionException ex) {
			sr.dispose();
			//RejectedExecutionException are propagated up
			throw ex;
		}
		catch (IllegalArgumentException | NullPointerException ex) {
			sr.dispose();
			//IllegalArgumentException are wrapped into RejectedExecutionException and propagated up
			throw new RejectedExecutionException(ex);
		}

		return sr;
	}

	/**
	 * Scan an {@link Executor} or {@link ExecutorService}, recognizing several special
	 * implementations. Unwraps some decorating schedulers, recognizes {@link Scannable}
	 * schedulers and delegates to their {@link Scannable#scanUnsafe(Scannable.Attr)}
	 * method, introspects {@link ThreadPoolExecutor} instances.
	 *

	 * If no data can be extracted, defaults to the provided {@code orElse}
	 * {@link Scannable#scanUnsafe(Scannable.Attr) scanUnsafe}.
	 *
	 * @param executor the executor to introspect in a best effort manner.
	 * @param key the key to scan for. CAPACITY and BUFFERED mainly.
	 * @return an equivalent of {@link Scannable#scanUnsafe(Scannable.Attr)} but that can
	 * also work on some implementations of {@link Executor}
	 */
	@Nullable
	static final Object scanExecutor(Executor executor, Scannable.Attr key) {
		if (executor instanceof DelegateServiceScheduler.UnsupportedScheduledExecutorService) {
			executor = ((DelegateServiceScheduler.UnsupportedScheduledExecutorService) executor).get();
		}
		if (executor instanceof Scannable) {
			return ((Scannable) executor).scanUnsafe(key);
		}

		if (executor instanceof ExecutorService) {
			ExecutorService service = (ExecutorService) executor;
			if (key == Scannable.Attr.TERMINATED) return service.isTerminated();
			if (key == Scannable.Attr.CANCELLED) return service.isShutdown();
		}

		if (executor instanceof ThreadPoolExecutor) {
				final ThreadPoolExecutor poolExecutor = (ThreadPoolExecutor) executor;
				if (key == Scannable.Attr.CAPACITY) return poolExecutor.getMaximumPoolSize();
				if (key == Scannable.Attr.BUFFERED) return ((Long) (poolExecutor.getTaskCount() - poolExecutor.getCompletedTaskCount())).intValue();
				if (key == Scannable.Attr.LARGE_BUFFERED) return poolExecutor.getTaskCount() - poolExecutor.getCompletedTaskCount();
		}

		return null;
	}

}


/*
 * Copyright (c) 2011-2017 Pivotal Software Inc, All Rights Reserved.
 *
 * 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
 *
 *       https://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 reactor.util.function;

import java.io.Serializable;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;

import reactor.util.annotation.NonNull;
import reactor.util.annotation.Nullable;

/**
 * A tuple that holds two non-null values.
 *
 * @param <T1> The type of the first non-null value held by this tuple
 * @param <T2> The type of the second non-null value held by this tuple
 * @author Jon Brisbin
 * @author Stephane Maldini
 */
@SuppressWarnings("rawtypes")
public class Tuple2<T1, T2> implements Iterable<Object>, Serializable {

	private static final long serialVersionUID = -3518082018884860684L;

	@NonNull final T1 t1;
	@NonNull final T2 t2;

	Tuple2(T1 t1, T2 t2) {
		this.t1 = Objects.requireNonNull(t1, "t1");
		this.t2 = Objects.requireNonNull(t2, "t2");
	}

	/**
	 * Type-safe way to get the fist object of this {@link Tuples}.
	 *
	 * @return The first object
	 */
	public T1 getT1() {
		return t1;
	}

	/**
	 * Type-safe way to get the second object of this {@link Tuples}.
	 *
	 * @return The second object
	 */
	public T2 getT2() {
		return t2;
	}

	/**
	 * Map the left-hand part (T1) of this {@link Tuple2} into a different value and type,
	 * keeping the right-hand part (T2).
	 *
	 * @param mapper the mapping {@link Function} for the left-hand part
	 * @param <R> the new type for the left-hand part
	 * @return a new {@link Tuple2} with a different left (T1) value
	 */
	public <R> Tuple2<R, T2> mapT1(Function<T1, R> mapper) {
		return new Tuple2<>(mapper.apply(t1), t2);
	}

	/**
	 * Map the right-hand part (T2) of this {@link Tuple2} into a different value and type,
	 * keeping the left-hand part (T1).
	 *
	 * @param mapper the mapping {@link Function} for the right-hand part
	 * @param <R> the new type for the right-hand part
	 * @return a new {@link Tuple2} with a different right (T2) value
	 */
	public <R> Tuple2<T1, R> mapT2(Function<T2, R> mapper) {
		return new Tuple2<>(t1, mapper.apply(t2));
	}

	/**
	 * Get the object at the given index.
	 *
	 * @param index The index of the object to retrieve. Starts at 0.
	 * @return The object or {@literal null} if out of bounds.
	 */
	@Nullable
	public Object get(int index) {
		switch (index) {
			case 0:
				return t1;
			case 1:
				return t2;
			default:
				return null;
		}
	}

	/**
	 * Turn this {@code Tuple} into a {@link List List<Object>}.
	 * The list isn't tied to this Tuple but is a copy with limited
	 * mutability ({@code add} and {@code remove} are not supported, but {@code set} is).
	 *
	 * @return A copy of the tuple as a new {@link List List<Object>}.
	 */
	public List<Object> toList() {
		return Arrays.asList(toArray());
	}

	/**
	 * Turn this {@code Tuple} into a plain {@code Object[]}.
	 * The array isn't tied to this Tuple but is a copy.
	 *
	 * @return A copy of the tuple as a new {@link Object Object[]}.
	 */
	public Object[] toArray() {
		return new Object[]{t1, t2};
	}

	/**
	 * Return an immutable {@link Iterator Iterator<Object>} around
	 * the content of this {@code Tuple}.
	 *
	 * @implNote As an {@link Iterator} is always tied to its {@link Iterable} source by
	 * definition, the iterator cannot be mutable without the iterable also being mutable.
	 * Since {@link Tuples} are immutable, so is the {@link Iterator}
	 * returned by this method.
	 *
	 * @return An unmodifiable {@link Iterator} over the elements in this Tuple.
	 */
	@Override
	public Iterator<Object> iterator() {
		return Collections.unmodifiableList(toList()).iterator();
	}

	@Override
	public boolean equals(@Nullable Object o) {
		if (this == o) {
			return true;
		}
		if (o == null || getClass() != o.getClass()) {
			return false;
		}

		Tuple2<?, ?> tuple2 = (Tuple2<?, ?>) o;

		return t1.equals(tuple2.t1) && t2.equals(tuple2.t2);

	}

	@Override
	public int hashCode() {
		int result = size();
		result = 31 * result + t1.hashCode();
		result = 31 * result + t2.hashCode();
		return result;
	}

	/**
	 * Return the number of elements in this {@literal Tuples}.
	 *
	 * @return The size of this {@literal Tuples}.
	 */
	public int size() {
		return 2;
	}

	/**
	 * A Tuple String representation is the comma separated list of values, enclosed
	 * in square brackets.
	 * @return the Tuple String representation
	 */
	@Override
	public final String toString() {
		return Tuples.tupleStringRepresentation(toArray()).insert(0, '[').append(']').toString();
	}
}


/*
 * Copyright (c) 2011-2017 Pivotal Software Inc, All Rights Reserved.
 *
 * 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
 *
 *       https://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 reactor.core.publisher;

import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;

import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import reactor.core.CoreSubscriber;
import reactor.core.Exceptions;
import reactor.core.Fuseable;
import reactor.core.Scannable;
import reactor.util.annotation.Nullable;
import reactor.util.context.Context;

/**
 * Maps a sequence of values each into a Publisher and flattens them
 * back into a single sequence, interleaving events from the various inner Publishers.
 *
 * @param <T> the source value type
 * @param <R> the result value type
 * @see Reactive-Streams-Commons
 */
final class FluxFlatMap<T, R> extends FluxOperator<T, R> {

	final Function<? super T, ? extends Publisher<? extends R>> mapper;

	final boolean delayError;

	final int maxConcurrency;

	final Supplier<? extends Queue<R>> mainQueueSupplier;

	final int prefetch;

	final Supplier<? extends Queue<R>> innerQueueSupplier;

	FluxFlatMap(Flux<? extends T> source,
			Function<? super T, ? extends Publisher<? extends R>> mapper,
			boolean delayError,
			int maxConcurrency,
			Supplier<? extends Queue<R>> mainQueueSupplier,
			int prefetch,
			Supplier<? extends Queue<R>> innerQueueSupplier) {
		super(source);
		if (prefetch <= 0) {
			throw new IllegalArgumentException("prefetch > 0 required but it was " + prefetch);
		}
		if (maxConcurrency <= 0) {
			throw new IllegalArgumentException("maxConcurrency > 0 required but it was " + maxConcurrency);
		}
		this.mapper = Objects.requireNonNull(mapper, "mapper");
		this.delayError = delayError;
		this.prefetch = prefetch;
		this.maxConcurrency = maxConcurrency;
		this.mainQueueSupplier =
				Objects.requireNonNull(mainQueueSupplier, "mainQueueSupplier");
		this.innerQueueSupplier =
				Objects.requireNonNull(innerQueueSupplier, "innerQueueSupplier");
	}

	@Override
	public int getPrefetch() {
		return prefetch;
	}

	@Override
	public void subscribe(CoreSubscriber<? super R> actual) {

		if (trySubscribeScalarMap(source, actual, mapper, false)) {
			return;
		}

		source.subscribe(new FlatMapMain<>(actual,
				mapper,
				delayError,
				maxConcurrency,
				mainQueueSupplier,
				prefetch, innerQueueSupplier));
	}

	/**
	 * Checks if the source is a Supplier and if the mapper's publisher output is also
	 * a supplier, thus avoiding subscribing to any of them.
	 *
	 * @param source the source publisher
	 * @param s the end consumer
	 * @param mapper the mapper function
	 * @param fuseableExpected if true, the parent class was marked Fuseable thus the
	 * mapping output has to signal onSubscribe with a QueueSubscription
	 *
	 * @return true if the optimization worked
	 */
	@SuppressWarnings("unchecked")
	static <T, R> boolean trySubscribeScalarMap(Publisher<? extends T> source,
			CoreSubscriber<? super R> s,
			Function<? super T, ? extends Publisher<? extends R>> mapper,
			boolean fuseableExpected) {
		if (source instanceof Callable) {
			T t;

			try {
				t = ((Callable<? extends T>) source).call();
			}
			catch (Throwable e) {
				Operators.error(s, Operators.onOperatorError(e, s.currentContext()));
				return true;
			}

			if (t == null) {
				Operators.complete(s);
				return true;
			}

			Publisher<? extends R> p;

			try {
				p = Objects.requireNonNull(mapper.apply(t),
						"The mapper returned a null Publisher");
			}
			catch (Throwable e) {
				Operators.error(s, Operators.onOperatorError(null, e, t, s.currentContext()));
				return true;
			}

			if (p instanceof Callable) {
				R v;

				try {
					v = ((Callable<R>) p).call();
				}
				catch (Throwable e) {
					Operators.error(s, Operators.onOperatorError(null, e, t, s.currentContext()));
					return true;
				}

				if (v != null) {
					s.onSubscribe(Operators.scalarSubscription(s, v));
				}
				else {
					Operators.complete(s);
				}
			}
			else {
				if (!fuseableExpected || p instanceof Fuseable) {
					p.subscribe(s);
				}
				else {
					p.subscribe(new FluxHide.SuppressFuseableSubscriber<>(s));
				}
			}

			return true;
		}

		return false;
	}

	static final class FlatMapMain<T, R> extends FlatMapTracker<FlatMapInner<R>>
			implements InnerOperator<T, R> {

		final boolean                                               delayError;
		final int                                                   maxConcurrency;
		final int                                                   prefetch;
		final int                                                   limit;
		final Function<? super T, ? extends Publisher<? extends R>> mapper;
		final Supplier<? extends Queue<R>>                          mainQueueSupplier;
		final Supplier<? extends Queue<R>>                          innerQueueSupplier;
		final CoreSubscriber<? super R>                             actual;
		final Context                                               ctx;

		volatile Queue<R> scalarQueue;

		volatile Throwable error;
		@SuppressWarnings("rawtypes")
		static final AtomicReferenceFieldUpdater<FlatMapMain, Throwable> ERROR =
				AtomicReferenceFieldUpdater.newUpdater(FlatMapMain.class,
						Throwable.class,
						"error");

		volatile boolean done;

		volatile boolean cancelled;

		Subscription s;

		volatile long requested;
		@SuppressWarnings("rawtypes")
		static final AtomicLongFieldUpdater<FlatMapMain> REQUESTED =
				AtomicLongFieldUpdater.newUpdater(FlatMapMain.class, "requested");

		volatile int wip;
		@SuppressWarnings("rawtypes")
		static final AtomicIntegerFieldUpdater<FlatMapMain> WIP =
				AtomicIntegerFieldUpdater.newUpdater(FlatMapMain.class, "wip");

		@SuppressWarnings("rawtypes")
		static final FlatMapInner[] EMPTY = new FlatMapInner[0];

		@SuppressWarnings("rawtypes")
		static final FlatMapInner[] TERMINATED = new FlatMapInner[0];

		int lastIndex;

		int produced;

		FlatMapMain(CoreSubscriber<? super R> actual,
				Function<? super T, ? extends Publisher<? extends R>> mapper,
				boolean delayError,
				int maxConcurrency,
				Supplier<? extends Queue<R>> mainQueueSupplier,
				int prefetch,
				Supplier<? extends Queue<R>> innerQueueSupplier) {
			this.actual = actual;
			this.ctx = actual.currentContext();
			this.mapper = mapper;
			this.delayError = delayError;
			this.maxConcurrency = maxConcurrency;
			this.mainQueueSupplier = mainQueueSupplier;
			this.prefetch = prefetch;
			this.innerQueueSupplier = innerQueueSupplier;
			this.limit = Operators.unboundedOrLimit(maxConcurrency);
		}

		@Override
		public final CoreSubscriber<? super R> actual() {
			return actual;
		}

		@Override
		public Stream<? extends Scannable> inners() {
			return Stream.of(array).filter(Objects::nonNull);
		}

		@Override
		@Nullable
		public Object scanUnsafe(Attr key) {
			if (key == Attr.PARENT) return s;
			if (key == Attr.CANCELLED) return cancelled;
			if (key == Attr.ERROR) return error;
			if (key == Attr.TERMINATED) return done && (scalarQueue == null || scalarQueue.isEmpty());
			if (key == Attr.DELAY_ERROR) return delayError;
			if (key == Attr.PREFETCH) return maxConcurrency;
			if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested;
			if (key == Attr.LARGE_BUFFERED) return (scalarQueue != null ? (long) scalarQueue.size() : 0L) + size;
			if (key == Attr.BUFFERED) {
				long realBuffered = (scalarQueue != null ? (long) scalarQueue.size() : 0L) + size;
				if (realBuffered <= Integer.MAX_VALUE) return (int) realBuffered;
				return Integer.MIN_VALUE;
			}

			return InnerOperator.super.scanUnsafe(key);
		}

		@SuppressWarnings("unchecked")
		@Override
		FlatMapInner<R>[] empty() {
			return EMPTY;
		}

		@SuppressWarnings("unchecked")
		@Override
		FlatMapInner<R>[] terminated() {
			return TERMINATED;
		}

		@SuppressWarnings("unchecked")
		@Override
		FlatMapInner<R>[] newArray(int size) {
			return new FlatMapInner[size];
		}

		@Override
		void setIndex(FlatMapInner<R> entry, int index) {
			entry.index = index;
		}

		@Override
		void unsubscribeEntry(FlatMapInner<R> entry) {
			entry.cancel();
		}

		@Override
		public void request(long n) {
			if (Operators.validate(n)) {
				Operators.addCap(REQUESTED, this, n);
				drain();
			}
		}

		@Override
		public void cancel() {
			if (!cancelled) {
				cancelled = true;

				if (WIP.getAndIncrement(this) == 0) {
					Operators.onDiscardQueueWithClear(scalarQueue, ctx, null);
					scalarQueue = null;
					s.cancel();
					unsubscribe();
				}
			}
		}

		@Override
		public void onSubscribe(Subscription s) {
			if (Operators.validate(this.s, s)) {
				this.s = s;

				actual.onSubscribe(this);
				s.request(Operators.unboundedOrPrefetch(maxConcurrency));
			}
		}

		@SuppressWarnings("unchecked")
		@Override
		public void onNext(T t) {
			if (done) {
				Operators.onNextDropped(t, ctx);
				return;
			}

			Publisher<? extends R> p;

			try {
				p = Objects.requireNonNull(mapper.apply(t),
				"The mapper returned a null Publisher");
			}
			catch (Throwable e) {
				Throwable e_ = Operators.onNextError(t, e, ctx, s);
				Operators.onDiscard(t, ctx);
				if (e_ != null) {
					onError(e_);
				}
				else {
					tryEmitScalar(null);
				}
				return;
			}

			if (p instanceof Callable) {
				R v;
				try {
					v = ((Callable<R>) p).call();
				}
				catch (Throwable e) {
					Context ctx = actual.currentContext();
					//does the strategy apply? if so, short-circuit the delayError. In any case, don't cancel
					Throwable e_ = Operators.onNextPollError(t, e, ctx);
					if (e_ == null) {
						return;
					}
					//now if error mode strategy doesn't apply, let delayError play
					if (!delayError || !Exceptions.addThrowable(ERROR, this, e)) {
						onError(Operators.onOperatorError(s, e, t, ctx));
					}
					Operators.onDiscard(t, ctx);
					return;
				}
				tryEmitScalar(v);
			}
			else {
				FlatMapInner<R> inner = new FlatMapInner<>(this, prefetch);
				if (add(inner)) {
					p.subscribe(inner);
				}
			}

		}

		Queue<R> getOrCreateScalarQueue() {
			Queue<R> q = scalarQueue;
			if (q == null) {
				q = mainQueueSupplier.get();
				scalarQueue = q;
			}
			return q;
		}

		@Override
		public void onError(Throwable t) {
			if (done) {
				Operators.onErrorDropped(t, ctx);
				return;
			}
			if (Exceptions.addThrowable(ERROR, this, t)) {
				done = true;
				drain();
			}
			else {
				Operators.onErrorDropped(t, ctx);
			}
		}

		@Override
		public void onComplete() {
			if (done) {
				return;
			}

			done = true;
			drain();
		}

		void tryEmitScalar(@Nullable R v) {
			if (v == null) {
				if (maxConcurrency != Integer.MAX_VALUE) {
					int p = produced + 1;
					if (p == limit) {
						produced = 0;
						s.request(p);
					}
					else {
						produced = p;
					}
				}
				return;
			}

			if (wip == 0 && WIP.compareAndSet(this, 0, 1)) {
				long r = requested;

				Queue<R> q = scalarQueue;
				if (r != 0L && (q == null || q.isEmpty())) {
					actual.onNext(v);

					if (r != Long.MAX_VALUE) {
						REQUESTED.decrementAndGet(this);
					}

					if (maxConcurrency != Integer.MAX_VALUE) {
						int p = produced + 1;
						if (p == limit) {
							produced = 0;
							s.request(p);
						}
						else {
							produced = p;
						}
					}
				}
				else {
					if (q == null) {
						q = getOrCreateScalarQueue();
					}

					if (!q.offer(v) && failOverflow(v, s)){
						done = true;
						drainLoop();
						return;
					}
				}
				if (WIP.decrementAndGet(this) == 0) {
					return;
				}

				drainLoop();
			}
			else {
				Queue<R> q;

				q = getOrCreateScalarQueue();

				if (!q.offer(v) && failOverflow(v, s)) {
					done = true;
				}
				drain();
			}
		}

		void tryEmit(FlatMapInner<R> inner, R v) {
			if (wip == 0 && WIP.compareAndSet(this, 0, 1)) {
				long r = requested;

				Queue<R> q = inner.queue;
				if (r != 0 && (q == null || q.isEmpty())) {
					actual.onNext(v);

					if (r != Long.MAX_VALUE) {
						REQUESTED.decrementAndGet(this);
					}

					inner.request(1);
				}
				else {
					if (q == null) {
						q = getOrCreateInnerQueue(inner);
					}

					if (!q.offer(v) && failOverflow(v, inner)){
						inner.done = true;
						drainLoop();
						return;
					}
				}
				if (WIP.decrementAndGet(this) == 0) {
					return;
				}

				drainLoop();
			}
			else {
				Queue<R> q = getOrCreateInnerQueue(inner);

				if (!q.offer(v) && failOverflow(v, inner)) {
					inner.done = true;
				}
				drain();
			}
		}

		void drain() {
			if (WIP.getAndIncrement(this) != 0) {
				return;
			}
			drainLoop();
		}

		void drainLoop() {
			int missed = 1;

			final Subscriber<? super R> a = actual;

			for (; ; ) {

				boolean d = done;

				FlatMapInner<R>[] as = get();

				int n = as.length;

				Queue<R> sq = scalarQueue;

				boolean noSources = isEmpty();

				if (checkTerminated(d, noSources && (sq == null || sq.isEmpty()), a)) {
					return;
				}

				boolean again = false;

				long r = requested;
				long e = 0L;
				long replenishMain = 0L;

				if (r != 0L && sq != null) {

					while (e != r) {
						d = done;

						R v = sq.poll();

						boolean empty = v == null;

						if (checkTerminated(d, false, a)) {
							return;
						}

						if (empty) {
							break;
						}

						a.onNext(v);

						e++;
					}

					if (e != 0L) {
						replenishMain += e;
						if (r != Long.MAX_VALUE) {
							r = REQUESTED.addAndGet(this, -e);
						}
						e = 0L;
						again = true;
					}
				}
				if (r != 0L && !noSources) {

					int j = lastIndex;
					//Do not need to wrap j since lastIndex is always 0..<n
//					if (j >= n) {
//						j = 0;
//					}

					for (int i = 0; i < n; i++) {
						if (cancelled) {
							Operators.onDiscardQueueWithClear(scalarQueue, ctx, null);
							scalarQueue = null;
							s.cancel();
							unsubscribe();
							return;
						}

						FlatMapInner<R> inner = as[j];
						if (inner != null) {
							d = inner.done;
							Queue<R> q = inner.queue;
							if (d && q == null) {
								remove(inner.index);
								again = true;
								replenishMain++;
							}
							else if (q != null) {
								while (e != r) {
									d = inner.done;

									R v;

									try {
										v = q.poll();
									}
									catch (Throwable ex) {
										ex = Operators.onOperatorError(inner, ex,
												actual.currentContext());
										if (!Exceptions.addThrowable(ERROR, this, ex)) {
											Operators.onErrorDropped(ex,
													actual.currentContext());
										}
										v = null;
										d = true;
									}

									boolean empty = v == null;

									if (checkTerminated(d, false, a)) {
										return;
									}

									if (d && empty) {
										remove(inner.index);
										again = true;
										replenishMain++;
										break;
									}

									if (empty) {
										break;
									}

									a.onNext(v);

									e++;
								}

								if (e == r) {
									d = inner.done;
									boolean empty = q.isEmpty();
									if (d && empty) {
										remove(inner.index);
										again = true;
										replenishMain++;
									}
								}

								if (e != 0L) {
									if (!inner.done) {
										inner.request(e);
									}
									if (r != Long.MAX_VALUE) {
										r = REQUESTED.addAndGet(this, -e);
										if (r == 0L) {
											break; // 0 .. n - 1
										}
									}
									e = 0L;
								}
							}
						}

						if (r == 0L) {
							break;
						}

						if (++j == n) {
							j = 0;
						}
					}

					lastIndex = j;
				}

				if (r == 0L && !noSources) {
					as = get();
					n = as.length;

					for (int i = 0; i < n; i++) {
						if (cancelled) {
							Operators.onDiscardQueueWithClear(scalarQueue, ctx, null);
							scalarQueue = null;
							s.cancel();
							unsubscribe();
							return;
						}

						FlatMapInner<R> inner = as[i];
						if (inner == null) {
							continue;
						}

						d = inner.done;
						Queue<R> q = inner.queue;
						boolean empty = (q == null || q.isEmpty());

						// if we have a non-empty source then quit the cleanup
						if (!empty) {
							break;
						}

						if (d && empty) {
							remove(inner.index);
							again = true;
							replenishMain++;
						}
					}
				}

				if (replenishMain != 0L && !done && !cancelled) {
					s.request(replenishMain);
				}

				if (again) {
					continue;
				}

				missed = WIP.addAndGet(this, -missed);
				if (missed == 0) {
					break;
				}
			}
		}

		boolean checkTerminated(boolean d, boolean empty, Subscriber<?> a) {
			if (cancelled) {
				Operators.onDiscardQueueWithClear(scalarQueue, ctx, null);
				scalarQueue = null;
				s.cancel();
				unsubscribe();

				return true;
			}

			if (delayError) {
				if (d && empty) {
					Throwable e = error;
					if (e != null && e != Exceptions.TERMINATED) {
						e = Exceptions.terminate(ERROR, this);
						a.onError(e);
					}
					else {
						a.onComplete();
					}

					return true;
				}
			}
			else {
				if (d) {
					Throwable e = error;
					if (e != null && e != Exceptions.TERMINATED) {
						e = Exceptions.terminate(ERROR, this);
						Operators.onDiscardQueueWithClear(scalarQueue, ctx, null);
						scalarQueue = null;
						s.cancel();
						unsubscribe();

						a.onError(e);
						return true;
					}
					else if (empty) {
						a.onComplete();
						return true;
					}
				}
			}

			return false;
		}

		void innerError(FlatMapInner<R> inner, Throwable e) {
			e = Operators.onNextInnerError(e, currentContext(), s);
			if(e != null) {
				if (Exceptions.addThrowable(ERROR, this, e)) {
					inner.done = true;
					if (!delayError) {
						done = true;
					}
					drain();
				}
				else {
					Operators.onErrorDropped(e, actual.currentContext());
				}
			}
			else {
				drain();
			}
		}

		boolean failOverflow(R v, Subscription toCancel){
			Throwable e = Operators.onOperatorError(toCancel,
					Exceptions.failWithOverflow(Exceptions.BACKPRESSURE_ERROR_QUEUE_FULL),
					v, actual.currentContext());

			if (!Exceptions.addThrowable(ERROR, this, e)) {
				Operators.onErrorDropped(e, actual.currentContext());
				return false;
			}
			return true;
		}

		void innerComplete(FlatMapInner<R> inner) {
			//FIXME temp. reduce the case to empty regular inners
//			if (wip == 0 && WIP.compareAndSet(this, 0, 1)) {
//				Queue<R> queue = inner.queue;
//				if (queue == null || queue.isEmpty()) {
//					remove(inner.index);
//
//					boolean d = done;
//					Queue<R> sq = scalarQueue;
//					boolean noSources = isEmpty();
//
//					if (checkTerminated(d,
//							noSources && (sq == null || sq.isEmpty()),
//							actual)) {
//						return;
//					}
//
//					s.request(1);
//					if (WIP.decrementAndGet(this) != 0) {
//						drainLoop();
//					}
//					return;
//				}
//			}
//			else {
				if (WIP.getAndIncrement(this) != 0) {
					return;
				}
//			}
			drainLoop();
		}

		Queue<R> getOrCreateInnerQueue(FlatMapInner<R> inner) {
			Queue<R> q = inner.queue;
			if (q == null) {
				q = innerQueueSupplier.get();
				inner.queue = q;
			}
			return q;
		}

	}

	static final class FlatMapInner<R>
			implements InnerConsumer<R>, Subscription {

		final FlatMapMain<?, R> parent;

		final int prefetch;

		final int limit;

		volatile Subscription s;
		@SuppressWarnings("rawtypes")
		static final AtomicReferenceFieldUpdater<FlatMapInner, Subscription> S =
				AtomicReferenceFieldUpdater.newUpdater(FlatMapInner.class,
						Subscription.class,
						"s");

		long produced;

		volatile Queue<R> queue;

		volatile boolean done;

		/**
		 * Represents the optimization mode of this inner subscriber.
		 */
		int sourceMode;

		int index;

		FlatMapInner(FlatMapMain<?, R> parent, int prefetch) {
			this.parent = parent;
			this.prefetch = prefetch;
//			this.limit = prefetch >> 2;
			this.limit = Operators.unboundedOrLimit(prefetch);
		}

		@Override
		public void onSubscribe(Subscription s) {
			if (Operators.setOnce(S, this, s)) {
				if (s instanceof Fuseable.QueueSubscription) {
					@SuppressWarnings("unchecked") Fuseable.QueueSubscription<R> f =
							(Fuseable.QueueSubscription<R>) s;
					int m = f.requestFusion(Fuseable.ANY | Fuseable.THREAD_BARRIER);
					if (m == Fuseable.SYNC) {
						sourceMode = Fuseable.SYNC;
						queue = f;
						done = true;
						parent.drain();
						return;
					}
					if (m == Fuseable.ASYNC) {
						sourceMode = Fuseable.ASYNC;
						queue = f;
					}
					// NONE is just fall-through as the queue will be created on demand
				}
				s.request(Operators.unboundedOrPrefetch(prefetch));
			}
		}

		@Override
		public void onNext(R t) {
			if (sourceMode == Fuseable.ASYNC) {
				parent.drain();
			}
			else {
				parent.tryEmit(this, t);
			}
		}

		@Override
		public void onError(Throwable t) {
			done = true;
			parent.innerError(this, t);
		}

		@Override
		public void onComplete() {
			// onComplete is practically idempotent so there is no risk due to subscription-race in async mode
			done = true;
			parent.innerComplete(this);
		}

		@Override
		public void request(long n) {
			long p = produced + n;
			if (p >= limit) {
				produced = 0L;
				s.request(p);
			}
			else {
				produced = p;
			}
		}

		@Override
		public Context currentContext() {
			return parent.currentContext();
		}

		@Override
		public void cancel() {
			Operators.terminate(S, this);
			Operators.onDiscardQueueWithClear(queue, parent.ctx, null);
		}

		@Override
		@Nullable
		public Object scanUnsafe(Attr key) {
			if (key == Attr.PARENT) return s;
			if (key == Attr.ACTUAL) return parent;
			if (key == Attr.TERMINATED) return done && (queue == null || queue.isEmpty());
			if (key == Attr.CANCELLED) return s == Operators.cancelledSubscription();
			if (key == Attr.BUFFERED) return queue == null ? 0 : queue.size();
			if (key == Attr.PREFETCH) return prefetch;

			return null;
		}
	}
}

abstract class FlatMapTracker<T> {

	volatile T[] array = empty();

	int[] free = FREE_EMPTY;

	long producerIndex;
	long consumerIndex;

	volatile int size;

	@SuppressWarnings("rawtypes")
	static final AtomicIntegerFieldUpdater<FlatMapTracker> SIZE =
			AtomicIntegerFieldUpdater.newUpdater(FlatMapTracker.class, "size");

	static final int[] FREE_EMPTY = new int[0];

	abstract T[] empty();

	abstract T[] terminated();

	abstract T[] newArray(int size);

	abstract void unsubscribeEntry(T entry);

	abstract void setIndex(T entry, int index);

	final void unsubscribe() {
		T[] a;
		T[] t = terminated();
		synchronized (this) {
			a = array;
			if (a == t) {
				return;
			}
			SIZE.lazySet(this, 0);
			free = null;
			array = t;
		}
		for (T e : a) {
			if (e != null) {
				unsubscribeEntry(e);
			}
		}
	}

	final T[] get() {
		return array;
	}

	final boolean add(T entry) {
		T[] a = array;
		if (a == terminated()) {
			return false;
		}
		synchronized (this) {
			a = array;
			if (a == terminated()) {
				return false;
			}

			int idx = pollFree();
			if (idx < 0) {
				int n = a.length;
				T[] b = n != 0 ? newArray(n << 1) : newArray(4);
				System.arraycopy(a, 0, b, 0, n);

				array = b;
				a = b;

				int m = b.length;
				int[] u = new int[m];
				for (int i = n + 1; i < m; i++) {
					u[i] = i;
				}
				free = u;
				consumerIndex = n + 1;
				producerIndex = m;

				idx = n;
			}
			setIndex(entry, idx);
			SIZE.lazySet(this, size); // make sure entry is released
			a[idx] = entry;
			SIZE.lazySet(this, size + 1);
		}
		return true;
	}

	final void remove(int index) {
		synchronized (this) {
			T[] a = array;
			if (a != terminated()) {
				a[index] = null;
				offerFree(index);
				SIZE.lazySet(this, size - 1);
			}
		}
	}

	int pollFree() {
		int[] a = free;
		int m = a.length - 1;
		long ci = consumerIndex;
		if (producerIndex == ci) {
			return -1;
		}
		int offset = (int) ci & m;
		consumerIndex = ci + 1;
		return a[offset];
	}

	void offerFree(int index) {
		int[] a = free;
		int m = a.length - 1;
		long pi = producerIndex;
		int offset = (int) pi & m;
		a[offset] = index;
		producerIndex = pi + 1;
	}

	final boolean isEmpty() {
		return size == 0;
	}
}