/*
 * 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.lang.reflect.Field;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.concurrent.locks.LockSupport;
import java.util.function.LongSupplier;
import java.util.function.Supplier;

import reactor.util.Logger;
import reactor.util.Loggers;
import reactor.util.annotation.Nullable;
import reactor.util.concurrent.Queues;
import reactor.util.concurrent.WaitStrategy;
import sun.misc.Unsafe;

import static java.util.Arrays.copyOf;

/**
 * Ring based store of reusable entries containing the data representing an event being exchanged between event producer
 * and ringbuffer consumers.
 * @param <E> implementation storing the data for sharing during exchange or parallel coordination of an event.
 *
 * This is an adaption of the original LMAX Disruptor RingBuffer code from
 * https://lmax-exchange.github.io/disruptor/.
 */
abstract class RingBuffer<E> implements LongSupplier {

	static <T> void addSequence(final T holder,
			final AtomicReferenceFieldUpdater<T, Sequence[]> updater,
			final Sequence sequence) {

		Sequence[] updatedSequences;
		Sequence[] currentSequences;

		do {
			currentSequences = updater.get(holder);
			updatedSequences = copyOf(currentSequences, currentSequences.length + 1);

			updatedSequences[currentSequences.length] = sequence;
		}
		while (!updater.compareAndSet(holder, currentSequences, updatedSequences));
	}

	private static <T> int countMatching(T[] values, final T toMatch) {
		int numToRemove = 0;
		for (T value : values) {
			if (value == toMatch) // Specifically uses identity
			{
				numToRemove++;
			}
		}
		return numToRemove;
	}

	static <T> boolean removeSequence(final T holder,
			final AtomicReferenceFieldUpdater<T, Sequence[]> sequenceUpdater,
			final Sequence sequence) {
		int numToRemove;
		Sequence[] oldSequences;
		Sequence[] newSequences;

		do {
			oldSequences = sequenceUpdater.get(holder);

			numToRemove = countMatching(oldSequences, sequence);

			if (0 == numToRemove) {
				break;
			}

			final int oldSize = oldSequences.length;
			newSequences = new Sequence[oldSize - numToRemove];

			for (int i = 0, pos = 0; i < oldSize; i++) {
				final Sequence testSequence = oldSequences[i];
				if (sequence != testSequence) {
					newSequences[pos++] = testSequence;
				}
			}
		}
		while (!sequenceUpdater.compareAndSet(holder, oldSequences, newSequences));

		return numToRemove != 0;
	}

	/**
	 * Set to -1 as sequence starting point
	 */
	static final long     INITIAL_CURSOR_VALUE = -1L;

	/**
	 * Create a new multiple producer RingBuffer with the specified wait strategy.
     *
See {@code MultiProducerRingBuffer}.
	 * @param <E> the element type
	 * @param factory used to create the events within the ring buffer.
	 * @param bufferSize number of elements to create within the ring buffer.
	 * @param waitStrategy used to determine how to wait for new elements to become available.
	 * @param spinObserver the Runnable to call on a spin loop wait
	 * @return the new RingBuffer instance
	 */
	static <E> RingBuffer<E> createMultiProducer(Supplier<E> factory,
			int bufferSize,
			WaitStrategy waitStrategy, Runnable spinObserver) {

		if (hasUnsafe()) {
			MultiProducerRingBuffer sequencer = new MultiProducerRingBuffer(bufferSize, waitStrategy, spinObserver);

			return new UnsafeRingBuffer<>(factory, sequencer);
		}
		else {
			throw new IllegalStateException("This JVM does not support sun.misc.Unsafe");
		}
	}

	/**
	 * Create a new single producer RingBuffer with the specified wait strategy.
     *
See {@code MultiProducerRingBuffer}.
	 * @param <E> the element type
	 * @param factory used to create the events within the ring buffer.
	 * @param bufferSize number of elements to create within the ring buffer.
	 * @param waitStrategy used to determine how to wait for new elements to become available.
	 * @return the new RingBuffer instance
	 */
	static <E> RingBuffer<E> createSingleProducer(Supplier<E> factory,
			int bufferSize,
			WaitStrategy waitStrategy) {
		return createSingleProducer(factory, bufferSize, waitStrategy, null);
	}

	/**
	 * Create a new single producer RingBuffer with the specified wait strategy.
     *
See {@code MultiProducerRingBuffer}.
     * @param <E> the element type
	 * @param factory used to create the events within the ring buffer.
	 * @param bufferSize number of elements to create within the ring buffer.
	 * @param waitStrategy used to determine how to wait for new elements to become available.
	 * @param spinObserver called each time the next claim is spinning and waiting for a slot
     * @return the new RingBuffer instance
	 */
	static <E> RingBuffer<E> createSingleProducer(Supplier<E> factory,
			int bufferSize,
			WaitStrategy waitStrategy,
			@Nullable Runnable spinObserver) {
		SingleProducerSequencer sequencer = new SingleProducerSequencer(bufferSize, waitStrategy, spinObserver);

		if (hasUnsafe() && Queues.isPowerOfTwo(bufferSize)) {
			return new UnsafeRingBuffer<>(factory, sequencer);
		}
		else {
			return new NotFunRingBuffer<>(factory, sequencer);
		}
	}

	/**
	 * Get the minimum sequence from an array of {@link Sequence}s.
	 *
	 * @param sequences to compare.
	 * @param minimum an initial default minimum.  If the array is empty this value will be returned.
	 *
	 * @return the minimum sequence found or Long.MAX_VALUE if the array is empty.
	 */
	static long getMinimumSequence(final Sequence[] sequences, long minimum) {
		for (int i = 0, n = sequences.length; i < n; i++) {
			long value = sequences[i].getAsLong();
			minimum = Math.min(minimum, value);
		}

		return minimum;
	}

	/**
	 * Get the minimum sequence from an array of {@link Sequence}s.
	 *
	 * @param excludeSequence to exclude from search.
	 * @param sequences to compare.
	 * @param minimum an initial default minimum.  If the array is empty this value will be returned.
	 *
	 * @return the minimum sequence found or Long.MAX_VALUE if the array is empty.
	 */
	static long getMinimumSequence(@Nullable Sequence excludeSequence, final Sequence[] sequences, long minimum) {
		for (int i = 0, n = sequences.length; i < n; i++) {
			if (excludeSequence == null || sequences[i] != excludeSequence) {
				long value = sequences[i].getAsLong();
				minimum = Math.min(minimum, value);
			}
		}

		return minimum;
	}

	/**
	 * Return the {@code sun.misc.Unsafe} instance if found on the classpath and can be used for acclerated
	 * direct memory access.
	 *
	 * @param <T> the Unsafe type
	 * @return the Unsafe instance
	 */
	@SuppressWarnings("unchecked")
	static <T> T getUnsafe() {
		return (T) UnsafeSupport.getUnsafe();
	}

	/**
	 * Calculate the log base 2 of the supplied integer, essentially reports the location of the highest bit.
	 *
	 * @param i Value to calculate log2 for.
	 *
	 * @return The log2 value
	 */
	static int log2(int i) {
		int r = 0;
		while ((i >>= 1) != 0) {
			++r;
		}
		return r;
	}

	/**
	 * @param init the initial value
	 *
	 * @return a safe or unsafe sequence set to the passed init value
	 */
	static Sequence newSequence(long init) {
		if (hasUnsafe()) {
			return new UnsafeSequence(init);
		}
		else {
			return new AtomicSequence(init);
		}
    }

	/**
	 * Add the specified gating sequence to this instance of the Disruptor.  It will safely and atomically be added to
	 * the list of gating sequences and not RESET to the current ringbuffer cursor.
	 * @param gatingSequence The sequences to add.
	 */
	abstract void addGatingSequence(Sequence gatingSequence);

	/**
	 * @return the fixed buffer size
	 */
	abstract int bufferSize();

	/**
	 *
Get the event for a given sequence in the RingBuffer.

	 *
	 *
This call has 2 uses.  Firstly use this call when publishing to a ring buffer. After calling {@link
	 * RingBuffer#next()} use this call to get hold of the preallocated event to fill with data before calling {@link
	 * RingBuffer#publish(long)}.

	 *
	 *
Secondly use this call when consuming data from the ring buffer.  After calling {@link
	 * Reader#waitFor)} call this method with any value greater than that your
	 * current consumer sequence
	 * and less than or equal to the value returned from the {@link Reader#waitFor)} method.

	 * @param sequence for the event
	 * @return the event for the given sequence
	 */
	abstract E get(long sequence);

	@Override
	public long getAsLong() {
		return getCursor();
	}

	/**
	 * Get the current cursor value for the ring buffer.  The actual value received will depend on the type of {@code
	 * RingBufferProducer} that is being used.
	 *

     * See {@code MultiProducerRingBuffer}.
     * See {@code SingleProducerSequencer}
	 * @return the current cursor value
	 */
	abstract long getCursor();

	/**
	 * Get the minimum sequence value from all of the gating sequences added to this ringBuffer.
	 * @return The minimum gating sequence or the cursor sequence if no sequences have been added.
	 */
	abstract long getMinimumGatingSequence();

	/**
	 * Get the minimum sequence value from all of the gating sequences added to this ringBuffer.
	 * @param sequence the target sequence
	 * @return The minimum gating sequence or the cursor sequence if no sequences have been added.
	 */
	abstract long getMinimumGatingSequence(Sequence sequence);

	/**
	 * Get the buffered count
	 * @return the buffered count
	 */
	abstract int getPending();

	/**
	 *
	 * @return the current list of read cursors
	 */
	Sequence[] getSequenceReceivers() {
		return getSequencer().getGatingSequences();
	}

	/**
	 * Create a new {@link Reader} to track
	 * which
	 * messages are available to be read
	 * from the ring buffer given a list of sequences to track.
	 * @return A sequence barrier that will track the ringbuffer.
	 * @see Reader
	 */
	abstract Reader newReader();

	/**
	 * Increment and return the next sequence for the ring buffer.  Calls of this method should ensure that they always
	 * publish the sequence afterward.  E.g.
	 *

	 * long sequence = ringBuffer.next();
	 * try {
	 *     Event e = ringBuffer.get(sequence);
	 *     // Do some work with the event.
	 * } finally {
	 *     ringBuffer.publish(sequence);
	 * }
	 *

	 * @return The next sequence to publish to.
	 * @see RingBuffer#publish(long)
	 * @see RingBuffer#get(long)
	 */
	abstract long next();

	/**
	 * The same functionality as {@link RingBuffer#next()}, but allows the caller to claim the next n sequences.
	 *

     * See {@code RingBufferProducer.next(int)}
	 * @param n number of slots to claim
	 * @return sequence number of the highest slot claimed
	 */
	abstract long next(int n);

	/**
	 * Publish the specified sequence.  This action marks this particular message as being available to be read.
	 * @param sequence the sequence to publish.
	 */
	abstract void publish(long sequence);
	/**
	 * Remove the specified sequence from this ringBuffer.
	 * @param sequence to be removed.
	 * @return <tt>true</tt> if this sequence was found, <tt>false</tt> otherwise.
	 */
	abstract boolean removeGatingSequence(Sequence sequence);

	abstract RingBufferProducer getSequencer();/*

	/**
	 * Return {@code true} if {@code sun.misc.Unsafe} was found on the classpath and can be used for accelerated
	 * direct memory access.
	 * @return true if unsafe is present
	 */
	static boolean hasUnsafe() {
		return HAS_UNSAFE;
	}

	static boolean hasUnsafe0() {
		return UnsafeSupport.hasUnsafe();
	}

	private static final boolean HAS_UNSAFE = hasUnsafe0();

	/**
	 *
Concurrent sequence class used for tracking the progress of
	 * the ring buffer and event processors.  Support a number
	 * of concurrent operations including CAS and order writes.
	 *
	 *
Also attempts to be more efficient with regards to false
	 * sharing by adding padding around the volatile field.
	 */
	interface Sequence extends LongSupplier
	{
	    long INITIAL_VALUE = INITIAL_CURSOR_VALUE;

	    /**
	     * Perform an ordered write of this sequence.  The intent is
	     * a Store/Store barrier between this write and any previous
	     * store.
	     *
	     * @param value The new value for the sequence.
	     */
	    void set(long value);

	    /**
	     * Perform a compare and set operation on the sequence.
	     *
	     * @param expectedValue The expected current value.
	     * @param newValue The value to update to.
	     * @return true if the operation succeeds, false otherwise.
	     */
	    boolean compareAndSet(long expectedValue, long newValue);

	}

	/**
	 * Used for Gating ringbuffer consumers on a cursor sequence and optional dependent ringbuffer consumer(s),
	 * using the given WaitStrategy.
	 */
	static final class Reader {
	    private final WaitStrategy waitStrategy;
	    private volatile boolean alerted = false;
	    private final Sequence cursorSequence;
	    private final RingBufferProducer  sequenceProducer;

	    Reader(final RingBufferProducer sequenceProducer,
	                           final WaitStrategy waitStrategy,
	                           final Sequence cursorSequence) {
	        this.sequenceProducer = sequenceProducer;
	        this.waitStrategy = waitStrategy;
	        this.cursorSequence = cursorSequence;
	    }

	    /**
	     * Wait for the given sequence to be available for consumption.
	     *
	     * @param consumer a spin observer to invoke when nothing is available to read
	     * @param sequence to wait for
	     * @return the sequence up to which is available
	     * @throws InterruptedException if the thread needs awaking on a condition variable.
	     */
	    long waitFor(final long sequence, Runnable consumer)
			    throws InterruptedException {
		    if (alerted)
		    {
			    WaitStrategy.alert();
		    }

	        long availableSequence = waitStrategy.waitFor(sequence, cursorSequence, consumer);

	        if (availableSequence < sequence) {
	            return availableSequence;
	        }

	        return sequenceProducer.getHighestPublishedSequence(sequence, availableSequence);
	    }
	    /**
	         * The current alert status for the barrier.
	         *
	         * @return true if in alert otherwise false.
	         */
	    boolean isAlerted() {
	        return alerted;
	    }

	    /**
	         * Alert the ringbuffer consumers of a status change and stay in this status until cleared.
	         */
	    void alert() {
	        alerted = true;
	        waitStrategy.signalAllWhenBlocking();
	    }

	    /**
	         * Signal the ringbuffer consumers.
	         */
	    void signal() {
	        waitStrategy.signalAllWhenBlocking();
	    }

	    /**
	         * Clear the current alert status.
	         */
	    void clearAlert() {
	        alerted = false;
	    }
	}
}

// UnsafeSupport static initialization is derived from Netty's PlatformDependent0
// static initialization, the original licence of which is included below, verbatim.
// Modifications to the source material are:
//   - a shorter amount of checks and fields (focusing on Unsafe and buffer address)
//   - modifications to the logging messages and their level
/*
 * Copyright 2013 The Netty Project
 *
 * The Netty Project licenses this file to you 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.
 */
enum  UnsafeSupport {
	;

	static final Logger logger = Loggers.getLogger(UnsafeSupport.class);

	static {
		String javaSpecVersion = System.getProperty("java.specification.version");
		logger.debug("Starting UnsafeSupport init in Java " + javaSpecVersion);

		ByteBuffer direct = ByteBuffer.allocateDirect(1);
		Unsafe unsafe;

		// Get an Unsafe instance first, via the (still legit as of Java 9)
		// deep reflection trick on theUnsafe Field
		Object maybeUnsafe;
		try {
			final Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
			unsafeField.setAccessible(true);
			// the unsafe instance
			maybeUnsafe = unsafeField.get(null);
		} catch (NoSuchFieldException | SecurityException | IllegalAccessException e) {
			maybeUnsafe = e;
		}

		// the conditional check here can not be replaced with checking that maybeUnsafe
		// is an instanceof Unsafe and reversing the if and else blocks; this is because an
		// instanceof check against Unsafe will trigger a class load and we might not have
		// the runtime permission accessClassInPackage.sun.misc
		if (maybeUnsafe instanceof Throwable) {
			unsafe = null;
			logger.debug("Unsafe unavailable - Could not get it via Field sun.misc.Unsafe.theUnsafe", (Throwable) maybeUnsafe);
		} else {
			unsafe = (Unsafe) maybeUnsafe;
			logger.trace("sun.misc.Unsafe.theUnsafe ok");
		}

		// ensure the unsafe supports all necessary methods to work around the mistake in the latest OpenJDK
		// https://github.com/netty/netty/issues/1061
		// https://www.mail-archive.com/jdk6-dev@openjdk.java.net/msg00698.html
		if (unsafe != null) {
			final Unsafe finalUnsafe = unsafe;
			Object maybeException;
			try {
				finalUnsafe.getClass().getDeclaredMethod(
						"copyMemory", Object.class, long.class, Object.class, long.class, long.class);
				maybeException = null;
			} catch (NoSuchMethodException | SecurityException e) {
				maybeException =  e;
			}

			if (maybeException == null) {
				logger.trace("sun.misc.Unsafe.copyMemory ok");
			} else {
				unsafe = null;
				logger.debug("Unsafe unavailable - failed on sun.misc.Unsafe.copyMemory", (Throwable) maybeException);
			}
		}

		// finally check the Buffer#address
		if (unsafe != null) {
			final Unsafe finalUnsafe = unsafe;
			Object maybeAddressField;
			try {
				final Field field = Buffer.class.getDeclaredField("address");

				// Use Unsafe to read value of the address field.
				// This way it will not fail on JDK9+ which forbids changing the
				// access level via reflection.
				final long offset = finalUnsafe.objectFieldOffset(field);
				final long heapAddress = finalUnsafe.getLong(ByteBuffer.allocate(1), offset);
				final long directAddress = finalUnsafe.getLong(direct, offset);

				if (heapAddress != 0 && "1.8".equals(javaSpecVersion)) {
					maybeAddressField = new IllegalStateException("A heap buffer must have 0 address in Java 8, got " + heapAddress);
				}
				else if (heapAddress == 0 && !"1.8".equals(javaSpecVersion)) {
					maybeAddressField = new IllegalStateException("A heap buffer must have non-zero address in Java " + javaSpecVersion);
				}
				else if (directAddress == 0) {
					maybeAddressField = new IllegalStateException("A direct buffer must have non-zero address");
				}
				else {
					maybeAddressField = field;
				}

			} catch (NoSuchFieldException | SecurityException e) {
				maybeAddressField = e;
			}

			if (maybeAddressField instanceof Throwable) {
				logger.debug("Unsafe unavailable - failed on java.nio.Buffer.address", (Throwable) maybeAddressField);

				// If we cannot access the address of a direct buffer, there's no point in using unsafe.
				// Let's just pretend unsafe is unavailable for overall simplicity.
				unsafe = null;
			}
			else {
				logger.trace("java.nio.Buffer.address ok");
				logger.debug("Unsafe is available");
			}
		}

		UNSAFE = unsafe;
	}

	static Unsafe getUnsafe(){
		return UNSAFE;
	}

	static boolean hasUnsafe() {
		return UNSAFE != null;
	}

	private static final Unsafe UNSAFE;
}
/**
 * Base class for the various sequencer types (single/multi).  Provides common functionality like the management of
 * gating sequences (add/remove) and ownership of the current cursor.
 */
abstract class RingBufferProducer {

	static final AtomicReferenceFieldUpdater<RingBufferProducer, RingBuffer.Sequence[]>
			SEQUENCE_UPDATER = AtomicReferenceFieldUpdater.newUpdater(RingBufferProducer.class, RingBuffer.Sequence[].class,
			"gatingSequences");

	final Runnable     spinObserver;
	final int          bufferSize;
	final WaitStrategy waitStrategy;
	final    RingBuffer.Sequence   cursor          = RingBuffer.newSequence(RingBuffer.INITIAL_CURSOR_VALUE);
	volatile RingBuffer.Sequence[] gatingSequences = new RingBuffer.Sequence[0];

	/**
	 * Create with the specified buffer size and wait strategy.
	 *
	 * @param bufferSize The total number of entries, must be a positive power of 2.
	 * @param waitStrategy The {@link WaitStrategy} to use.
	 * @param spinObserver an iteration observer
	 */
	RingBufferProducer(int bufferSize, WaitStrategy waitStrategy, @Nullable Runnable spinObserver) {
		this.spinObserver = spinObserver;
		this.bufferSize = bufferSize;
		this.waitStrategy = waitStrategy;
	}

	/**
	 * Get the current cursor value.
	 *
	 * @return current cursor value
	 */
	final long getCursor() {
		return cursor.getAsLong();
	}

	/**
	 * The capacity of the data structure to hold entries.
	 *
	 * @return the size of the RingBuffer.
	 */
	final int getBufferSize() {
		return bufferSize;
	}

	/**
	 * Add the specified gating sequences to this instance of the Disruptor.  They will
	 * safely and atomically added to the list of gating sequences.
	 *
	 * @param gatingSequence The sequences to add.
	 */
	final void addGatingSequence(RingBuffer.Sequence gatingSequence) {
		RingBuffer.addSequence(this, SEQUENCE_UPDATER, gatingSequence);
	}

	/**
	 * Remove the specified sequence from this sequencer.
	 *
	 * @param sequence to be removed.
	 * @return <tt>true</tt> if this sequence was found, <tt>false</tt> otherwise.
	 */
	boolean removeGatingSequence(RingBuffer.Sequence sequence) {
		return RingBuffer.removeSequence(this, SEQUENCE_UPDATER, sequence);
	}

	/**
	 * Get the minimum sequence value from all of the gating sequences
	 * added to this ringBuffer.
	 *
	 * @param excludeSequence to exclude from search
	 * @return The minimum gating sequence or the cursor sequence if
	 * no sequences have been added.
	 */
	long getMinimumSequence(@Nullable RingBuffer.Sequence excludeSequence) {
		return RingBuffer.getMinimumSequence(excludeSequence, gatingSequences, cursor.getAsLong());
	}

	/**
	 * Create a new {@link RingBuffer.Reader} to be used by an EventProcessor to track which messages
	 * are available to be read from the ring buffer
	 *
	 * @see RingBuffer.Reader
	 * @return A sequence barrier that will track the specified sequences.
	 */
	RingBuffer.Reader newBarrier() {
		return new RingBuffer.Reader(this, waitStrategy, cursor);
	}

	/**
	 * Get the highest sequence number that can be safely read from the ring buffer.  Depending
	 * on the implementation of the Sequencer this call may need to get a number of values
	 * in the Sequencer.  The get will range from nextSequence to availableSequence.  If
	 * there are no available values >= nextSequence the return value will be
	 * nextSequence - 1.  To work correctly a consumer should pass a value that
	 * it 1 higher than the last sequence that was successfully processed.
	 *
	 * @param nextSequence The sequence to start scanning from.
	 * @param availableSequence The sequence to get to.
	 * @return The highest value that can be safely read, will be at least nextSequence - 1.
	 */
	abstract long getHighestPublishedSequence(long nextSequence, long availableSequence);

	/**
	 * Get the pending capacity for this sequencer.
	 * @return The number of slots pending consuming.
	 */
	abstract long getPending();

	/**
	 * Claim the next event in sequence for publishing.
	 * @return the claimed sequence value
	 */
	abstract long next();

	/**
	 * Claim the next n events in sequence for publishing.  This is for batch event producing.  Using batch producing
	 * requires a little care and some math.
	 *

	 * int n = 10;
	 * long hi = sequencer.next(n);
	 * long lo = hi - (n - 1);
	 * for (long sequence = lo; sequence <= hi; sequence++) {
	 *     // Do work.
	 * }
	 * sequencer.publish(lo, hi);
	 *

	 *
	 * @param n the number of sequences to claim
	 * @return the highest claimed sequence value
	 */
	abstract long next(int n);

	/**
	 * Publishes a sequence. Call when the event has been filled.
	 *
	 * @param sequence the sequence number to be published
	 */
	abstract void publish(long sequence);

	/**
	 *
	 * @return the gating sequences array
	 */
	RingBuffer.Sequence[] getGatingSequences() {
		return gatingSequences;
	}
}

abstract class SingleProducerSequencerPad extends RingBufferProducer
{
	protected long p1, p2, p3, p4, p5, p6, p7;
	SingleProducerSequencerPad(int bufferSize, WaitStrategy waitStrategy, @Nullable Runnable spinObserver)
	{
		super(bufferSize, waitStrategy, spinObserver);
	}
}

abstract class SingleProducerSequencerFields extends SingleProducerSequencerPad
{
	SingleProducerSequencerFields(int bufferSize, WaitStrategy waitStrategy, @Nullable Runnable spinObserver)
	{
		super(bufferSize, waitStrategy, spinObserver);
	}

	/** Set to -1 as sequence starting point */
	protected long nextValue = RingBuffer.Sequence.INITIAL_VALUE;
	protected long cachedValue = RingBuffer.Sequence.INITIAL_VALUE;
}

/**
 *
Coordinator for claiming sequences for access to a data structure while tracking dependent {@link RingBuffer.Sequence}s.
 * Not safe for use from multiple threads as it does not implement any barriers.

 *
 *
Note on {@code RingBufferProducer.getCursor()}:  With this sequencer the cursor value is updated after the call
 * to {@code RingBufferProducer.publish(long)} is made.
 */

final class SingleProducerSequencer extends SingleProducerSequencerFields {
	protected long p1, p2, p3, p4, p5, p6, p7;

	/**
	 * Construct a Sequencer with the selected wait strategy and buffer size.
	 *
	 * @param bufferSize the size of the buffer that this will sequence over.
	 * @param waitStrategy for those waiting on sequences.
	 * @param spinObserver the runnable to call on a spin-wait
	 */
	SingleProducerSequencer(int bufferSize, final WaitStrategy waitStrategy, @Nullable Runnable spinObserver) {
		super(bufferSize, waitStrategy, spinObserver);
	}

	/**
	 * See {@code RingBufferProducer.next()}.
	 */
	@Override
	long next() {
		return next(1);
	}

	/**
	 * See {@code RingBufferProducer.next(int)}.
	 */
	@Override
	long next(int n) {
		long nextValue = this.nextValue;

		long nextSequence = nextValue + n;
		long wrapPoint = nextSequence - bufferSize;
		long cachedGatingSequence = this.cachedValue;

		if (wrapPoint > cachedGatingSequence || cachedGatingSequence > nextValue)
		{
			long minSequence;
			while (wrapPoint > (minSequence = RingBuffer.getMinimumSequence(gatingSequences, nextValue)))
			{
				if(spinObserver != null) {
					spinObserver.run();
				}
				LockSupport.parkNanos(1L); // TODO: Use waitStrategy to spin?
			}

			this.cachedValue = minSequence;
		}

		this.nextValue = nextSequence;

		return nextSequence;
	}

	/**
	 * See {@code RingBufferProducer.producerCapacity()}.
	 */
	@Override
	public long getPending() {
		long nextValue = this.nextValue;

		long consumed = RingBuffer.getMinimumSequence(gatingSequences, nextValue);
		long produced = nextValue;
		return produced - consumed;
	}

	/**
	 * See {@code RingBufferProducer.publish(long)}.
	 */
	@Override
	void publish(long sequence) {
		cursor.set(sequence);
		waitStrategy.signalAllWhenBlocking();
	}

	@Override
	long getHighestPublishedSequence(long lowerBound, long availableSequence) {
		return availableSequence;
	}
}

abstract class NotFunRingBufferFields<E> extends RingBuffer<E>
{
	private final   long               indexMask;
	private final   Object[]           entries;
	final int                bufferSize;
	final RingBufferProducer sequenceProducer;

	NotFunRingBufferFields(Supplier<E> eventFactory,
			RingBufferProducer sequenceProducer)
	{
		this.sequenceProducer = sequenceProducer;
		this.bufferSize = sequenceProducer.getBufferSize();
		this.indexMask = bufferSize - 1;
		this.entries   = new Object[sequenceProducer.getBufferSize()];
		fill(eventFactory);
	}

	private void fill(Supplier<E> eventFactory)
	{
		for (int i = 0; i < bufferSize; i++)
		{
			entries[i] = eventFactory.get();
		}
	}

	@SuppressWarnings("unchecked")
	final E elementAt(long sequence)
	{
		return (E) entries[(int) (sequence & indexMask)];
	}
}

/**
 * Ring based store of reusable entries containing the data representing
 * an event being exchanged between event producer and ringbuffer consumers.
 *
 * @param <E> implementation storing the data for sharing during exchange or parallel coordination of an event.
 */
final class NotFunRingBuffer<E> extends NotFunRingBufferFields<E>
{
	/**
	 * Construct a RingBuffer with the full option set.
	 *
	 * @param eventFactory to newInstance entries for filling the RingBuffer
	 * @param sequenceProducer sequencer to handle the ordering of events moving through the RingBuffer.
	 * @throws IllegalArgumentException if bufferSize is less than 1 or not a power of 2
	 */
	NotFunRingBuffer(Supplier<E> eventFactory,
			RingBufferProducer sequenceProducer)
	{
		super(eventFactory, sequenceProducer);
	}

	@Override
	E get(long sequence)
	{
		return elementAt(sequence);
	}

	@Override
	long next()
	{
		return next(1);
	}

	@Override
	long next(int n)
	{
		return sequenceProducer.next(n);
	}

	@Override
	void addGatingSequence(Sequence gatingSequence)
	{
		sequenceProducer.addGatingSequence(gatingSequence);
	}

	@Override
	long getMinimumGatingSequence()
	{
		return getMinimumGatingSequence(null);
	}

	@Override
	long getMinimumGatingSequence(@Nullable Sequence sequence)
	{
		return sequenceProducer.getMinimumSequence(sequence);
	}

	@Override
	boolean removeGatingSequence(Sequence sequence)
	{
		return sequenceProducer.removeGatingSequence(sequence);
	}

	@Override
	Reader newReader()
	{
		return sequenceProducer.newBarrier();
	}

	@Override
	long getCursor()
	{
		return sequenceProducer.getCursor();
	}

	@Override
	int bufferSize()
	{
		return bufferSize;
	}

	@Override
	void publish(long sequence)
	{
		sequenceProducer.publish(sequence);
	}

	@Override
	int getPending() {
		return (int)sequenceProducer.getPending();
	}

	@Override
	RingBufferProducer getSequencer() {
		return sequenceProducer;
	}
}
final class AtomicSequence extends RhsPadding implements LongSupplier, RingBuffer.Sequence
{

	private static final AtomicLongFieldUpdater<Value> UPDATER =
			AtomicLongFieldUpdater.newUpdater(Value.class, "value");

	/**
	 * Create a sequence with a specified initial value.
	 *
	 * @param initialValue The initial value for this sequence.
	 */
	AtomicSequence(final long initialValue)
	{
		UPDATER.lazySet(this, initialValue);
	}

	@Override
	public long getAsLong()
	{
		return value;
	}

	@Override
	public void set(final long value)
	{
		UPDATER.set(this, value);
	}

	@Override
	public boolean compareAndSet(final long expectedValue, final long newValue)
	{
		return UPDATER.compareAndSet(this, expectedValue, newValue);
	}
}
abstract class RingBufferPad<E> extends RingBuffer<E>
{
	protected long p1, p2, p3, p4, p5, p6, p7;
}

abstract class RingBufferFields<E> extends RingBufferPad<E>
{
	private static final int  BUFFER_PAD;
	private static final long REF_ARRAY_BASE;
	private static final int  REF_ELEMENT_SHIFT;
	private static final Unsafe UNSAFE = RingBuffer.getUnsafe();

	static {
		final int scale = UNSAFE.arrayIndexScale(Object[].class);
		if (4 == scale) {
			REF_ELEMENT_SHIFT = 2;
		} else if (8 == scale) {
			REF_ELEMENT_SHIFT = 3;
		} else {
			throw new IllegalStateException("Unknown pointer size");
		}
		BUFFER_PAD = 128 / scale;
		// Including the buffer pad in the array base offset
		REF_ARRAY_BASE = UNSAFE.arrayBaseOffset(Object[].class) + (BUFFER_PAD << REF_ELEMENT_SHIFT);
	}

	private final   long               indexMask;
	private final   Object[]           entries;
	protected final int                bufferSize;
	protected final RingBufferProducer sequenceProducer;

	RingBufferFields(Supplier<E> eventFactory,
			RingBufferProducer sequenceProducer) {
		this.sequenceProducer = sequenceProducer;
		this.bufferSize = sequenceProducer.getBufferSize();
		this.indexMask = bufferSize - 1;
		this.entries   = new Object[sequenceProducer.getBufferSize() + 2 * BUFFER_PAD];
		fill(eventFactory);
	}

	private void fill(Supplier<E> eventFactory)
	{
		for (int i = 0; i < bufferSize; i++)
		{
			entries[BUFFER_PAD + i] = eventFactory.get();
		}
	}

	@SuppressWarnings("unchecked")
	final E elementAt(long sequence)
	{
		return (E) UNSAFE.getObject(entries, REF_ARRAY_BASE + ((sequence & indexMask) << REF_ELEMENT_SHIFT));
	}
}

/**
 * Ring based store of reusable entries containing the data representing
 * an event being exchanged between event producer and ringbuffer consumers.
 *
 * @param <E> implementation storing the data for sharing during exchange or parallel coordination of an event.
 */
final class UnsafeRingBuffer<E> extends RingBufferFields<E>
{
	protected long p1, p2, p3, p4, p5, p6, p7;

	/**
	 * Construct a RingBuffer with the full option set.
	 *
	 * @param eventFactory to newInstance entries for filling the RingBuffer
	 * @param sequenceProducer sequencer to handle the ordering of events moving through the RingBuffer.
	 * @throws IllegalArgumentException if bufferSize is less than 1 or not a power of 2
	 */
	UnsafeRingBuffer(Supplier<E> eventFactory,
			RingBufferProducer sequenceProducer)
	{
		super(eventFactory, sequenceProducer);
	}

	@Override
	E get(long sequence)
	{
		return elementAt(sequence);
	}

	@Override
	long next()
	{
		return sequenceProducer.next();
	}

	@Override
	long next(int n)
	{
		return sequenceProducer.next(n);
	}

	@Override
	void addGatingSequence(Sequence gatingSequence)
	{
		sequenceProducer.addGatingSequence(gatingSequence);
	}

	@Override
	long getMinimumGatingSequence()
	{
		return getMinimumGatingSequence(null);
	}

	@Override
	long getMinimumGatingSequence(@Nullable Sequence sequence)
	{
		return sequenceProducer.getMinimumSequence(sequence);
	}

	@Override
	boolean removeGatingSequence(Sequence sequence)
	{
		return sequenceProducer.removeGatingSequence(sequence);
	}

	@Override
	Reader newReader()
	{
		return sequenceProducer.newBarrier();
	}

	@Override
	long getCursor()
	{
		return sequenceProducer.getCursor();
	}

	@Override
	int bufferSize()
	{
		return bufferSize;
	}

	@Override
	void publish(long sequence)
	{
		sequenceProducer.publish(sequence);
	}

	@Override
	int getPending() {
		return (int)sequenceProducer.getPending();
	}

	@Override
	RingBufferProducer getSequencer() {
		return sequenceProducer;
	}

}

class LhsPadding
{
	protected long p1, p2, p3, p4, p5, p6, p7;
}

class Value extends LhsPadding
{
	protected volatile long value;
}

class RhsPadding extends Value
{
	protected long p9, p10, p11, p12, p13, p14, p15;
}

/**
 *
Concurrent sequence class used for tracking the progress of
 * the ring buffer and event processors.  Support a number
 * of concurrent operations including CAS and order writes.
 *
 *
Also attempts to be more efficient with regards to false
 * sharing by adding padding around the volatile field.
 */
final class UnsafeSequence extends RhsPadding implements RingBuffer.Sequence, LongSupplier
{
	private static final Unsafe UNSAFE;
	private static final long VALUE_OFFSET;

	static
	{
		UNSAFE = RingBuffer.getUnsafe();
		try
		{
			VALUE_OFFSET = UNSAFE.objectFieldOffset(Value.class.getDeclaredField("value"));
		}
		catch (final Exception e)
		{
			throw new RuntimeException(e);
		}
	}

	/**
	 * Create a sequence with a specified initial value.
	 *
	 * @param initialValue The initial value for this sequence.
	 */
	UnsafeSequence(final long initialValue)
	{
		UNSAFE.putOrderedLong(this, VALUE_OFFSET, initialValue);
	}

	@Override
	public long getAsLong()
	{
		return value;
	}

	@Override
	public void set(final long value)
	{
		UNSAFE.putOrderedLong(this, VALUE_OFFSET, value);
	}

	@Override
	public boolean compareAndSet(final long expectedValue, final long newValue)
	{
		return UNSAFE.compareAndSwapLong(this, VALUE_OFFSET, expectedValue, newValue);
	}

}
/**
 *
Coordinator for claiming sequences for access to a data structure while tracking dependent {@link RingBuffer.Sequence}s.
 * Suitable for use for sequencing across multiple publisher threads.

 *
 *

 *
Note on {@code RingBufferProducer.getCursor()}:  With this sequencer the cursor value is updated after the call
 * to {@code RingBufferProducer.next()}, to determine the highest available sequence that can be read, then
 * {@code RingBufferProducer.getHighestPublishedSequence(long, long)} should be used.
 */
final class MultiProducerRingBuffer extends RingBufferProducer
{
	private static final Unsafe UNSAFE = RingBuffer.getUnsafe();
	private static final long   BASE   = UNSAFE.arrayBaseOffset(int[].class);
	private static final long   SCALE  = UNSAFE.arrayIndexScale(int[].class);

	private final RingBuffer.Sequence gatingSequenceCache = new UnsafeSequence(RingBuffer.INITIAL_CURSOR_VALUE);

	// availableBuffer tracks the state of each ringbuffer slot
	// see below for more details on the approach
	private final int[] availableBuffer;
	private final int   indexMask;
	private final int   indexShift;

	/**
	 * Construct a Sequencer with the selected wait strategy and buffer size.
	 *
	 * @param bufferSize the size of the buffer that this will sequence over.
	 * @param waitStrategy for those waiting on sequences.
	 */
	MultiProducerRingBuffer(int bufferSize, final WaitStrategy waitStrategy, Runnable spinObserver) {
		super(bufferSize, waitStrategy, spinObserver);
		availableBuffer = new int[bufferSize];
		indexMask = bufferSize - 1;
		indexShift = RingBuffer.log2(bufferSize);
		initialiseAvailableBuffer();
	}

	/**
	 * See {@code RingBufferProducer.next()}.
	 */
	@Override
	long next()
	{
		return next(1);
	}

	/**
	 * See {@code RingBufferProducer.next(int)}.
	 */
	@Override
	long next(int n)
	{
		long current;
		long next;

		do
		{
			current = cursor.getAsLong();
			next = current + n;

			long wrapPoint = next - bufferSize;
			long cachedGatingSequence = gatingSequenceCache.getAsLong();

			if (wrapPoint > cachedGatingSequence || cachedGatingSequence > current)
			{
				long gatingSequence = RingBuffer.getMinimumSequence(gatingSequences, current);

				if (wrapPoint > gatingSequence)
				{
					if(spinObserver != null) {
						spinObserver.run();
					}
					LockSupport.parkNanos(1); // TODO, should we spin based on the wait strategy?
					continue;
				}

				gatingSequenceCache.set(gatingSequence);
			}
			else if (cursor.compareAndSet(current, next))
			{
				break;
			}
		}
		while (true);

		return next;
	}

	/**
	 * See {@code RingBufferProducer.producerCapacity()}.
	 */
	@Override
	long getPending()
	{
		long consumed = RingBuffer.getMinimumSequence(gatingSequences, cursor.getAsLong());
		long produced = cursor.getAsLong();
		return produced - consumed;
	}

	private void initialiseAvailableBuffer()
	{
		for (int i = availableBuffer.length - 1; i != 0; i--)
		{
			setAvailableBufferValue(i, -1);
		}

		setAvailableBufferValue(0, -1);
	}

	/**
	 * See {@code RingBufferProducer.publish(long)}.
	 */
	@Override
	void publish(final long sequence)
	{
		setAvailable(sequence);
		waitStrategy.signalAllWhenBlocking();
	}

	/**
	 * The below methods work on the availableBuffer flag.
	 *
	 * The prime reason is to avoid a shared sequence object between publisher threads.
	 * (Keeping single pointers tracking start and end would require coordination
	 * between the threads).
	 *
	 * --  Firstly we have the constraint that the delta between the cursor and minimum
	 * gating sequence will never be larger than the buffer size (the code in
	 * next/tryNext in the Sequence takes care of that).
	 * -- Given that; take the sequence value and mask off the lower portion of the
	 * sequence as the index into the buffer (indexMask). (aka modulo operator)
	 * -- The upper portion of the sequence becomes the value to check for availability.
	 * ie: it tells us how many times around the ring buffer we've been (aka division)
	 * -- Because we can't wrap without the gating sequences moving forward (i.e. the
	 * minimum gating sequence is effectively our last available position in the
	 * buffer), when we have new data and successfully claimed a slot we can simply
	 * write over the top.
	 */
	private void setAvailable(final long sequence)
	{
		setAvailableBufferValue(calculateIndex(sequence), calculateAvailabilityFlag(sequence));
	}

	private void setAvailableBufferValue(int index, int flag)
	{
		long bufferAddress = (index * SCALE) + BASE;
		UNSAFE.putOrderedInt(availableBuffer, bufferAddress, flag);
	}

	/**
	 * See {@code RingBufferProducer.isAvailable(long)}
	 */
	boolean isAvailable(long sequence)
	{
		int index = calculateIndex(sequence);
		int flag = calculateAvailabilityFlag(sequence);
		long bufferAddress = (index * SCALE) + BASE;
		return UNSAFE.getIntVolatile(availableBuffer, bufferAddress) == flag;
	}

	@Override
	long getHighestPublishedSequence(long lowerBound, long availableSequence)
	{
		for (long sequence = lowerBound; sequence <= availableSequence; sequence++)
		{
			if (!isAvailable(sequence))
			{
				return sequence - 1;
			}
		}

		return availableSequence;
	}

	private int calculateAvailabilityFlag(final long sequence)
	{
		return (int) (sequence >>> indexShift);
	}

	private int calculateIndex(final long sequence)
	{
		return ((int) sequence) & indexMask;
	}
}


/*
 * 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.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.function.Supplier;

import reactor.core.Disposable;
import reactor.core.Scannable;

/**
 * Scheduler that works with a single-threaded ScheduledExecutorService and is suited for
 * same-thread work (like an event dispatch thread). This scheduler is time-capable (can
 * schedule with delay / periodically).
 */
final class SingleScheduler implements Scheduler, Supplier<ScheduledExecutorService>,
                                       Scannable {

	static final AtomicLong COUNTER       = new AtomicLong();

	final ThreadFactory factory;

	volatile ScheduledExecutorService executor;
	static final AtomicReferenceFieldUpdater<SingleScheduler, ScheduledExecutorService> EXECUTORS =
			AtomicReferenceFieldUpdater.newUpdater(SingleScheduler.class,
					ScheduledExecutorService.class,
					"executor");

	static final ScheduledExecutorService TERMINATED;

	static {
		TERMINATED = Executors.newSingleThreadScheduledExecutor();
		TERMINATED.shutdownNow();
	}

	SingleScheduler(ThreadFactory factory) {
		this.factory = factory;
		init();
	}

	/**
	 * Instantiates the default {@link ScheduledExecutorService} for the SingleScheduler
	 * ({@code Executors.newScheduledThreadPoolExecutor} with core and max pool size of 1).
	 */
	@Override
	public ScheduledExecutorService get() {
		ScheduledThreadPoolExecutor e = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(1, this.factory);
		e.setRemoveOnCancelPolicy(true);
		e.setMaximumPoolSize(1);
		return e;
	}

	private void init() {
		EXECUTORS.lazySet(this, Schedulers.decorateExecutorService(this, this.get()));
	}

	@Override
	public boolean isDisposed() {
		return executor == TERMINATED;
	}

	@Override
	public void start() {
		//TODO SingleTimedScheduler didn't implement start, check if any particular reason?
		ScheduledExecutorService b = null;
		for (; ; ) {
			ScheduledExecutorService a = executor;
			if (a != TERMINATED) {
				if (b != null) {
					b.shutdownNow();
				}
				return;
			}

			if (b == null) {
				b = Schedulers.decorateExecutorService(this, this.get());
			}

			if (EXECUTORS.compareAndSet(this, a, b)) {
				return;
			}
		}
	}

	@Override
	public void dispose() {
		ScheduledExecutorService a = executor;
		if (a != TERMINATED) {
			a = EXECUTORS.getAndSet(this, TERMINATED);
			if (a != TERMINATED) {
				a.shutdownNow();
			}
		}
	}

	@Override
	public Disposable schedule(Runnable task) {
		return Schedulers.directSchedule(executor, task, 0L, TimeUnit.MILLISECONDS);
	}

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

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

	@Override
	public String toString() {
		StringBuilder ts = new StringBuilder(Schedulers.SINGLE)
				.append('(');
		if (factory instanceof ReactorThreadFactory) {
			ts.append('\"').append(((ReactorThreadFactory) factory).get()).append('\"');
		}
		return ts.append(')').toString();
	}

	@Override
	public Object scanUnsafe(Attr key) {
		if (key == Attr.TERMINATED || key == Attr.CANCELLED) return isDisposed();
		if (key == Attr.NAME) return this.toString();
		if (key == Attr.CAPACITY || key == Attr.BUFFERED) return 1; //BUFFERED: number of workers doesn't vary

		return Schedulers.scanExecutor(executor, key);
	}

	@Override
	public Worker createWorker() {
		return new ExecutorServiceWorker(executor);
	}

}


/*
 * 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.Objects;
import java.util.function.Function;

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

/**
 * A tuple that holds eight values
 *
 * @param <T1> The type of the first value held by this tuple
 * @param <T2> The type of the second value held by this tuple
 * @param <T3> The type of the third value held by this tuple
 * @param <T4> The type of the fourth value held by this tuple
 * @param <T5> The type of the fifth value held by this tuple
 * @param <T6> The type of the sixth value held by this tuple
 * @param <T7> The type of the seventh value held by this tuple
 * @param <T8> The type of the eighth value held by this tuple
 * @author Jon Brisbin
 * @author Stephane Maldini
 */
public class Tuple8<T1, T2, T3, T4, T5, T6, T7, T8> extends
                                                    Tuple7<T1, T2, T3, T4, T5, T6, T7> {

	private static final long serialVersionUID = -8746796646535446242L;

	@NonNull final T8 t8;

	Tuple8(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8) {
		super(t1, t2, t3, t4, t5, t6, t7);
		this.t8 = Objects.requireNonNull(t8, "t8");
	}

	/**
	 * Type-safe way to get the eighth object of this {@link Tuples}.
	 *
	 * @return The eighth object
	 */
	public T8 getT8() {
		return t8;
	}

	/**
	 * Map the 1st part (T1) of this {@link Tuple8} into a different value and type,
	 * keeping the other parts.
	 *
	 * @param mapper the mapping {@link Function} for the T1 part
	 * @param <R> the new type for the T1 part
	 * @return a new {@link Tuple8} with a different T1 value
	 */
	public <R> Tuple8<R, T2, T3, T4, T5, T6, T7, T8> mapT1(Function<T1, R> mapper) {
		return new Tuple8<>(mapper.apply(t1), t2, t3, t4, t5, t6, t7, t8);
	}

	/**
	 * Map the 2nd part (T2) of this {@link Tuple8} into a different value and type,
	 * keeping the other parts.
	 *
	 * @param mapper the mapping {@link Function} for the T2 part
	 * @param <R> the new type for the T2 part
	 * @return a new {@link Tuple8} with a different T2 value
	 */
	public <R> Tuple8<T1, R, T3, T4, T5, T6, T7, T8> mapT2(Function<T2, R> mapper) {
		return new Tuple8<>(t1, mapper.apply(t2), t3, t4, t5, t6, t7, t8);
	}

	/**
	 * Map the 3rd part (T3) of this {@link Tuple8} into a different value and type,
	 * keeping the other parts.
	 *
	 * @param mapper the mapping {@link Function} for the T3 part
	 * @param <R> the new type for the T3 part
	 * @return a new {@link Tuple8} with a different T3 value
	 */
	public <R> Tuple8<T1, T2, R, T4, T5, T6, T7, T8> mapT3(Function<T3, R> mapper) {
		return new Tuple8<>(t1, t2, mapper.apply(t3), t4, t5, t6, t7, t8);
	}

	/**
	 * Map the 4th part (T4) of this {@link Tuple8} into a different value and type,
	 * keeping the other parts.
	 *
	 * @param mapper the mapping {@link Function} for the T4 part
	 * @param <R> the new type for the T4 part
	 * @return a new {@link Tuple8} with a different T4 value
	 */
	public <R> Tuple8<T1, T2, T3, R, T5, T6, T7, T8> mapT4(Function<T4, R> mapper) {
		return new Tuple8<>(t1, t2, t3, mapper.apply(t4), t5, t6, t7, t8);
	}

	/**
	 * Map the 5th part (T5) of this {@link Tuple8} into a different value and type,
	 * keeping the other parts.
	 *
	 * @param mapper the mapping {@link Function} for the T5 part
	 * @param <R> the new type for the T5 part
	 * @return a new {@link Tuple8} with a different T5 value
	 */
	public <R> Tuple8<T1, T2, T3, T4, R, T6, T7, T8> mapT5(Function<T5, R> mapper) {
		return new Tuple8<>(t1, t2, t3, t4, mapper.apply(t5), t6, t7, t8);
	}

	/**
	 * Map the 6th part (T6) of this {@link Tuple8} into a different value and type,
	 * keeping the other parts.
	 *
	 * @param mapper the mapping {@link Function} for the T6 part
	 * @param <R> the new type for the T6 part
	 * @return a new {@link Tuple8} with a different T6 value
	 */
	public <R> Tuple8<T1, T2, T3, T4, T5, R, T7, T8> mapT6(Function<T6, R> mapper) {
		return new Tuple8<>(t1, t2, t3, t4, t5, mapper.apply(t6), t7, t8);
	}

	/**
	 * Map the 7th part (T7) of this {@link Tuple8} into a different value and type,
	 * keeping the other parts.
	 *
	 * @param mapper the mapping {@link Function} for the T7 part
	 * @param <R> the new type for the T7 part
	 * @return a new {@link Tuple8} with a different T7 value
	 */
	public <R> Tuple8<T1, T2, T3, T4, T5, T6, R, T8> mapT7(Function<T7, R> mapper) {
		return new Tuple8<>(t1, t2, t3, t4, t5, t6, mapper.apply(t7), t8);
	}

	/**
	 * Map the 8th part (t8) of this {@link Tuple8} into a different value and type,
	 * keeping the other parts.
	 *
	 * @param mapper the mapping {@link Function} for the T8 part
	 * @param <R> the new type for the T8 part
	 * @return a new {@link Tuple8} with a different T8 value
	 */
	public <R> Tuple8<T1, T2, T3, T4, T5, T6, T7, R> mapT8(Function<T8, R> mapper) {
		return new Tuple8<>(t1, t2, t3, t4, t5, t6, t7, mapper.apply(t8));
	}

	@Nullable
	@Override
	public Object get(int index) {
		switch (index) {
			case 0:
				return t1;
			case 1:
				return t2;
			case 2:
				return t3;
			case 3:
				return t4;
			case 4:
				return t5;
			case 5:
				return t6;
			case 6:
				return t7;
			case 7:
				return t8;
			default:
				return null;
		}
	}

	@Override
	public Object[] toArray() {
		return new Object[]{t1, t2, t3, t4, t5, t6, t7, t8};
	}

	@Override
	public boolean equals(@Nullable Object o) {
		if (this == o) return true;
		if (!(o instanceof Tuple8)) return false;
		if (!super.equals(o)) return false;

		@SuppressWarnings("rawtypes")
        Tuple8 tuple8 = (Tuple8) o;

		return t8.equals(tuple8.t8);

	}

	@Override
	public int hashCode() {
		int result = super.hashCode();
		result = 31 * result + t8.hashCode();
		return result;
	}

	@Override
	public int size() {
		return 8;
	}
}


/*
 * 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.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Queue;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.LongConsumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.logging.Level;

import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import reactor.core.CorePublisher;
import reactor.core.CoreSubscriber;
import reactor.core.Disposable;
import reactor.core.Disposables;
import reactor.core.Scannable;
import reactor.core.publisher.FluxConcatMap.ErrorMode;
import reactor.core.publisher.FluxOnAssembly.AssemblyLightSnapshot;
import reactor.core.publisher.FluxOnAssembly.AssemblySnapshot;
import reactor.core.scheduler.Scheduler;
import reactor.util.Logger;
import reactor.util.annotation.Nullable;
import reactor.util.concurrent.Queues;

/**
 * A ParallelFlux publishes to an array of Subscribers, in parallel 'rails' (or
 * {@link #groups() 'groups'}).
 *

 * Use {@code from()} to start processing a regular Publisher in 'rails', which each
 * cover a subset of the original Publisher's data. {@link Flux#parallel()} is a
 * convenient shortcut to achieve that on a {@link Flux}.
 *

 * Use {@code runOn()} to introduce where each 'rail' should run on thread-vise.
 *

 * Use {@code sequential()} to merge the sources back into a single {@link Flux} or
 * {@link #subscribe(Subscriber)} if you simply want to subscribe to the merged sequence.
 * Note that other variants like {@link #subscribe(Consumer)} instead do multiple
 * subscribes, one on each rail (which means that the lambdas should be as stateless and
 * side-effect free as possible).
 *
 * @param <T> the value type
 */
public abstract class ParallelFlux<T> implements CorePublisher<T> {

	/**
	 * Take a Publisher and prepare to consume it on multiple 'rails' (one per CPU core)
	 * in a round-robin fashion.
	 *
	 * @param <T> the value type
	 * @param source the source Publisher
	 *
	 * @return the {@link ParallelFlux} instance
	 */
	public static <T> ParallelFlux<T> from(Publisher<? extends T> source) {
		return from(source,
				Runtime.getRuntime()
				       .availableProcessors(), Queues.SMALL_BUFFER_SIZE,
				Queues.small());
	}

	/**
	 * Take a Publisher and prepare to consume it on {@code parallelism} number of 'rails',
	 * possibly ordered and in a round-robin fashion.
	 *
	 * @param <T> the value type
	 * @param source the source Publisher
	 * @param parallelism the number of parallel rails
	 *
	 * @return the new {@link ParallelFlux} instance
	 */
	public static <T> ParallelFlux<T> from(Publisher<? extends T> source,
			int parallelism) {
		return from(source,
				parallelism, Queues.SMALL_BUFFER_SIZE,
				Queues.small());
	}

	/**
	 * Take a Publisher and prepare to consume it on {@code parallelism} number of 'rails'
	 * and in a round-robin fashion and use custom prefetch amount and queue
	 * for dealing with the source Publisher's values.
	 *
	 * @param <T> the value type
	 * @param source the source Publisher
	 * @param parallelism the number of parallel rails
	 * @param prefetch the number of values to prefetch from the source
	 * @param queueSupplier the queue structure supplier to hold the prefetched values
	 * from the source until there is a rail ready to process it.
	 *
	 * @return the new {@link ParallelFlux} instance
	 */
	public static <T> ParallelFlux<T> from(Publisher<? extends T> source,
			int parallelism,
			int prefetch,
			Supplier<Queue<T>> queueSupplier) {
		Objects.requireNonNull(queueSupplier, "queueSupplier");
		Objects.requireNonNull(source, "source");

		return onAssembly(new ParallelSource<>(source,
				parallelism,
				prefetch, queueSupplier));
	}

	/**
	 * Wraps multiple Publishers into a {@link ParallelFlux} which runs them in parallel and
	 * unordered.
	 *
	 * @param <T> the value type
	 * @param publishers the array of publishers
	 *
	 * @return the new {@link ParallelFlux} instance
	 */
	@SafeVarargs
	public static <T> ParallelFlux<T> from(Publisher<T>... publishers) {
		return onAssembly(new ParallelArraySource<>(publishers));
	}

	/**
	 * Perform a fluent transformation to a value via a converter function which receives
	 * this ParallelFlux.
	 *
	 * @param  the output value type
	 * @param converter the converter function from {@link ParallelFlux} to some type
	 *
	 * @return the value returned by the converter function
	 */
	public final  U as(Function<? super ParallelFlux<T>, U> converter) {
		return converter.apply(this);
	}

	/**
	 * Activate assembly tracing for this particular {@link ParallelFlux}, in case of an
	 * error upstream of the checkpoint. Tracing incurs the cost of an exception stack trace
	 * creation.
	 *

	 * It should be placed towards the end of the reactive chain, as errors
	 * triggered downstream of it cannot be observed and augmented with assembly trace.
	 *
	 * @return the assembly tracing {@link ParallelFlux}
	 */
	public final ParallelFlux<T> checkpoint() {
		AssemblySnapshot stacktrace = new AssemblySnapshot(null, Traces.callSiteSupplierFactory.get());
		return new ParallelFluxOnAssembly<>(this, stacktrace);
	}

	/**
	 * Activate assembly marker for this particular {@link ParallelFlux} by giving it a description that
	 * will be reflected in the assembly traceback in case of an error upstream of the
	 * checkpoint. Note that unlike {@link #checkpoint()}, this doesn't create a
	 * filled stack trace, avoiding the main cost of the operator.
	 * However, as a trade-off the description must be unique enough for the user to find
	 * out where this ParallelFlux was assembled. If you only want a generic description, and
	 * still rely on the stack trace to find the assembly site, use the
	 * {@link #checkpoint(String, boolean)} variant.
	 *

	 * It should be placed towards the end of the reactive chain, as errors
	 * triggered downstream of it cannot be observed and augmented with assembly trace.
	 *
	 * @param description a unique enough description to include in the light assembly traceback.
	 * @return the assembly marked {@link ParallelFlux}
	 */
	public final ParallelFlux<T> checkpoint(String description) {
		return new ParallelFluxOnAssembly<>(this, new AssemblyLightSnapshot(description));
	}

	/**
	 * Activate assembly tracing or the lighter assembly marking depending on the
	 * {@code forceStackTrace} option.
	 *

	 * By setting the {@code forceStackTrace} parameter to {@literal true}, activate assembly
	 * tracing for this particular {@link ParallelFlux} and give it a description that
	 * will be reflected in the assembly traceback in case of an error upstream of the
	 * checkpoint. Note that unlike {@link #checkpoint(String)}, this will incur
	 * the cost of an exception stack trace creation. The description could for
	 * example be a meaningful name for the assembled ParallelFlux or a wider correlation ID,
	 * since the stack trace will always provide enough information to locate where this
	 * ParallelFlux was assembled.
	 *

	 * By setting {@code forceStackTrace} to {@literal false}, behaves like
	 * {@link #checkpoint(String)} and is subject to the same caveat in choosing the
	 * description.
	 *

	 * It should be placed towards the end of the reactive chain, as errors
	 * triggered downstream of it cannot be observed and augmented with assembly marker.
	 *
	 * @param description a description (must be unique enough if forceStackTrace is set
	 * to false).
	 * @param forceStackTrace false to make a light checkpoint without a stacktrace, true
	 * to use a stack trace.
	 * @return the assembly marked {@link ParallelFlux}.
	 */
	public final ParallelFlux<T> checkpoint(String description, boolean forceStackTrace) {
		final AssemblySnapshot stacktrace;
		if (!forceStackTrace) {
			stacktrace = new AssemblyLightSnapshot(description);
		}
		else {
			stacktrace = new AssemblySnapshot(description, Traces.callSiteSupplierFactory.get());
		}

		return new ParallelFluxOnAssembly<>(this, stacktrace);
	}

	/**
	 * Collect the elements in each rail into a collection supplied via a
	 * collectionSupplier and collected into with a collector action, emitting the
	 * collection at the end.
	 *
	 * @param <C> the collection type
	 * @param collectionSupplier the supplier of the collection in each rail
	 * @param collector the collector, taking the per-rail collection and the current
	 * item
	 *
	 * @return the new {@link ParallelFlux} instance
	 */
	public final <C> ParallelFlux<C> collect(Supplier<? extends C> collectionSupplier,
			BiConsumer<? super C, ? super T> collector) {
		return onAssembly(new ParallelCollect<>(this, collectionSupplier, collector));
	}

	/**
	 * Sorts the 'rails' according to the comparator and returns a full sorted list as a
	 * Publisher.
	 *

	 * This operator requires a finite source ParallelFlux.
	 *
	 * @param comparator the comparator to compare elements
	 *
	 * @return the new Flux instance
	 */
	public final Mono<List<T>> collectSortedList(Comparator<? super T> comparator) {
		return collectSortedList(comparator, 16);
	}

	/**
	 * Sorts the 'rails' according to the comparator and returns a full sorted list as a
	 * Publisher.
	 *

	 * This operator requires a finite source ParallelFlux.
	 *
	 * @param comparator the comparator to compare elements
	 * @param capacityHint the expected number of total elements
	 *
	 * @return the new Mono instance
	 */
	public final Mono<List<T>> collectSortedList(Comparator<? super T> comparator,
			int capacityHint) {
		int ch = capacityHint / parallelism() + 1;
		ParallelFlux<List<T>> railReduced =
				reduce(() -> new ArrayList<>(ch), (a, b) -> {
					a.add(b);
					return a;
				});
		ParallelFlux<List<T>> railSorted = railReduced.map(list -> {
			list.sort(comparator);
			return list;
		});

		Mono<List<T>> merged = railSorted.reduce((a, b) -> sortedMerger(a, b, comparator));

		return merged;
	}

	/**
	 * Allows composing operators off the 'rails', as individual {@link GroupedFlux} instances keyed by
	 * the zero based rail's index. The transformed groups are {@link Flux#parallel parallelized} back
	 * once the transformation has been applied.
	 *

	 * Note that like in {@link #groups()}, requests and cancellation compose through, and
	 * cancelling only one rail may result in undefined behavior.
	 *
	 * @param composer the composition function to apply on each {@link GroupedFlux rail}
	 * @param  the type of the resulting parallelized flux
	 * @return a {@link ParallelFlux} of the composed groups
	 */
	public final  ParallelFlux composeGroup(Function<? super GroupedFlux<Integer, T>,
			? extends Publisher<? extends U>> composer) {
		if (getPrefetch() > -1) {
			return from(groups().flatMap(composer::apply),
				parallelism(), getPrefetch(),
				Queues.small());
		}
		else {
			return from(groups().flatMap(composer::apply), parallelism());
		}
	}

	/**
	 * Generates and concatenates Publishers on each 'rail', signalling errors immediately
	 * and generating 2 publishers upfront.
	 *
	 * @param <R> the result type
	 * @param mapper the function to map each rail's value into a Publisher source and the
	 * inner Publishers (immediate, boundary, end)
	 *
	 * @return the new {@link ParallelFlux} instance
	 */
	public final <R> ParallelFlux<R> concatMap(Function<? super T, ? extends Publisher<? extends R>> mapper) {
		return concatMap(mapper, 2, ErrorMode.IMMEDIATE);
	}

	/**
	 * Generates and concatenates Publishers on each 'rail', signalling errors immediately
	 * and using the given prefetch amount for generating Publishers upfront.
	 *
	 * @param <R> the result type
	 * @param mapper the function to map each rail's value into a Publisher
	 * @param prefetch the number of items to prefetch from each inner Publisher source
	 * and the inner Publishers (immediate, boundary, end)
	 *
	 * @return the new {@link ParallelFlux} instance
	 */
	public final <R> ParallelFlux<R> concatMap(Function<? super T, ? extends Publisher<? extends R>> mapper,
			int prefetch) {
		return concatMap(mapper, prefetch, ErrorMode.IMMEDIATE);
	}

	/**
	 * Generates and concatenates Publishers on each 'rail',  delaying errors
	 * and generating 2 publishers upfront.
	 *
	 * @param <R> the result type
	 * @param mapper the function to map each rail's value into a Publisher
	 * source and the inner Publishers (immediate, boundary, end)
	 *
	 * @return the new {@link ParallelFlux} instance
	 */
	public final <R> ParallelFlux<R> concatMapDelayError(Function<? super T, ? extends
			Publisher<? extends R>> mapper) {
		return concatMap(mapper, 2, ErrorMode.END);
	}

	/**
	 * Run the specified runnable when a 'rail' completes or signals an error.
	 *
	 * @param afterTerminate the callback
	 *
	 * @return the new {@link ParallelFlux} instance
	 */
	public final ParallelFlux<T> doAfterTerminate(Runnable afterTerminate) {
		Objects.requireNonNull(afterTerminate, "afterTerminate");
		return doOnSignal(this, null, null, null, null, afterTerminate, null, null, null);
	}

	/**
	 * Run the specified runnable when a 'rail' receives a cancellation.
	 *
	 * @param onCancel the callback
	 *
	 * @return the new {@link ParallelFlux} instance
	 */
	public final ParallelFlux<T> doOnCancel(Runnable onCancel) {
		Objects.requireNonNull(onCancel, "onCancel");
		return doOnSignal(this, null, null, null, null, null, null, null, onCancel);
	}

	/**
	 * Run the specified runnable when a 'rail' completes.
	 *
	 * @param onComplete the callback
	 *
	 * @return the new {@link ParallelFlux} instance
	 */
	public final ParallelFlux<T> doOnComplete(Runnable onComplete) {
		Objects.requireNonNull(onComplete, "onComplete");
		return doOnSignal(this, null, null, null, onComplete, null, null, null, null);
	}

	/**
	 * Triggers side-effects when the {@link ParallelFlux} emits an item, fails with an error
	 * or completes successfully. All these events are represented as a {@link Signal}
	 * that is passed to the side-effect callback. Note that with {@link ParallelFlux} and
	 * the {@link #subscribe(Consumer) lambda-based subscribes} or the
	 * {@link #subscribe(CoreSubscriber[]) array-based one}, onError and onComplete will be
	 * invoked as many times as there are rails, resulting in as many corresponding
	 * {@link Signal} being seen in the callback.
	 *

	 * Use of {@link #subscribe(Subscriber)}, which calls {@link #sequential()}, might
	 * cancel some rails, resulting in less signals being observed. This is an advanced
	 * operator, typically used for monitoring of a ParallelFlux.
	 *
	 * @param signalConsumer the mandatory callback to call on
	 *   {@link Subscriber#onNext(Object)}, {@link Subscriber#onError(Throwable)} and
	 *   {@link Subscriber#onComplete()}
	 * @return an observed {@link ParallelFlux}
	 * @see #doOnNext(Consumer)
	 * @see #doOnError(Consumer)
	 * @see #doOnComplete(Runnable)
	 * @see #subscribe(CoreSubscriber[])
	 * @see Signal
	 */
	public final ParallelFlux<T> doOnEach(Consumer<? super Signal<T>> signalConsumer) {
		Objects.requireNonNull(signalConsumer, "signalConsumer");
		return onAssembly(new ParallelDoOnEach<>(
				this,
				(ctx, v) -> signalConsumer.accept(Signal.next(v, ctx)),
				(ctx, e) -> signalConsumer.accept(Signal.error(e, ctx)),
				ctx -> signalConsumer.accept(Signal.complete(ctx))
		));
	}

	/**
	 * Call the specified consumer with the exception passing through any 'rail'.
	 *
	 * @param onError the callback
	 *
	 * @return the new {@link ParallelFlux} instance
	 */
	public final ParallelFlux<T> doOnError(Consumer<? super Throwable> onError) {
		Objects.requireNonNull(onError, "onError");
		return doOnSignal(this, null, null, onError, null, null, null, null, null);
	}

	/**
	 * Call the specified callback when a 'rail' receives a Subscription from its
	 * upstream.
	 *

	 * This method is not intended for capturing the subscription and calling its methods,
	 * but for side effects like monitoring. For instance, the correct way to cancel a subscription is
	 * to call {@link Disposable#dispose()} on the Disposable returned by {@link ParallelFlux#subscribe()}.
	 *
	 * @param onSubscribe the callback
	 *
	 * @return the new {@link ParallelFlux} instance
	 */
	public final ParallelFlux<T> doOnSubscribe(Consumer<? super Subscription> onSubscribe) {
		Objects.requireNonNull(onSubscribe, "onSubscribe");
		return doOnSignal(this, null, null, null, null, null, onSubscribe, null, null);
	}

	/**
	 * Call the specified consumer with the current element passing through any 'rail'.
	 *
	 * @param onNext the callback
	 *
	 * @return the new {@link ParallelFlux} instance
	 */
	public final ParallelFlux<T> doOnNext(Consumer<? super T> onNext) {
		Objects.requireNonNull(onNext, "onNext");
		return doOnSignal(this, onNext, null, null, null, null, null, null, null);
	}

	/**
	 * Call the specified consumer with the request amount if any rail receives a
	 * request.
	 *
	 * @param onRequest the callback
	 *
	 * @return the new {@link ParallelFlux} instance
	 */
	public final ParallelFlux<T> doOnRequest(LongConsumer onRequest) {
		Objects.requireNonNull(onRequest, "onRequest");
		return doOnSignal(this, null, null, null, null, null, null, onRequest, null);
	}

	/**
	 * Triggered when the {@link ParallelFlux} terminates, either by completing successfully or with an error.
	 * @param onTerminate the callback to call on {@link Subscriber#onComplete} or {@link Subscriber#onError}
	 *
	 * @return an observed  {@link ParallelFlux}
	 */
	public final ParallelFlux<T> doOnTerminate(Runnable onTerminate) {
		Objects.requireNonNull(onTerminate, "onTerminate");
		return doOnSignal(this,
				null,
				null,
				e -> onTerminate.run(),
				onTerminate,
				null,
				null,
				null,
				null);
	}

	/**
	 * Filters the source values on each 'rail'.
	 *

	 * Note that the same predicate may be called from multiple threads concurrently.
	 *
	 * @param predicate the function returning true to keep a value or false to drop a
	 * value
	 *
	 * @return the new {@link ParallelFlux} instance
	 */
	public final ParallelFlux<T> filter(Predicate<? super T> predicate) {
		Objects.requireNonNull(predicate, "predicate");
		return onAssembly(new ParallelFilter<>(this, predicate));
	}

	/**
	 * Generates and flattens Publishers on each 'rail'.
	 *

	 * Errors are not delayed and uses unbounded concurrency along with default inner
	 * prefetch.
	 *
	 * @param <R> the result type
	 * @param mapper the function to map each rail's value into a Publisher
	 *
	 * @return the new {@link ParallelFlux} instance
	 */
	public final <R> ParallelFlux<R> flatMap(Function<? super T, ? extends Publisher<? extends R>> mapper) {
		return flatMap(mapper,
				false,
				Integer.MAX_VALUE, Queues.SMALL_BUFFER_SIZE);
	}

	/**
	 * Generates and flattens Publishers on each 'rail', optionally delaying errors.
	 *

	 * It uses unbounded concurrency along with default inner prefetch.
	 *
	 * @param <R> the result type
	 * @param mapper the function to map each rail's value into a Publisher
	 * @param delayError should the errors from the main and the inner sources delayed
	 * till everybody terminates?
	 *
	 * @return the new {@link ParallelFlux} instance
	 */
	public final <R> ParallelFlux<R> flatMap(Function<? super T, ? extends Publisher<? extends R>> mapper,
			boolean delayError) {
		return flatMap(mapper,
				delayError,
				Integer.MAX_VALUE, Queues.SMALL_BUFFER_SIZE);
	}

	/**
	 * Generates and flattens Publishers on each 'rail', optionally delaying errors and
	 * having a total number of simultaneous subscriptions to the inner Publishers.
	 *

	 * It uses a default inner prefetch.
	 *
	 * @param <R> the result type
	 * @param mapper the function to map each rail's value into a Publisher
	 * @param delayError should the errors from the main and the inner sources delayed
	 * till everybody terminates?
	 * @param maxConcurrency the maximum number of simultaneous subscriptions to the
	 * generated inner Publishers
	 *
	 * @return the new {@link ParallelFlux} instance
	 */
	public final <R> ParallelFlux<R> flatMap(Function<? super T, ? extends Publisher<? extends R>> mapper,
			boolean delayError,
			int maxConcurrency) {
		return flatMap(mapper,
				delayError,
				maxConcurrency, Queues.SMALL_BUFFER_SIZE);
	}

	/**
	 * Generates and flattens Publishers on each 'rail', optionally delaying errors,
	 * having a total number of simultaneous subscriptions to the inner Publishers and
	 * using the given prefetch amount for the inner Publishers.
	 *
	 * @param <R> the result type
	 * @param mapper the function to map each rail's value into a Publisher
	 * @param delayError should the errors from the main and the inner sources delayed
	 * till everybody terminates?
	 * @param maxConcurrency the maximum number of simultaneous subscriptions to the
	 * generated inner Publishers
	 * @param prefetch the number of items to prefetch from each inner Publisher
	 *
	 * @return the new {@link ParallelFlux} instance
	 */
	public final <R> ParallelFlux<R> flatMap(Function<? super T, ? extends Publisher<? extends R>> mapper,
			boolean delayError,
			int maxConcurrency,
			int prefetch) {
		return onAssembly(new ParallelFlatMap<>(this,
				mapper,
				delayError,
				maxConcurrency,
				Queues.get(maxConcurrency),
				prefetch, Queues.get(prefetch)));
	}

	/**
	 * Exposes the 'rails' as individual GroupedFlux instances, keyed by the rail
	 * index (zero based).
	 *

	 * Each group can be consumed only once; requests and cancellation compose through.
	 * Note that cancelling only one rail may result in undefined behavior.
	 *
	 * @return the new Flux instance
	 */
	public final Flux<GroupedFlux<Integer, T>> groups() {
		return Flux.onAssembly(new ParallelGroup<>(this));
	}

	/**
	 * Hides the identities of this {@link ParallelFlux} and its {@link Subscription}
	 * as well.
	 *
	 * @return a new {@link ParallelFlux} defeating any {@link Publisher} / {@link Subscription} feature-detection
	 */
	public final ParallelFlux<T> hide() {
		return new ParallelFluxHide<>(this);
	}

	/**
	 * Observe all Reactive Streams signals and use {@link Logger} support to handle trace
	 * implementation. Default will use {@link Level#INFO} and java.util.logging. If SLF4J
	 * is available, it will be used instead.
	 *

	 *
	 *

	 * The default log category will be "reactor.*", a generated operator suffix will
	 * complete, e.g. "reactor.Parallel.Map".
	 *
	 * @return a new unaltered {@link ParallelFlux}
	 */
	public final ParallelFlux<T> log() {
		return log(null, Level.INFO);
	}

	/**
	 * Observe all Reactive Streams signals and use {@link Logger} support to handle trace
	 * implementation. Default will use {@link Level#INFO} and java.util.logging. If SLF4J
	 * is available, it will be used instead.
	 *

	 *
	 *

	 *
	 * @param category to be mapped into logger configuration (e.g. org.springframework
	 * .reactor). If category ends with "." like "reactor.", a generated operator suffix
	 * will complete, e.g. "reactor.Parallel.Map".
	 *
	 * @return a new unaltered {@link ParallelFlux}
	 */
	public final ParallelFlux<T> log(@Nullable String category) {
		return log(category, Level.INFO);
	}

	/**
	 * Observe Reactive Streams signals matching the passed filter {@code options} and use
	 * {@link Logger} support to handle trace implementation. Default will use the passed
	 * {@link Level} and java.util.logging. If SLF4J is available, it will be used
	 * instead.
	 *

	 * Options allow fine grained filtering of the traced signal, for instance to only
	 * capture onNext and onError:
	 *

	 *     ParallelFlux.log("category", Level.INFO, SignalType.ON_NEXT,
	 * SignalType.ON_ERROR)
	 *
	 *

	 *
	 *
	 * @param category to be mapped into logger configuration (e.g. org.springframework
	 * .reactor). If category ends with "." like "reactor.", a generated operator
	 * suffix will complete, e.g. "reactor.Parallel.Map".
	 * @param level the {@link Level} to enforce for this tracing ParallelFlux (only
	 * FINEST, FINE, INFO, WARNING and SEVERE are taken into account)
	 * @param options a vararg {@link SignalType} option to filter log messages
	 *
	 * @return a new unaltered {@link ParallelFlux}
	 */
	public final ParallelFlux<T> log(@Nullable String category,
			Level level,
			SignalType... options) {
		return log(category, level, false, options);
	}

	/**
	 * Observe Reactive Streams signals matching the passed filter {@code options} and use
	 * {@link Logger} support to handle trace implementation. Default will use the passed
	 * {@link Level} and java.util.logging. If SLF4J is available, it will be used
	 * instead.
	 *

	 * Options allow fine grained filtering of the traced signal, for instance to only
	 * capture onNext and onError:
	 *

	 *     ParallelFlux.log("category", Level.INFO, SignalType.ON_NEXT,
	 * SignalType.ON_ERROR)
	 *
	 *

	 *
	 *
	 * @param category to be mapped into logger configuration (e.g. org.springframework
	 * .reactor). If category ends with "." like "reactor.", a generated operator
	 * suffix will complete, e.g. "reactor.ParallelFlux.Map".
	 * @param level the {@link Level} to enforce for this tracing ParallelFlux (only
	 * FINEST, FINE, INFO, WARNING and SEVERE are taken into account)
	 * @param showOperatorLine capture the current stack to display operator
	 * class/line number.
	 * @param options a vararg {@link SignalType} option to filter log messages
	 *
	 * @return a new unaltered {@link ParallelFlux}
	 */
	public final ParallelFlux<T> log(@Nullable String category,
			Level level,
			boolean showOperatorLine,
			SignalType... options) {
		return onAssembly(new ParallelLog<>(this, new SignalLogger<>(this, category, level, showOperatorLine, options)));
	}

	/**
	 * Maps the source values on each 'rail' to another value.
	 *

	 * Note that the same mapper function may be called from multiple threads
	 * concurrently.
	 *
	 * @param  the output value type
	 * @param mapper the mapper function turning Ts into Us.
	 *
	 * @return the new {@link ParallelFlux} instance
	 */
	public final  ParallelFlux map(Function<? super T, ? extends U> mapper) {
		Objects.requireNonNull(mapper, "mapper");
		return onAssembly(new ParallelMap<>(this, mapper));
	}

	/**
	 * Give a name to this sequence, which can be retrieved using {@link Scannable#name()}
	 * as long as this is the first reachable {@link Scannable#parents()}.
	 *
	 * @param name a name for the sequence
	 * @return the same sequence, but bearing a name
	 */
	public final ParallelFlux<T> name(String name) {
		return ParallelFluxName.createOrAppend(this, name);
	}

	/**
	 * Merges the values from each 'rail', but choose which one to merge by way of a
	 * provided {@link Comparator}, picking the smallest of all rails. The result is
	 * exposed back as a {@link Flux}.
	 *

	 * This version uses a default prefetch of {@link Queues#SMALL_BUFFER_SIZE}.
	 *
	 * @param comparator the comparator to choose the smallest value available from all rails
	 * @return the new Flux instance
	 *
	 * @see ParallelFlux#ordered(Comparator, int)
	 */
	public final Flux<T> ordered(Comparator<? super T> comparator) {
		return ordered(comparator, Queues.SMALL_BUFFER_SIZE);
	}

	/**
	 * Merges the values from each 'rail', but choose which one to merge by way of a
	 * provided {@link Comparator}, picking the smallest of all rails. The result is
	 * exposed back as a {@link Flux}.
	 *
	 * @param comparator the comparator to choose the smallest value available from all rails
	 * @param prefetch the prefetch to use
	 * @return the new Flux instance
	 *
	 * @see ParallelFlux#ordered(Comparator)
	 */
	public final Flux<T> ordered(Comparator<? super T> comparator, int prefetch) {
		return new ParallelMergeOrdered<>(this, prefetch, Queues.get(prefetch), comparator);
	}

	/**
	 * Returns the number of expected parallel Subscribers.
	 *
	 * @return the number of expected parallel Subscribers
	 */
	public abstract int parallelism();

	/**
	 * Reduces all values within a 'rail' and across 'rails' with a reducer function into
	 * a single sequential value.
	 *

	 * Note that the same reducer function may be called from multiple threads
	 * concurrently.
	 *
	 * @param reducer the function to reduce two values into one.
	 *
	 * @return the new Mono instance emitting the reduced value or empty if the
	 * {@link ParallelFlux} was empty
	 */
	public final Mono<T> reduce(BiFunction<T, T, T> reducer) {
		Objects.requireNonNull(reducer, "reducer");
		return Mono.onAssembly(new ParallelMergeReduce<>(this, reducer));
	}

	/**
	 * Reduces all values within a 'rail' to a single value (with a possibly different
	 * type) via a reducer function that is initialized on each rail from an
	 * initialSupplier value.
	 *

	 * Note that the same mapper function may be called from multiple threads
	 * concurrently.
	 *
	 * @param <R> the reduced output type
	 * @param initialSupplier the supplier for the initial value
	 * @param reducer the function to reduce a previous output of reduce (or the initial
	 * value supplied) with a current source value.
	 *
	 * @return the new {@link ParallelFlux} instance
	 */
	public final <R> ParallelFlux<R> reduce(Supplier<R> initialSupplier,
			BiFunction<R, ? super T, R> reducer) {
		Objects.requireNonNull(initialSupplier, "initialSupplier");
		Objects.requireNonNull(reducer, "reducer");
		return onAssembly(new ParallelReduceSeed<>(this, initialSupplier, reducer));
	}

	/**
	 * Specifies where each 'rail' will observe its incoming values with no work-stealing
	 * and default prefetch amount.
	 *

	 * This operator uses the default prefetch size returned by {@code
	 * Queues.SMALL_BUFFER_SIZE}.
	 *

	 * The operator will call {@code Scheduler.createWorker()} as many times as this
	 * ParallelFlux's parallelism level is.
	 *

	 * No assumptions are made about the Scheduler's parallelism level, if the Scheduler's
	 * parallelism level is lower than the ParallelFlux's, some rails may end up on
	 * the same thread/worker.
	 *

	 * This operator doesn't require the Scheduler to be trampolining as it does its own
	 * built-in trampolining logic.
	 *
	 * @param scheduler the scheduler to use
	 *
	 * @return the new {@link ParallelFlux} instance
	 */
	public final ParallelFlux<T> runOn(Scheduler scheduler) {
		return runOn(scheduler, Queues.SMALL_BUFFER_SIZE);
	}

	/**
	 * Specifies where each 'rail' will observe its incoming values with possibly
	 * work-stealing and a given prefetch amount.
	 *

	 * This operator uses the default prefetch size returned by {@code
	 * Queues.SMALL_BUFFER_SIZE}.
	 *

	 * The operator will call {@code Scheduler.createWorker()} as many times as this
	 * ParallelFlux's parallelism level is.
	 *

	 * No assumptions are made about the Scheduler's parallelism level, if the Scheduler's
	 * parallelism level is lower than the ParallelFlux's, some rails may end up on
	 * the same thread/worker.
	 *

	 * This operator doesn't require the Scheduler to be trampolining as it does its own
	 * built-in trampolining logic.
	 *
	 * @param scheduler the scheduler to use that rail's worker has run out of work.
	 * @param prefetch the number of values to request on each 'rail' from the source
	 *
	 * @return the new {@link ParallelFlux} instance
	 */
	public final ParallelFlux<T> runOn(Scheduler scheduler, int prefetch) {
		Objects.requireNonNull(scheduler, "scheduler");
		return onAssembly(new ParallelRunOn<>(this,
				scheduler,
				prefetch,
				Queues.get(prefetch)));
	}

	/**
	 * Merges the values from each 'rail' in a round-robin or same-order fashion and
	 * exposes it as a regular Publisher sequence, running with a default prefetch value
	 * for the rails.
	 *

	 * This operator uses the default prefetch size returned by {@code
	 * Queues.SMALL_BUFFER_SIZE}.
	 *
	 * @return the new Flux instance
	 *
	 * @see ParallelFlux#sequential(int)
	 */
	public final Flux<T> sequential() {
		return sequential(Queues.SMALL_BUFFER_SIZE);
	}

	/**
	 * Merges the values from each 'rail' in a round-robin or same-order fashion and
	 * exposes it as a regular Publisher sequence, running with a give prefetch value for
	 * the rails.
	 *
	 * @param prefetch the prefetch amount to use for each rail
	 *
	 * @return the new Flux instance
	 */
	public final Flux<T> sequential(int prefetch) {
		return Flux.onAssembly(new ParallelMergeSequential<>(this,
				prefetch,
				Queues.get(prefetch)));
	}

	/**
	 * Sorts the 'rails' of this {@link ParallelFlux} and returns a Publisher that
	 * sequentially picks the smallest next value from the rails.
	 *

	 * This operator requires a finite source ParallelFlux.
	 *
	 * @param comparator the comparator to use
	 *
	 * @return the new Flux instance
	 */
	public final Flux<T> sorted(Comparator<? super T> comparator) {
		return sorted(comparator, 16);
	}

	/**
	 * Sorts the 'rails' of this {@link ParallelFlux} and returns a Publisher that
	 * sequentially picks the smallest next value from the rails.
	 *

	 * This operator requires a finite source ParallelFlux.
	 *
	 * @param comparator the comparator to use
	 * @param capacityHint the expected number of total elements
	 *
	 * @return the new Flux instance
	 */
	public final Flux<T> sorted(Comparator<? super T> comparator, int capacityHint) {
		int ch = capacityHint / parallelism() + 1;
		ParallelFlux<List<T>> railReduced = reduce(() -> new ArrayList<>(ch), (a, b) -> {
					a.add(b);
					return a;
				});
		ParallelFlux<List<T>> railSorted = railReduced.map(list -> {
			list.sort(comparator);
			return list;
		});

		return Flux.onAssembly(new ParallelMergeSort<>(railSorted, comparator));
	}

	/**
	 * Subscribes an array of Subscribers to this {@link ParallelFlux} and triggers the
	 * execution chain for all 'rails'.
	 *
	 * @param subscribers the subscribers array to run in parallel, the number of items
	 * must be equal to the parallelism level of this ParallelFlux
	 */
	protected abstract void subscribe(CoreSubscriber<? super T>[] subscribers);

	/**
	 * Subscribes to this {@link ParallelFlux} and triggers the execution chain for all
	 * 'rails'.
	 */
	public final Disposable subscribe(){
		return subscribe(null, null, null);
	}

	/**
	 * Subscribes to this {@link ParallelFlux} by providing an onNext callback and
	 * triggers the execution chain for all 'rails'.
	 *
	 * @param onNext consumer of onNext signals
	 */
	public final Disposable subscribe(Consumer<? super T> onNext){
		return subscribe(onNext, null, null);
	}

	/**
	 * Subscribes to this {@link ParallelFlux} by providing an onNext and onError callback
	 * and triggers the execution chain for all 'rails'.
	 *
	 * @param onNext consumer of onNext signals
	 * @param onError consumer of error signal
	 */
	public final Disposable subscribe(@Nullable Consumer<? super T> onNext, Consumer<? super Throwable>
			onError){
		return subscribe(onNext, onError, null);
	}

	/**
	 * Subscribes to this {@link ParallelFlux} by providing an onNext, onError and
	 * onComplete callback and triggers the execution chain for all 'rails'.
	 *
	 * @param onNext consumer of onNext signals
	 * @param onError consumer of error signal
	 * @param onComplete callback on completion signal
	 */
	public final Disposable subscribe(
			@Nullable Consumer<? super T> onNext,
			@Nullable Consumer<? super Throwable> onError,
			@Nullable Runnable onComplete) {
		return subscribe(onNext, onError, onComplete, null);
	}

	@Override
	@SuppressWarnings("unchecked")
	public final void subscribe(CoreSubscriber<? super T> s) {
		FluxHide.SuppressFuseableSubscriber<T> subscriber =
				new FluxHide.SuppressFuseableSubscriber<>(Operators.toCoreSubscriber(s));

		sequential().subscribe(Operators.toCoreSubscriber(subscriber));
	}

	/**
	 * Subscribes to this {@link ParallelFlux} by providing an onNext, onError,
	 * onComplete and onSubscribe callback and triggers the execution chain for all
	 * 'rails'.
	 *
	 * @param onNext consumer of onNext signals
	 * @param onError consumer of error signal
	 * @param onComplete callback on completion signal
	 * @param onSubscribe consumer of the subscription signal
	 */
	public final Disposable subscribe(
			@Nullable Consumer<? super T> onNext,
			@Nullable Consumer<? super Throwable> onError,
			@Nullable Runnable onComplete,
			@Nullable Consumer<? super Subscription> onSubscribe){

		CorePublisher<T> publisher = Operators.onLastAssembly(this);
		if (publisher instanceof ParallelFlux) {
			@SuppressWarnings("unchecked")
			LambdaSubscriber<? super T>[] subscribers = new LambdaSubscriber[parallelism()];

			int i = 0;
			while(i < subscribers.length){
				subscribers[i++] =
						new LambdaSubscriber<>(onNext, onError, onComplete, onSubscribe);
			}

			((ParallelFlux<T>) publisher).subscribe(subscribers);

			return Disposables.composite(subscribers);
		}
		else {
			LambdaSubscriber<? super T> subscriber =
					new LambdaSubscriber<>(onNext, onError, onComplete, onSubscribe);

			publisher.subscribe(Operators.toCoreSubscriber(new FluxHide.SuppressFuseableSubscriber<>(subscriber)));

			return subscriber;
		}
	}

	/**
	 * Merge the rails into a {@link #sequential()} Flux and
	 * {@link Flux#subscribe(Subscriber) subscribe} to said Flux.
	 *
	 * @param s the subscriber to use on {@link #sequential()} Flux
	 */
	@Override
	@SuppressWarnings("unchecked")
	public final void subscribe(Subscriber<? super T> s) {
		FluxHide.SuppressFuseableSubscriber<T> subscriber =
				new FluxHide.SuppressFuseableSubscriber<>(Operators.toCoreSubscriber(s));

		Operators.onLastAssembly(sequential()).subscribe(Operators.toCoreSubscriber(subscriber));
	}

	/**
	 * Tag this ParallelFlux with a key/value pair. These can be retrieved as a
	 * {@link Set} of
	 * all tags throughout the publisher chain by using {@link Scannable#tags()} (as
	 * traversed
	 * by {@link Scannable#parents()}).
	 *
	 * @param key a tag key
	 * @param value a tag value
	 * @return the same sequence, but bearing tags
	 */
	public final ParallelFlux<T> tag(String key, String value) {
		return ParallelFluxName.createOrAppend(this, key, value);
	}

	/**
	 * Allows composing operators, in assembly time, on top of this {@link ParallelFlux}
	 * and returns another {@link ParallelFlux} with composed features.
	 *
	 * @param  the output value type
	 * @param composer the composer function from {@link ParallelFlux} (this) to another
	 * ParallelFlux
	 *
	 * @return the {@link ParallelFlux} returned by the function
	 */
	public final  ParallelFlux transform(Function<? super ParallelFlux<T>, ParallelFlux> composer) {
		return onAssembly(as(composer));
	}

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

	/**
	 * Validates the number of subscribers and returns true if their number matches the
	 * parallelism level of this ParallelFlux.
	 *
	 * @param subscribers the array of Subscribers
	 *
	 * @return true if the number of subscribers equals to the parallelism level
	 */
	protected final boolean validate(Subscriber<?>[] subscribers) {
		int p = parallelism();
		if (subscribers.length != p) {
			IllegalArgumentException iae = new IllegalArgumentException("parallelism = " +
					"" + p + ", subscribers = " + subscribers.length);
			for (Subscriber<?> s : subscribers) {
				Operators.error(s, iae);
			}
			return false;
		}
		return true;
	}

	/**
	 * Generates and concatenates Publishers on each 'rail', optionally delaying errors
	 * and using the given prefetch amount for generating Publishers upfront.
	 *
	 * @param <R> the result type
	 * @param mapper the function to map each rail's value into a Publisher
	 * @param prefetch the number of items to prefetch from each inner Publisher
	 * @param errorMode the error handling, i.e., when to report errors from the main
	 * source and the inner Publishers (immediate, boundary, end)
	 *
	 * @return the new {@link ParallelFlux} instance
	 */
	final <R> ParallelFlux<R> concatMap(Function<? super T, ? extends Publisher<? extends R>> mapper,
			int prefetch,
			ErrorMode errorMode) {
		return onAssembly(new ParallelConcatMap<>(this,
				mapper,
				Queues.get(prefetch),
				prefetch,
				errorMode));
	}

	/**
	 * Generates and concatenates Publishers on each 'rail', delaying errors
	 * and using the given prefetch amount for generating Publishers upfront.
	 *
	 * @param <R> the result type
	 * @param mapper the function to map each rail's value into a Publisher
	 * @param delayUntilEnd true if delayed until all sources are concatenated
	 * @param prefetch the number of items to prefetch from each inner Publisher
	 * source and the inner Publishers (immediate, boundary, end)
	 *
	 * @return the new {@link ParallelFlux} instance
	 */
	final <R> ParallelFlux<R> concatMapDelayError(Function<? super T, ? extends
			Publisher<? extends R>> mapper,
			boolean delayUntilEnd,
			int prefetch) {
		return concatMap(mapper, prefetch, delayUntilEnd ? ErrorMode.END: ErrorMode.BOUNDARY);
	}

	/**
	 * Generates and concatenates Publishers on each 'rail', delaying errors
	 * and using the given prefetch amount for generating Publishers upfront.
	 *
	 * @param <R> the result type
	 * @param mapper the function to map each rail's value into a Publisher
	 * @param prefetch the number of items to prefetch from each inner Publisher
	 * source and the inner Publishers (immediate, boundary, end)
	 *
	 * @return the new {@link ParallelFlux} instance
	 */
	final <R> ParallelFlux<R> concatMapDelayError(Function<? super T, ? extends
			Publisher<? extends R>> mapper, int prefetch) {
		return concatMap(mapper, prefetch, ErrorMode.END);
	}

	/**
	 * The prefetch configuration of the component
	 *
	 * @return the prefetch configuration of the component
	 */
	public int getPrefetch() {
		return -1;
	}

	/**
	 * Invoke {@link Hooks} pointcut given a {@link ParallelFlux} and returning an
	 * eventually new {@link ParallelFlux}
	 *
	 * @param <T> the value type
	 * @param source the source to wrap
	 *
	 * @return the potentially wrapped source
	 */
	@SuppressWarnings("unchecked")
	protected static <T> ParallelFlux<T> onAssembly(ParallelFlux<T> source) {
		Function<Publisher, Publisher> hook = Hooks.onEachOperatorHook;
		if(hook != null) {
			source = (ParallelFlux<T>) hook.apply(source);
		}
		if (Hooks.GLOBAL_TRACE) {
			AssemblySnapshot stacktrace = new AssemblySnapshot(null, Traces.callSiteSupplierFactory.get());
			source = (ParallelFlux<T>) Hooks.addAssemblyInfo(source, stacktrace);
		}
		return source;
	}

	/**
	 * Invoke {@link Hooks} pointcut given a {@link ParallelFlux} and returning an
	 * eventually new {@link ParallelFlux}
	 *
	 * @param <T> the value type
	 * @param source the source to wrap
	 *
	 * @return the potentially wrapped source
	 * @deprecated use {@link Operators#onLastAssembly(CorePublisher)}
	 */
	@SuppressWarnings("unchecked")
	@Deprecated
	protected static <T> ParallelFlux<T> onLastAssembly(ParallelFlux<T> source) {
		Function<Publisher, Publisher> hook = Hooks.onLastOperatorHook;
		if (hook == null) {
			return source;
		}
		return (ParallelFlux<T>) Objects.requireNonNull(hook.apply(source),
				"LastOperator hook returned null");
	}

	@SuppressWarnings("unchecked")
	static <T> ParallelFlux<T> doOnSignal(ParallelFlux<T> source,
			@Nullable Consumer<? super T> onNext,
			@Nullable Consumer<? super T> onAfterNext,
			@Nullable Consumer<? super Throwable> onError,
			@Nullable Runnable onComplete,
			@Nullable Runnable onAfterTerminate,
			@Nullable Consumer<? super Subscription> onSubscribe,
			@Nullable LongConsumer onRequest,
			@Nullable Runnable onCancel) {
		return onAssembly(new ParallelPeek<>(source,
				onNext,
				onAfterNext,
				onError,
				onComplete,
				onAfterTerminate,
				onSubscribe,
				onRequest,
				onCancel));
	}

	static final <T> List<T> sortedMerger(List<T> a, List<T> b, Comparator<? super T> comparator) {
		int n = a.size() + b.size();
		if (n == 0) {
			return new ArrayList<>();
		}
		List<T> both = new ArrayList<>(n);

		Iterator<T> at = a.iterator();
		Iterator<T> bt = b.iterator();

		T s1 = at.hasNext() ? at.next() : null;
		T s2 = bt.hasNext() ? bt.next() : null;

		while (s1 != null && s2 != null) {
			if (comparator.compare(s1, s2) < 0) { // s1 comes before s2
				both.add(s1);
				s1 = at.hasNext() ? at.next() : null;
			}
			else {
				both.add(s2);
				s2 = bt.hasNext() ? bt.next() : null;
			}
		}

		if (s1 != null) {
			both.add(s1);
			while (at.hasNext()) {
				both.add(at.next());
			}
		}
		else if (s2 != null) {
			both.add(s2);
			while (bt.hasNext()) {
				both.add(bt.next());
			}
		}

		return both;
	}

}