/*
 * Copyright (C) 2002-2022 Sebastiano Vigna
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


package PACKAGE;

import java.util.Comparator;
import java.util.Spliterator;
import java.util.Objects;
import java.util.function.Consumer;
#if KEYS_REFERENCE
import java.util.function.Predicate;
#endif
import it.unimi.dsi.fastutil.SafeMath;
#if KEYS_BYTE_CHAR_SHORT_FLOAT
import WIDENED_PACKAGE.KEY_WIDENED_SPLITERATOR;
import WIDENED_PACKAGE.WIDENED_SPLITERATORS;
#endif

/** A class providing static methods and objects that do useful things with type-specific spliterators.
 *
 * @author C. Sean Young &lt;csyoung@google.com&gt;
 * @see java.util.Spliterators
 * @since 8.5.0
 */

public final class SPLITERATORS {

	private SPLITERATORS() {}

#if KEYS_PRIMITIVE
	static final int BASE_SPLITERATOR_CHARACTERISTICS = Spliterator.NONNULL;
#else
	static final int BASE_SPLITERATOR_CHARACTERISTICS = 0;
#endif

	// Default characteristics for various Collection implementations
	public static final int COLLECTION_SPLITERATOR_CHARACTERISTICS = BASE_SPLITERATOR_CHARACTERISTICS | Spliterator.SIZED;

	public static final int LIST_SPLITERATOR_CHARACTERISTICS = COLLECTION_SPLITERATOR_CHARACTERISTICS | Spliterator.ORDERED | Spliterator.SUBSIZED;

	public static final int SET_SPLITERATOR_CHARACTERISTICS = COLLECTION_SPLITERATOR_CHARACTERISTICS | Spliterator.DISTINCT;

	private static final int SORTED_CHARACTERISTICS = Spliterator.ORDERED | Spliterator.SORTED;

	public static final int SORTED_SET_SPLITERATOR_CHARACTERISTICS = SET_SPLITERATOR_CHARACTERISTICS | SORTED_CHARACTERISTICS;

	/** A class returning no elements and a type-specific spliterator interface.
	 *
	 * <p>This class may be useful to implement your own in case you subclass
	 * a type-specific spliterator.
	 */

	public static class EmptySpliterator KEY_GENERIC implements KEY_SPLITERATOR KEY_GENERIC, java.io.Serializable, Cloneable {

		private static final long serialVersionUID = 8379247926738230492L;
		private static final int CHARACTERISTICS = Spliterator.SIZED | Spliterator.SUBSIZED;

		protected EmptySpliterator() {}

#if KEYS_PRIMITIVE
		@Override
		public boolean tryAdvance(final METHOD_ARG_KEY_CONSUMER action) { return false; }
#endif

		DEPRECATED_IF_KEYS_PRIMITIVE
		@Override
		public boolean tryAdvance(final Consumer<? super KEY_GENERIC_CLASS> action) { return false; }

		@Override
		public KEY_SPLITERATOR KEY_GENERIC trySplit() { return null; }
		@Override
		public long estimateSize() { return 0; }
		@Override
		public int characteristics() { return CHARACTERISTICS; }

#if KEYS_PRIMITIVE
		@Override
		public void forEachRemaining(final METHOD_ARG_KEY_CONSUMER action) { }
#endif

		DEPRECATED_IF_KEYS_PRIMITIVE
		@Override
		public void forEachRemaining(final Consumer<? super KEY_GENERIC_CLASS> action) { }

		@Override
		public Object clone() { return EMPTY_SPLITERATOR; }

		private Object readResolve() { return EMPTY_SPLITERATOR; }
	}

	/** An empty spliterator. It is serializable and cloneable.
	 *
	 * <p>The class of this objects represent an abstract empty spliterator
	 * that can iterate as a type-specific spliterator.
	 */
	SUPPRESS_WARNINGS_KEY_RAWTYPES
	public static final EmptySpliterator EMPTY_SPLITERATOR = new EmptySpliterator();

#if KEYS_REFERENCE
	/** Returns an empty spliterator. It is serializable and cloneable.
	 *
	 * <p>The class of the object returned represent an abstract empty spliterator
	 * that can iterate as a type-specific (list) spliterator.
	 *
	 * <p>This method provides a typesafe access to {@link #EMPTY_SPLITERATOR}.
	 * @return an empty spliterator.
	 */
	SUPPRESS_WARNINGS_KEY_UNCHECKED
	public static KEY_GENERIC KEY_SPLITERATOR KEY_GENERIC emptySpliterator() { return EMPTY_SPLITERATOR; }
#endif


	/** a spliterator returning a single element. */

	private static class SingletonSpliterator KEY_GENERIC implements KEY_SPLITERATOR KEY_GENERIC {
		private final KEY_GENERIC_TYPE element;
		private final KEY_COMPARATOR KEY_SUPER_GENERIC comparator;
		private boolean consumed = false;
		private static final int CHARACTERISTICS = BASE_SPLITERATOR_CHARACTERISTICS
			| Spliterator.SIZED | Spliterator.SUBSIZED | Spliterator.ORDERED
			| Spliterator.DISTINCT | Spliterator.SORTED | Spliterator.IMMUTABLE;

		public SingletonSpliterator(final KEY_GENERIC_TYPE element) {
			this(element, null);
		}

		public SingletonSpliterator(final KEY_GENERIC_TYPE element, final KEY_COMPARATOR KEY_SUPER_GENERIC comparator) {
			this.element = element;
			this.comparator = comparator;
		}

		@Override
		public boolean tryAdvance(METHOD_ARG_KEY_CONSUMER action) {
			Objects.requireNonNull(action);
			if (consumed) return false;
			// Existing JVM implementations advance even if the action throw.
			consumed = true;
			action.accept(element);
			return true;
		}

		@Override
		public KEY_SPLITERATOR KEY_GENERIC trySplit() { return null; }
		@Override
		public long estimateSize() { return consumed ? 0 : 1; }
		@Override
		public int characteristics() {
#if KEYS_PRIMITIVE
			return CHARACTERISTICS;
#else
			return CHARACTERISTICS | (element != null ? Spliterator.NONNULL : 0);
#endif
		}

		@Override
#if KEYS_PRIMITIVE
		public void forEachRemaining(final METHOD_ARG_KEY_CONSUMER action) {
#else // ! KEY_PRIMITIVE == KEY_REFERENCE
		public void forEachRemaining(final Consumer<? super KEY_GENERIC_CLASS> action) {
#endif
			Objects.requireNonNull(action);
			if (!consumed) {
				consumed = true;
				action.accept(element);
			}
		}

		@Override
		public KEY_COMPARATOR KEY_SUPER_GENERIC getComparator() {
			return comparator;
		}

		@Override
		public long skip(long n) {
			if (n < 0) throw new IllegalArgumentException("Argument must be nonnegative: " + n);
			if (n == 0 || consumed) return 0;
			consumed = true;
			return 1;
		}
	}


	/** Returns a spliterator that iterates just over the given element.
	 *
	 * @param element the only element to be returned by a type-specific spliterator.
	 * @return a spliterator that iterates just over {@code element}.
	 */
	public static KEY_GENERIC KEY_SPLITERATOR KEY_GENERIC singleton(final KEY_GENERIC_TYPE element) {
		return new SingletonSpliterator KEY_GENERIC_DIAMOND(element);
	}

	/** Returns a spliterator that iterates just over the given element.
	 *
	 * <p>The {@link Spliterator#getComparator()} method will return the given comparator.
	 * This is within spec because sequences of size 1 are trivially sorted for any
	 * comparison function.
	 *
	 * @param element the only element to be returned by a type-specific spliterator.
	 * @param comparator the comparator to return when {@link Spliterator#getComparator()} is called.
	 * @return a spliterator that iterates just over {@code element}.
	 */
	public static KEY_GENERIC KEY_SPLITERATOR KEY_GENERIC singleton(final KEY_GENERIC_TYPE element, final KEY_COMPARATOR KEY_SUPER_GENERIC comparator) {
		return new SingletonSpliterator KEY_GENERIC_DIAMOND(element, comparator);
	}

	/** A class to wrap arrays in spiterators. */

	private static class ArraySpliterator KEY_GENERIC implements KEY_SPLITERATOR KEY_GENERIC {

		private static final int BASE_CHARACTERISTICS = BASE_SPLITERATOR_CHARACTERISTICS
			| Spliterator.SIZED | Spliterator.SUBSIZED | Spliterator.ORDERED;

		final KEY_GENERIC_TYPE[] array;
		private final int offset;
		private int length, curr;
		final int characteristics;

		public ArraySpliterator(final KEY_GENERIC_TYPE[] array, final int offset, final int length, int additionalCharacteristics) {
			this.array = array;
			this.offset = offset;
			this.length = length;
			characteristics = BASE_CHARACTERISTICS | additionalCharacteristics;
		}

		@Override
		public boolean tryAdvance(METHOD_ARG_KEY_CONSUMER action) {
			if (curr >= length) return false;
			Objects.requireNonNull(action);
			action.accept(array[offset + curr++]);
			return true;
		}

		@Override
		public long estimateSize() { return length - curr; }
		@Override
		public int characteristics() { return characteristics; }

		protected ArraySpliterator KEY_GENERIC makeForSplit(int newOffset, int newLength) {
			return new ArraySpliterator KEY_GENERIC_DIAMOND(array, newOffset, newLength, characteristics);
		}

		@Override
		public KEY_SPLITERATOR KEY_GENERIC trySplit() {
			int retLength = (length - curr) >> 1;
			if (retLength <= 1) return null;
			int myNewCurr = curr + retLength;
			int retOffset = offset + curr;
			// int myNewLength = length - retLength;
			this.curr = myNewCurr;
			// this.length = myNewLength;
			return makeForSplit(retOffset, retLength);
		}

		@Override
		public void forEachRemaining(final METHOD_ARG_KEY_CONSUMER action) {
			Objects.requireNonNull(action);
			for (; curr < length; ++curr) {
				action.accept(array[offset + curr]);
			}
		}

		@Override
		public long skip(long n) {
			if (n < 0) throw new IllegalArgumentException("Argument must be nonnegative: " + n);
			if (curr >= length) return 0;
			final int remaining = length - curr;
			if (n < remaining) {
				curr = SafeMath.safeLongToInt(curr + n);
				return n;
			}
			n = remaining;
			curr = length;
			return n;
		}
	}

	private static class ArraySpliteratorWithComparator KEY_GENERIC extends ArraySpliterator KEY_GENERIC {
		private final KEY_COMPARATOR KEY_SUPER_GENERIC comparator;

		public ArraySpliteratorWithComparator(final KEY_GENERIC_TYPE[] array, final int offset, final int length, int additionalCharacteristics, final KEY_COMPARATOR KEY_SUPER_GENERIC comparator) {
			super(array, offset, length, additionalCharacteristics | SORTED_CHARACTERISTICS);
			this.comparator = comparator;
		}

		@Override
		protected ArraySpliteratorWithComparator KEY_GENERIC makeForSplit(int newOffset, int newLength) {
			return new ArraySpliteratorWithComparator KEY_GENERIC_DIAMOND(array, newOffset, newLength, characteristics, comparator);
		}

		@Override
		public KEY_COMPARATOR KEY_SUPER_GENERIC getComparator() {
			return comparator;
		}
	}

	/** Wraps the given part of an array into a type-specific spliterator.
	 *
	 * <p>The type-specific spliterator returned by this method will iterate
	 * {@code length} times, advancing over consecutive elements of the given
	 * array starting from the one with index {@code offset}.
	 *
	 * <p>The returned spliterator will report {@linkplain Spliterator#characteristics() characteristics}
	 * {@link Spliterator#SIZED}, {@link Spliterator#SUBSIZED},
	 * {@link Spliterator#ORDERED}, and for primitive arrays, {@link Spliterator#NONNULL}.
	 *
	 * @param array an array to wrap into a type-specific spliterator.
	 * @param offset the first element of the array to be returned.
	 * @param length the number of elements to return.
	 * @return a spliterator that will iterate over {@code length} elements of {@code array} starting at position {@code offset}.
	 */
	public static KEY_GENERIC KEY_SPLITERATOR KEY_GENERIC wrap(final KEY_GENERIC_TYPE[] array, final int offset, final int length) {
		ARRAYS.ensureOffsetLength(array, offset, length);
		return new ArraySpliterator KEY_GENERIC_DIAMOND(array, offset, length, 0);
	}

	/** Wraps the given array into a type-specific spliterator.
	 *
	 * <p>The type-specific spliterator returned by this method will advance over
	 * all elements of the given array.
	 *
	 * <p>The returned spliterator will report {@linkplain Spliterator#characteristics() characteristics}
	 * {@link Spliterator#SIZED}, {@link Spliterator#SUBSIZED},
	 * {@link Spliterator#ORDERED}, and for primitive arrays, {@link Spliterator#NONNULL}.
	 *
	 * @param array an array to wrap into a type-specific spliterator.
	 * @return a spliterator that will iterate over the elements of {@code array}.
	 */
	public static KEY_GENERIC KEY_SPLITERATOR KEY_GENERIC wrap(final KEY_GENERIC_TYPE[] array) {
		return new ArraySpliterator KEY_GENERIC_DIAMOND(array, 0, array.length, 0);
	}

	/** Wraps the given part of an array into a type-specific spliterator.
	 *
	 * <p>The type-specific spliterator returned by this method will iterate
	 * {@code length} times, advancing over consecutive elements of the given
	 * array starting from the one with index {@code offset}.
	 *
	 * <p>The returned spliterator will report {@linkplain Spliterator#characteristics() characteristics}
	 * {@link Spliterator#SIZED}, {@link Spliterator#SUBSIZED},
	 * {@link Spliterator#ORDERED}, and for primitive arrays, {@link Spliterator#NONNULL},
	 * on top of any additional characteristics given in {@code additionalCharacteristics} (for example, if
	 * the caller knows the backing array has distinct elements, they can pass {@link Spliterator#DISTINCT}).
	 *
	 * @param array an array to wrap into a type-specific spliterator.
	 * @param offset the first element of the array to be returned.
	 * @param length the number of elements to return.
	 * @param additionalCharacteristics any additional characteristics to report.
	 * @return a spliterator that will iterate over {@code length} elements of {@code array} starting at position {@code offset}.
	 */
	public static KEY_GENERIC KEY_SPLITERATOR KEY_GENERIC wrap(final KEY_GENERIC_TYPE[] array, final int offset, final int length, final int additionalCharacteristics) {
		ARRAYS.ensureOffsetLength(array, offset, length);
		return new ArraySpliterator KEY_GENERIC_DIAMOND(array, offset, length, additionalCharacteristics);
	}

	/** Wraps the given part of a sorted array into a type-specific spliterator.
	 *
	 * <p>It is the caller's responsibility to ensure the array is actually sorted using
	 * the comparator given.
	 *
	 * <p>The type-specific spliterator returned by this method will iterate
	 * {@code length} times, advancing over consecutive elements of the given
	 * array starting from the one with index {@code offset}.
	 *
	 * <p>The returned spliterator will report {@linkplain Spliterator#characteristics() characteristics}
	 * {@link Spliterator#SIZED}, {@link Spliterator#SUBSIZED},
	 * {@link Spliterator#ORDERED}, {@link Spliterator#SORTED}, and for primitive arrays,
	 * {@link Spliterator#NONNULL},
	 * on top of any additional characteristics given in {@code additionalCharacteristics} (for example, if
	 * the caller knows the backing array has distinct elements, they can pass {@link Spliterator#DISTINCT}).
	 *
	 * @param array an array to wrap into a type-specific spliterator.
	 * @param offset the first element of the array to be returned.
	 * @param length the number of elements to return.
	 * @param additionalCharacteristics any additional characteristics to report.
	 * @param comparator the comparator the array was sorted with (or {@code null} for natural ordering)
	 * @return a spliterator that will iterate over {@code length} elements of {@code array} starting at position {@code offset}.
	 */
	public static KEY_GENERIC KEY_SPLITERATOR KEY_GENERIC wrapPreSorted(
			final KEY_GENERIC_TYPE[] array, final int offset, final int length, final int additionalCharacteristics, KEY_COMPARATOR KEY_SUPER_GENERIC comparator) {
		ARRAYS.ensureOffsetLength(array, offset, length);
		return new ArraySpliteratorWithComparator KEY_GENERIC_DIAMOND(array, offset, length, additionalCharacteristics, comparator);
	}

	/** Wraps the given part of a sorted array into a type-specific spliterator.
	 *
	 * <p>It is the caller's responsibility to ensure the array is actually sorted using
	 * the comparator given.
	 *
	 * <p>The type-specific spliterator returned by this method will iterate
	 * {@code length} times, advancing over consecutive elements of the given
	 * array starting from the one with index {@code offset}.
	 *
	 * <p>The returned spliterator will report {@linkplain Spliterator#characteristics() characteristics}
	 * {@link Spliterator#SIZED}, {@link Spliterator#SUBSIZED},
	 * {@link Spliterator#ORDERED}, {@link Spliterator#SORTED}, and for primitive arrays,
	 * {@link Spliterator#NONNULL}.
	 *
	 * @param array an array to wrap into a type-specific spliterator.
	 * @param offset the first element of the array to be returned.
	 * @param length the number of elements to return.
	 * @param comparator the comparator the array was sorted with (or {@code null} for natural ordering)
	 * @return a spliterator that will iterate over {@code length} elements of {@code array} starting at position {@code offset}.
	 */
	public static KEY_GENERIC KEY_SPLITERATOR KEY_GENERIC wrapPreSorted(
			final KEY_GENERIC_TYPE[] array, final int offset, final int length, KEY_COMPARATOR KEY_SUPER_GENERIC comparator) {
		return wrapPreSorted(array, offset, length, 0, comparator);
	}

	/** Wraps the given sorted array into a type-specific spliterator.
	 *
	 * <p>It is the caller's responsibility to ensure the array is actually sorted using
	 * the comparator given.
	 *
	 * <p>The type-specific spliterator returned by this method will advance over
	 * all elements of the given array.
	 *
	 * <p>The returned spliterator will report {@linkplain Spliterator#characteristics() characteristics}
	 * {@link Spliterator#SIZED}, {@link Spliterator#SUBSIZED},
	 * {@link Spliterator#ORDERED}, {@link Spliterator#SORTED}, and for primitive arrays,
	 * {@link Spliterator#NONNULL}.
	 *
	 * @param array an array to wrap into a type-specific spliterator.
	 * @param comparator the comparator the array was sorted with (or {@code null} for natural ordering)
	 * @return a spliterator that will iterate over {@code length} elements of {@code array} starting at position {@code offset}.
	 */
	public static KEY_GENERIC KEY_SPLITERATOR KEY_GENERIC wrapPreSorted(
			final KEY_GENERIC_TYPE[] array, KEY_COMPARATOR KEY_SUPER_GENERIC comparator) {
		return wrapPreSorted(array, 0, array.length, comparator);
	}

	// There is no non-comparator version of wrapPreSorted; because Spliterator has to return the Comparator
	// it is ordered with respect to, the caller should think about the Spliterator they use.

	// wrap, unwrap, and pour are not provided because if you are using Spliterators, you typically
	// are going to be using streams. That and Spliterator's API isn't well suited for these
	// types of tasks.

	private static class SpliteratorWrapper KEY_GENERIC implements KEY_SPLITERATOR KEY_GENERIC {
		final Spliterator<KEY_GENERIC_CLASS> i;

		public SpliteratorWrapper(final Spliterator<KEY_GENERIC_CLASS> i) {
			this.i = i;
		}

#if KEYS_INT_LONG_DOUBLE
		// This is pretty much the only time overriding this overload is correct; we want to
		// delegate as an Object consumer, not wrap it as a primitive one.
		@Override
		public boolean tryAdvance(final KEY_CONSUMER action) {
			return i.tryAdvance(action);
		}
#endif

#if KEYS_INT_LONG_DOUBLE
		@SuppressWarnings("unchecked")
#endif
#if KEYS_PRIMITIVE
		@Override
		public boolean tryAdvance(final METHOD_ARG_KEY_CONSUMER action) {
#if KEYS_INT_LONG_DOUBLE // The JDK's IntConsumer is not a subclass of Consumer, so we need another lambda.
			Objects.requireNonNull(action);
			return i.tryAdvance(action instanceof Consumer ? (Consumer<? super KEY_GENERIC_CLASS>)action : action::accept);
#else
			return i.tryAdvance(action);
#endif
		}
#endif

		DEPRECATED_IF_KEYS_PRIMITIVE
		@Override
		public boolean tryAdvance(final Consumer<? super KEY_GENERIC_CLASS> action) {
			return i.tryAdvance(action);
		}

#if KEYS_INT_LONG_DOUBLE
		// This is pretty much the only time overriding this overload is correct; we want to
		// delegate as an Object consumer, not wrap it as a primitive one.
		@Override
		public void forEachRemaining(final KEY_CONSUMER action) {
			i.forEachRemaining(action);
		}
#endif

#if KEYS_INT_LONG_DOUBLE
		@SuppressWarnings("unchecked")
#endif
#if KEYS_PRIMITIVE
		@Override
		public void forEachRemaining(final METHOD_ARG_KEY_CONSUMER action) {
#if KEYS_INT_LONG_DOUBLE // The JDK's IntConsumer is not a subclass of Consumer, so we need another lambda.
			Objects.requireNonNull(action);
			i.forEachRemaining(action instanceof Consumer ? (Consumer<? super KEY_GENERIC_CLASS>)action : action::accept);
#else
			i.forEachRemaining(action);
#endif
		}
#endif

		DEPRECATED_IF_KEYS_PRIMITIVE
		@Override
		public void forEachRemaining(final Consumer<? super KEY_GENERIC_CLASS> action) {
			i.forEachRemaining(action);
		}

		@Override
		public long estimateSize() { return i.estimateSize(); }

		@Override
		public int characteristics() { return i.characteristics(); }

		@Override
		public KEY_COMPARATOR KEY_SUPER_GENERIC getComparator() {
			return COMPARATORS.AS_KEY_COMPARATOR(i.getComparator());
		}

		@Override
		public KEY_SPLITERATOR KEY_GENERIC trySplit() {
			Spliterator<KEY_GENERIC_CLASS> innerSplit = i.trySplit();
			if (innerSplit == null) return null;
			return new SpliteratorWrapper KEY_GENERIC_DIAMOND(innerSplit);
		}
	}

	private static class SpliteratorWrapperWithComparator KEY_GENERIC extends SpliteratorWrapper KEY_GENERIC {
		final KEY_COMPARATOR KEY_SUPER_GENERIC comparator;

		public SpliteratorWrapperWithComparator(final Spliterator<KEY_GENERIC_CLASS> i, final KEY_COMPARATOR KEY_SUPER_GENERIC comparator) {
			super(i);
			this.comparator = comparator;
		}

		@Override
		public KEY_COMPARATOR KEY_SUPER_GENERIC getComparator() {
			return comparator;
		}

		@Override
		public KEY_SPLITERATOR KEY_GENERIC trySplit() {
			Spliterator<KEY_GENERIC_CLASS> innerSplit = i.trySplit();
			if (innerSplit == null) return null;
			return new SpliteratorWrapperWithComparator KEY_GENERIC_DIAMOND(innerSplit, comparator);
		}
	}

#if KEYS_PRIMITIVE && ! KEY_CLASS_Boolean
	private static class PrimitiveSpliteratorWrapper KEY_GENERIC implements KEY_SPLITERATOR KEY_GENERIC {
		final JDK_PRIMITIVE_SPLITERATOR i;

		public PrimitiveSpliteratorWrapper(final JDK_PRIMITIVE_SPLITERATOR i) {
			this.i = i;
		}

		@Override
		public boolean tryAdvance(final METHOD_ARG_KEY_CONSUMER action) {
			return i.tryAdvance(action);
		}

		@Override
		public void forEachRemaining(final METHOD_ARG_KEY_CONSUMER action) {
			i.forEachRemaining(action);
		}

		@Override
		public long estimateSize() { return i.estimateSize(); }

		@Override
		public int characteristics() { return i.characteristics(); }

		@Override
		public KEY_COMPARATOR KEY_SUPER_GENERIC getComparator() {
#if KEYS_BYTE_CHAR_SHORT_FLOAT
				// Get comparator outside of lambda to fail-fast in case inner call throws.
				final java.util.Comparator<? super KEY_CLASS_WIDENED> comp = i.getComparator();
				return (KEY_TYPE left, KEY_TYPE right) -> comp.compare(KEY_CLASS_WIDENED.valueOf(left), KEY_CLASS_WIDENED.valueOf(right));
#else
				return COMPARATORS.AS_KEY_COMPARATOR(i.getComparator());
#endif
		}

		@Override
		public KEY_SPLITERATOR KEY_GENERIC trySplit() {
			JDK_PRIMITIVE_SPLITERATOR innerSplit = i.trySplit();
			if (innerSplit == null) return null;
			return new PrimitiveSpliteratorWrapper(innerSplit);
		}
	}

	private static class PrimitiveSpliteratorWrapperWithComparator KEY_GENERIC extends PrimitiveSpliteratorWrapper KEY_GENERIC {
		final KEY_COMPARATOR KEY_SUPER_GENERIC comparator;

		public PrimitiveSpliteratorWrapperWithComparator(final JDK_PRIMITIVE_SPLITERATOR i, final KEY_COMPARATOR KEY_SUPER_GENERIC comparator) {
			super(i);
			this.comparator = comparator;
		}

		@Override
		public KEY_COMPARATOR KEY_SUPER_GENERIC getComparator() {
			return comparator;
		}

		@Override
		public KEY_SPLITERATOR KEY_GENERIC trySplit() {
			JDK_PRIMITIVE_SPLITERATOR innerSplit = i.trySplit();
			if (innerSplit == null) return null;
			return new PrimitiveSpliteratorWrapperWithComparator(innerSplit, comparator);
		}
	}
#endif


	/** Wraps a standard spliterator into a type-specific spliterator.
	 *
	 * <p>This method wraps a standard spliterator into a type-specific one which will handle the
	 * type conversions for you. Of course, any attempt to wrap a spliterator returning the
	 * instances of the wrong class will generate a {@link ClassCastException}. The
	 * returned spliterator is backed by {@code i}: changes to one of the spliterators
	 * will affect the other, too.
	 *
	 * <p>If {@code i} is already type-specific, it will returned and no new object
	 * will be generated.
	 *
	 * @param i a spliterator.
	 * @return a type-specific spliterator backed by {@code i}.
	 */
#if KEYS_PRIMITIVE
	@SuppressWarnings({"unchecked","rawtypes"})
#endif
	public static KEY_GENERIC KEY_SPLITERATOR KEY_GENERIC AS_KEY_SPLITERATOR(final Spliterator KEY_GENERIC i) {
		if (i instanceof KEY_SPLITERATOR) return (KEY_SPLITERATOR KEY_GENERIC)i;
#if KEYS_INT_LONG_DOUBLE
		if (i instanceof JDK_PRIMITIVE_SPLITERATOR) return new PrimitiveSpliteratorWrapper KEY_GENERIC_DIAMOND((JDK_PRIMITIVE_SPLITERATOR)i);
#endif
		return new SpliteratorWrapper KEY_GENERIC_DIAMOND(i);
	}

	/** Wraps a standard spliterator into a type-specific spliterator.
	 *
	 * <p>This method wraps a standard spliterator into a type-specific one which will handle the
	 * type conversions for you. Of course, any attempt to wrap a spliterator returning the
	 * instances of the wrong class will generate a {@link ClassCastException}. The
	 * returned spliterator is backed by {@code i}: changes to one of the spliterators
	 * will affect the other, too.
	 *
	 * <p>This method will cause the returned spliterator's {@link Spliterator#getComparator()} method
	 * to always return {@code comparatorOverride}, regardless of what the wrapped spliterator's
	 * {@code getComparator()} method returns.
	 *
	 * <p><b>NOTE:</b>This is mostly intended for supporting default
	 * implementations in interfaces that wrap JDK spliterators, and not a general purpose method.
	 *
	 * <p>If {@code i} is already type-specific, this method will throw, as such spliterators already
	 * have a {@code getComparator()} that returns a properly typed comparator.
	 *
	 * @param i a spliterator.
	 * @param comparatorOverride the comparator to return when {@link Spliterator#getComparator()}
	 * @return a type-specific spliterator backed by {@code i}.
	 */
#if KEYS_PRIMITIVE
	@SuppressWarnings({"unchecked","rawtypes"})
#endif
	public static KEY_GENERIC KEY_SPLITERATOR KEY_GENERIC AS_KEY_SPLITERATOR(final Spliterator KEY_GENERIC i, final KEY_COMPARATOR KEY_SUPER_GENERIC comparatorOverride) {
		if (i instanceof KEY_SPLITERATOR) throw new IllegalArgumentException("Cannot override comparator on instance that is already a " + KEY_SPLITERATOR.class.getSimpleName());
#if defined JDK_PRIMITIVE_SPLITERATOR && KEYS_PRIMITIVE
		if (i instanceof JDK_PRIMITIVE_SPLITERATOR) return new PrimitiveSpliteratorWrapperWithComparator KEY_GENERIC_DIAMOND((JDK_PRIMITIVE_SPLITERATOR)i, comparatorOverride);
#endif
		return new SpliteratorWrapperWithComparator KEY_GENERIC_DIAMOND(i, comparatorOverride);
	}

#if KEYS_BYTE_CHAR_SHORT_FLOAT
	/** Wrap a JDK primitive spliterator to a type-specific spliterator, making checked
	 * narrowed casts.<p>
	 * The {@code tryAdvance} method throws {@link IllegalArgumentException} if any element would underflow or overflow.
	 *
	 * @param i a spiterator
	 * @return a type-specific spliterator backed by {@code i}
	 */
	public static KEY_GENERIC KEY_SPLITERATOR KEY_GENERIC narrow(final JDK_PRIMITIVE_SPLITERATOR i) {
#if KEYS_INT_LONG_DOUBLE
		if (i instanceof KEY_SPLITERATOR) return (KEY_SPLITERATOR KEY_GENERIC)i;
#endif
		return new PrimitiveSpliteratorWrapper KEY_GENERIC_DIAMOND(i);
	}
#endif

#if KEYS_BYTE_CHAR_SHORT_FLOAT
#if KEY_CLASS_Character
	/** Wrap a type-specific spliterator to a JDK compatible primitive spliterator.
	 *
	 * <p><b>WARNING</b>: This is <em>not</em> the same as converting the source to a sequence
	 * of code points. This returned instance literally performs {@code (int)(charValue)} casts.
	 * Surrogate pairs will be left as separate elements instead of combined into a single element
	 * with the code point it represents.
	 *
	 * @param i a spliterator
	 * @return a JDK compatible primitive spliterator backed by {@code i}
	 */
#else
	/** Wrap a type-specific spliterator to a JDK compatible primitive spliterator.
	 *
	 * @param i a spliterator
	 * @return a JDK compatible primitive spliterator backed by {@code i}
	 */
#endif
	public static KEY_WIDENED_SPLITERATOR widen(KEY_SPLITERATOR i) { return WIDENED_SPLITERATORS.wrap(i); }
#endif

	/**
	 * Perform the given {@code action} on each element that matches the given {@code predicate}.
	 *
	 * <p>This is equivalent to {@code java.util.stream.StreamSupport.stream(spliterator).filter(predicate).forEach(action)}
	 * (substitute the proper primitive stream as needed), except it may perform better (but no potential for parallelism).
	 */
	public static KEY_GENERIC void onEachMatching(final STD_KEY_SPLITERATOR KEY_GENERIC spliterator, final METHOD_ARG_PREDICATE predicate, final METHOD_ARG_KEY_CONSUMER action) {
		Objects.requireNonNull(predicate);
		Objects.requireNonNull(action);

		spliterator.forEachRemaining((KEY_GENERIC_TYPE value) -> {
			if (predicate.test(value)) {
				action.accept(value);
			}
		});
	}
#if KEYS_BYTE_CHAR_SHORT_FLOAT
	/**
	 * Perform the given {@code action} on each element that matches the given {@code predicate}.
	 *
	 * <p>This is equivalent to {@code java.util.stream.StreamSupport.stream(spliterator).filter(predicate).forEach(action)}
	 * (substitute the proper primitive stream as needed), except it may perform better (but no potential for parallelism).
	 */
	public static KEY_GENERIC void onEachMatching(final STD_KEY_SPLITERATOR KEY_GENERIC spliterator, final JDK_PRIMITIVE_PREDICATE predicate, final JDK_PRIMITIVE_KEY_CONSUMER action) {
		Objects.requireNonNull(predicate);
		Objects.requireNonNull(action);

		// No lambda introduction here; this isn't overrideable (static method) and widening casts are implicit. 
		spliterator.forEachRemaining((KEY_GENERIC_TYPE value) -> {
			if (predicate.test(value)) {
				action.accept(value);
			}
		});
	}
#endif

	/**
	 * A skeletal implementation for a spliterator backed by an index based data store. High performance
	 * concrete implementations (like the main Spliterator of ArrayList) generally should avoid using this
	 * and just implement the interface directly, but should be decent for less
	 * performance critical implementations.
	 *
	 * <p>This class is only appropriate for sequences that are at most {@link Integer#MAX_VALUE} long.
	 * If your backing data store can be bigger then this, consider the equivalently named class in
	 * the type specific {@code BigSpliterators} class.
	 *
	 * <p>As the abstract methods in this class are used in inner loops, it is generally a
	 * good idea to override the class as {@code final} as to encourage the JVM to inline
	 * them (or alternatively, override the abstract methods as final).
	 */
	public static abstract class AbstractIndexBasedSpliterator KEY_GENERIC extends KEY_ABSTRACT_SPLITERATOR KEY_GENERIC {

		/** The current position index, the index of the item to be given after the next call to {@link #tryAdvance}.
		 *
		 * <p>This value will be between {@code minPos} and {@link #getMaxPos()} (exclusive) (on a best effort, so concurrent
		 * structural modifications may cause this to be violated, but that usually invalidates spliterators anyways).
		 * Thus {@code pos} being {@code minPos + 2} would mean {@link #tryAdvance}
		 * was called twice and the next call will give the third element of this spliterator.
		 */
		protected int pos;

		protected AbstractIndexBasedSpliterator(int initialPos) {
			this.pos = initialPos;
		}

		// When you implement these, you should probably declare them final to encourage the JVM to inline them.

		/** Get the item corresponding to the given index location.
		 *
		 * <p>Do <em>not</em> advance {@link #pos} in this method; the default {@link #tryAdvance} and
		 * {@link #forEachRemaining} methods takes care of this.
		 *
		 * <p>The {@code location} given will be between {@code minPos} and {@link #getMaxPos()} (exclusive).
		 * Thus, a {@code location} of {@code minPos + 2} would mean {@link #tryAdvance} was called twice
		 * and this method should return what the next call to {@link #tryAdvance()} should give.
		 */
		protected abstract KEY_GENERIC_TYPE get(int location);

		/** The maximum pos can be, and is the logical end (exclusive) of the "range".
		 *
		 * <p>If pos is equal to the return of this method, this means the last element has been returned and the next call to {@link #tryAdvance} will return {@code false}.
		 *
		 * <p>Usually set return the parent {@linkplain java.util.Collection#size() collection's size}, but does not have to be
		 * (for example, sublists and subranges).
		 *
		 * <p>This method allows the implementation to decide how it binds on the size (late or early).
		 * However, {@link EarlyBindingSizeIndexBasedSpliterator} and {@link LateBindingSizeIndexBasedSpliterator} give
		 * an implementation of this method for the two most common strategies.
		 */
		protected abstract int getMaxPos();

		/** Make a new spliterator to {@link #trySplit()} starting with the given {@code pos}
		 * and ending at the given {@code maxPos}.
		 *
		 * <p>An implementation is free to look at the range given, and if it deems it too small
		 * to split further, return {@code null}. In which case, {@link #trySplit()} will not
		 * modify the state of this spliterator.
		 *
		 * <p>Do <em>not</em> modify {@link #pos} in this method; the default {@link #trySplit()}
		 * method takes care of this.
		 *
		 * <p>To comply with the spec of {@link Spliterator#ORDERED}, this will
		 * only be called to create prefixes of the current sequence this spliterator is over,
		 * and this instance will start at the end of the returned sequence and have the same
		 * end point.
		 * As such, this method should also not change what {@link #getMaxPos()} returns.
		 */
		protected abstract KEY_SPLITERATOR KEY_GENERIC makeForSplit(int pos, int maxPos);

		/** Compute where to split on the next {@link #trySplit()}, given the current pos and
		 * {@link #getMaxPos()} (or any other metric the implementation wishes to use).
		 *
		 * <p>If a value {@code == pos} or {@code == getMaxPos()} is returned, the
		 * {@link #trySplit()} method will assume a split of size 0 was computed,
		 * and thus won't split or change state. If a value outside that range is
		 * returned, then {@link #trySplit()} will throw {@link IndexOutOfBoundsException}.
		 * In particular, this means that no handling of overflow or underflow
		 * is performed.
		 *
		 * @apiNote The reasoning behind the throwing if out of range behavior is that, even
		 * though it can significantly slow the process of splitting, it is much better then
		 * risking a buggy implementation causing splits to stop happening much earlier then
		 * intended. Also, splitting is not usually in the "inner loop" of stream operations,
		 * so this slowness isn't in the bottleneck. That and we have already warned that
		 * high performance spliterators should prefer implementing all the methods themselves
		 * instead of through this interface.
		 *
		 * @implSpec This default implementation is a simple split-by-2 strategy, dividing
		 * in the middle of pos and {@link #getMaxPos()}. It is unspecified whether
		 * the first range or the second range will be larger in the case of an odd length range.
		 */
		protected int computeSplitPoint() {
			// Overflow safe midpoint computation.
			return pos + ((getMaxPos() - pos) / 2);
		}

		private void splitPointCheck(final int splitPoint, final int observedMax) {
			// TODO When minimum Java version becomes Java 9, use Objects.checkFromToIndex​ (after first letting == max case pass through)
			if (splitPoint < pos || splitPoint > observedMax) {
				throw new IndexOutOfBoundsException("splitPoint " + splitPoint + " outside of range of current position " + pos + " and range end " + observedMax);
			}
		}

		// Since this is an index based spliterator, list characteristics make sense.
		@Override
		public int characteristics() { return SPLITERATORS.LIST_SPLITERATOR_CHARACTERISTICS; }

		@Override
		public long estimateSize() { return (long)getMaxPos() - pos; }

		@Override
		public boolean tryAdvance(final METHOD_ARG_KEY_CONSUMER action) {
			if (pos >= getMaxPos()) return false;
			action.accept(get(pos++));
			return true;
		}

		@Override
		public void forEachRemaining(final METHOD_ARG_KEY_CONSUMER action) {
			for (final int max = getMaxPos(); pos < max; ++pos) {
				action.accept(get(pos));
			}
		}

		// TODO since this method doesn't depend on the type at all, should it be "hoisted" into a
		// non type-specific superclass in it.unimi.dsi.fastutil?
		@Override
		public long skip(long n) {
			if (n < 0) throw new IllegalArgumentException("Argument must be nonnegative: " + n);
			final int max = getMaxPos();
			if (pos >= max) return 0;
			final int remaining = max - pos;
			if (n < remaining) {
				pos = SafeMath.safeLongToInt(pos + n);
				return n;
			}
			n = remaining;
			pos = max;
			return n;
		}

		// TODO since this method doesn't depend on the type at all, should it be "hoisted" into a
		// non type-specific superclass in it.unimi.dsi.fastutil?
		/** {@inheritDoc}
		 *
		 * @implSpec This implementation always returns a prefix of the elements, in order to comply with
		 * the {@link Spliterator#ORDERED} property. This means this current iterator does not need to
		 * to update what {@link #getMaxPos()} returns in response to this method (but it may do
		 * "book-keeping" on it based on binding strategy).
		 *
		 * <p>The split point is computed by {@link #computeSplitPoint()}; see that method for details.
		 *
		 * @throws IndexOutOfBoundsException if the return of {@link #computeSplitPoint()} was
		 *  {@code < pos} or {@code > {@link #getMaxPos()}}.
		 */
		@Override
		public KEY_SPLITERATOR KEY_GENERIC trySplit() {
			final int max = getMaxPos();
			final int splitPoint = computeSplitPoint();
			if (splitPoint == pos || splitPoint == max) return null;
			splitPointCheck(splitPoint, max);
			int oldPos = pos;
			KEY_SPLITERATOR KEY_GENERIC maybeSplit = makeForSplit(oldPos, splitPoint);
			if (maybeSplit != null) this.pos = splitPoint;
			return maybeSplit;
		}
	}

	/**
	 * A skeletal implementation for a spliterator backed by an index based data store. High performance
	 * concrete implementations (like the main Spliterator of ArrayList) generally should avoid using this
	 * and just implement the interface directly, but should be decent for less
	 * performance critical implementations.
	 *
	 * <p>This class implements an early binding strategy for {@link #getMaxPos()}. The last index
	 * this spliterator covers is fixed at construction time and does not vary on changes to the
	 * backing data store. This should usually be the {@linkplain java.util.Collection#size() size} of the
	 * backing data store (until a split at least), hence the class' name, but this is not required.
	 *
	 * <p>As the abstract methods in this class are used in inner loops, it is generally a
	 * good idea to override the class as {@code final} as to encourage the JVM to inline
	 * them (or alternatively, override the abstract methods as final).
	 */
	 public static abstract class EarlyBindingSizeIndexBasedSpliterator KEY_GENERIC extends AbstractIndexBasedSpliterator KEY_GENERIC {
	 	/** The maximum {@link #pos} can be */
	 	protected final int maxPos;

		protected EarlyBindingSizeIndexBasedSpliterator(int initialPos, int maxPos) {
			super(initialPos);
			this.maxPos = maxPos;
		}


		@Override
		protected final int getMaxPos() { return maxPos; }
	}

	/**
	 * A skeletal implementation for a spliterator backed by an index based data store. High performance
	 * concrete implementations (like the main Spliterator of ArrayList) generally should avoid using this
	 * and just implement the interface directly, but should be decent for less
	 * performance critical implementations.
	 *
	 * <p>This class implements a late binding strategy. On a new, non-split instance, the
	 * {@link #getMaxPos() max pos} will track the given data store (usually it's
	 * {@linkplain java.util.Collection#size() size}, hence the class' name). On the first
	 * {@linkplain #trySplit() split}, the last index will be read from the backing data store one
	 * last time and then be fixed for the remaining duration of this instance.<br>
	 * The returned split <em>should</em> should also be have a constant {@code maxPos}.
	 *
	 * <p>As the abstract methods in this class are used in inner loops, it is generally a
	 * good idea to override the class as {@code final} as to encourage the JVM to inline
	 * them (or alternatively, override the abstract methods as final).
	 */
	 public static abstract class LateBindingSizeIndexBasedSpliterator KEY_GENERIC extends AbstractIndexBasedSpliterator KEY_GENERIC {
	 	/** The maximum {@link #pos} can be, or -1 if it hasn't been fixed yet. */
	 	protected int maxPos = -1;
	 	private boolean maxPosFixed;

		protected LateBindingSizeIndexBasedSpliterator(int initialPos) {
			super(initialPos);
			this.maxPosFixed = false;
		}

		protected LateBindingSizeIndexBasedSpliterator(int initialPos, int fixedMaxPos) {
			super(initialPos);
			this.maxPos = fixedMaxPos;
			this.maxPosFixed = true;
		}

		/** Return the maximum pos can be dynamically tracking the backing data store.
		 *
		 * <p>This method will be the return value of {@link #getMaxPos()} until this spliterator
		 * is {@linkplain #trySplit()} split, in which case its final return value will be saved
		 * and remain constant for the rest of the duration of this instance.
		 */
		protected abstract int getMaxPosFromBackingStore();

		@Override
		protected final int getMaxPos() { return maxPosFixed ? maxPos : getMaxPosFromBackingStore(); }

		@Override
		public KEY_SPLITERATOR KEY_GENERIC trySplit() {
			KEY_SPLITERATOR KEY_GENERIC maybeSplit = super.trySplit();
			if (!maxPosFixed && maybeSplit != null) {
				maxPos = getMaxPosFromBackingStore();
				maxPosFixed = true;
			}
			return maybeSplit;
		}
	}

#if KEY_CLASS_Integer || KEY_CLASS_Byte || KEY_CLASS_Short || KEY_CLASS_Character || KEY_CLASS_Long

	private static class IntervalSpliterator implements KEY_SPLITERATOR {
		private static final int DONT_SPLIT_THRESHOLD = 2;
#if KEY_CLASS_Long
		private static final long MAX_SPLIT_SIZE = 1 << 30;
#endif
		private static final int CHARACTERISTICS = BASE_SPLITERATOR_CHARACTERISTICS
			| Spliterator.SIZED | Spliterator.SUBSIZED | Spliterator.ORDERED
			| Spliterator.DISTINCT | Spliterator.SORTED | Spliterator.IMMUTABLE;

		private KEY_TYPE curr, to;

		public IntervalSpliterator(final KEY_TYPE from, final KEY_TYPE to) {
			this.curr = from;
			this.to = to;
		}

		@Override
		public boolean tryAdvance(final METHOD_ARG_KEY_CONSUMER action) {
			if (curr >= to) return false;
			action.accept(curr++);
			return true;
		}

		@Override
#if KEYS_PRIMITIVE
		public void forEachRemaining(final METHOD_ARG_KEY_CONSUMER action) {
#else // ! KEY_PRIMITIVE == KEY_REFERENCE
		public void forEachRemaining(final Consumer<? super KEY_GENERIC_CLASS> action) {
#endif
			Objects.requireNonNull(action);
			for (; curr < to; ++curr) {
				action.accept(curr);
			}
		}

		@Override
		public long estimateSize() {
#if KEY_CLASS_Integer
			return (long)to - curr;
#else
			return to - curr;
#endif
		}

		@Override
		public int characteristics() {
			return CHARACTERISTICS;
		}

		@Override
		public KEY_COMPARATOR getComparator() {
			// Return null to indicate natural ordering.
			return null;
		}

		@Override
		public KEY_SPLITERATOR trySplit() {
#if KEY_CLASS_Integer || KEY_CLASS_Long
			long remaining = to - curr;
#else
			int remaining = to - curr;
#endif
#if KEY_CLASS_Long
			long mid = curr + (remaining >> 1);
			// Can be less then 0 if range overflowed.
			if (remaining > 2 * MAX_SPLIT_SIZE || remaining < 0) {
				mid = curr + MAX_SPLIT_SIZE;
			}
#else
			KEY_TYPE mid = (KEY_TYPE)(curr + (remaining >> 1));
#endif
			if (remaining >= 0 && remaining <= DONT_SPLIT_THRESHOLD) return null;
			KEY_TYPE old_curr = curr;
			curr = mid;
			return new IntervalSpliterator(old_curr, mid);
		}

		@Override
		public long skip(long n) {
			if (n < 0) throw new IllegalArgumentException("Argument must be nonnegative: " + n);
			if (curr >= to) return 0;
			// Can't do the traditional int remaining = to - curr
			// because that could overflow, due to signed from and to (and thus curr).
			long newCurr = curr + n;
			// Can be less then "curr" if overflow
			if (newCurr <= to && newCurr >= curr) {
				curr = KEY_LONG_NARROWING(newCurr);
				return n;
			}
			n = to - curr;
			curr = to;
			return n;
		}
	}


	/** Creates a type-specific spliterator over an interval.
	 *
	 * <p>The type-specific spliterator returned by this method will return the
	 * elements {@code from}, {@code from+1},&hellip;, {@code to-1}.
	 *
	 * @param from the starting element (inclusive).
	 * @param to the ending element (exclusive).
	 * @return a type-specific spliterator enumerating the elements from {@code from} to {@code to}.
	 */
	public static KEY_SPLITERATOR fromTo(final KEY_TYPE from, final KEY_TYPE to) {
		return new IntervalSpliterator(from, to);
	}

#endif

	private static class SpliteratorConcatenator KEY_GENERIC implements KEY_SPLITERATOR KEY_GENERIC {
		private static final int EMPTY_CHARACTERISTICS = Spliterator.SIZED | Spliterator.SUBSIZED;
		// Neither SORTED nor DISTINCT "combine". Two combined spliterators with these characteristics may not have it.
		// Example, {1, 2} and {1, 3}, both SORTED and DISTINCT, concat to {1, 2, 1, 3}, which isn't.
		private static final int CHARACTERISTICS_NOT_SUPPORTED_WHILE_MULTIPLE = Spliterator.SORTED | Spliterator.DISTINCT;
			
		final KEY_SPLITERATOR KEY_EXTENDS_GENERIC a[];
		// Unlike the other classes in this file, length represents remaining, NOT the high mark for offset.
		int offset, length;
		/** The sum of estimatedRemaining <em>except</em> current offset */
		long remainingEstimatedExceptCurrent = Long.MAX_VALUE;
		int characteristics = 0;

		public SpliteratorConcatenator(final KEY_SPLITERATOR KEY_EXTENDS_GENERIC a[], int offset, int length) {
			this.a = a;
			this.offset = offset;
			this.length = length;
			this.remainingEstimatedExceptCurrent = recomputeRemaining();
			this.characteristics = computeCharacteristics();
		}

		private long recomputeRemaining() {
			int curLength = length - 1;
			int curOffset = offset + 1;
			long result = 0;
			while (curLength > 0) {
				long cur = a[curOffset++].estimateSize();
				--curLength;
				if (cur == Long.MAX_VALUE) return Long.MAX_VALUE;
				result += cur;
				// Hit max or overflow
				if (result == Long.MAX_VALUE || result < 0) {
					return Long.MAX_VALUE;
				}
			}
			return result;
		}

		/** Compute the intersection of all contained spliterators' characteristics. */
		private int computeCharacteristics() {
			if (length <= 0) {
				return EMPTY_CHARACTERISTICS;
			}
			int current = ~0;
			int curLength = length;
			int curOffset = offset;
			if (curLength > 1) {
				current &= ~CHARACTERISTICS_NOT_SUPPORTED_WHILE_MULTIPLE; 
			}
			while (curLength > 0) {
				current &= a[curOffset++].characteristics();
				--curLength;
			}
			return current;
		}

		private void advanceNextSpliterator() {
			if (length <= 0) {
				throw new AssertionError("advanceNextSpliterator() called with none remaining");
			}
			++offset;
			--length;
			this.remainingEstimatedExceptCurrent = recomputeRemaining();
			// We do NOT recompute the union of all characteristics here.
			// Per spec, the only time characteristics() can change its
			// return value on an instance is after a call to trySplt().
		}

		@Override
		public boolean tryAdvance(final METHOD_ARG_KEY_CONSUMER action) {
			boolean any = false;

			while(length > 0) {
				if (a[offset].tryAdvance(action)) {
					any = true;
					break;
				}
				advanceNextSpliterator();
			}
			return any;
		}

#if KEYS_PRIMITIVE
		@Override
		public void forEachRemaining(final METHOD_ARG_KEY_CONSUMER action) {
			while (length > 0) {
				a[offset].forEachRemaining(action);
				advanceNextSpliterator();
			}
		}
#endif


		DEPRECATED_IF_KEYS_PRIMITIVE
		@Override
		public void forEachRemaining(final Consumer<? super KEY_GENERIC_CLASS> action) {
			while (length > 0) {
				a[offset].forEachRemaining(action);
				advanceNextSpliterator();
			}
		}

		@Override
		public long estimateSize() {
			if (length <= 0) return 0;
			long est = a[offset].estimateSize() + remainingEstimatedExceptCurrent;
			if (est < 0) {
				// Overflow
				return Long.MAX_VALUE;
			}
			return est;
		}

		@Override
		public int characteristics() {
			return characteristics;
		}

		SUPPRESS_WARNINGS_KEY_UNCHECKED
		@Override
		public KEY_COMPARATOR KEY_SUPER_GENERIC getComparator() {
			if (length == 1 && ((characteristics & Spliterator.SORTED) != 0) ) {
#if KEYS_REFERENCE
				return (KEY_COMPARATOR KEY_SUPER_GENERIC) a[offset].getComparator();
#else
				return a[offset].getComparator();
#endif
			}
			throw new IllegalStateException();
		}

		@Override
		public KEY_SPLITERATOR KEY_GENERIC trySplit() {
			/* First we split on the spliterators array, with new concating spliterators for those array slices.
			 * Then if we can't split anymore due to only 1 spliterator we are "concating", return the splits
			 * of that single spliterator.
			 */
			switch(length) {
				case 0: return null;
				case 1: {
					// We are on the last spliterator. So now we ask it to split.
#if KEYS_REFERENCE
					SUPPRESS_WARNINGS_KEY_UNCHECKED // This is safe because spliterators only "give" K, never "take" them.
					KEY_SPLITERATOR KEY_GENERIC split = (KEY_SPLITERATOR KEY_GENERIC)a[offset].trySplit();
#else
					KEY_SPLITERATOR split = a[offset].trySplit();
#endif
					// It is possible for a Spliterator to change characteristics after a split.
					// e.g. a SIZED but not SUBSIZED spliterator may split into non-SIZED spliterators.
					this.characteristics = a[offset].characteristics();
					return split;
				}
				case 2: {
					// Per spec, this instance becomes suffix, and we return prefix.
					// Fetch first to return
#if KEYS_REFERENCE
					SUPPRESS_WARNINGS_KEY_UNCHECKED // This is safe because spliterators only "give" K, never "take" them.
					KEY_SPLITERATOR KEY_GENERIC split = (KEY_SPLITERATOR KEY_GENERIC)a[offset++];
#else
					KEY_SPLITERATOR KEY_GENERIC split = a[offset++];
#endif
					--length;
					// assert length == 1;
					// We become the second
					this.characteristics = a[offset].characteristics();
					this.remainingEstimatedExceptCurrent = 0;
					return split;
				}
				default:  // Fallthrough to below
			}
			int mid = length >> 1;
			int ret_offset = offset;
			int new_offset = offset + mid;
			int ret_length = mid;
			int new_length = length - mid;
			this.offset = new_offset;
			this.length = new_length;
			this.remainingEstimatedExceptCurrent = recomputeRemaining();
			this.characteristics = computeCharacteristics();
			return new SpliteratorConcatenator KEY_GENERIC_DIAMOND(a, ret_offset, ret_length);
		}

		@Override
		public long skip(long n) {
			long skipped = 0;
			if (length <= 0) return 0;

			while(skipped < n && length >= 0) {
				long curSkipped = a[offset].skip(n - skipped);
				skipped += curSkipped;
				// This relies on the sub spliterators implementing skip() correctly
				// and always skipping as much as possible first call, so the next
				// call to skip() will always return 0.
				// If this assumption does not hold, change the condition to curSkipped == 0.
				// That will make it work correctly in the face of non-conforming implementations,
				// at the cost of doing at least 2 passes through this loop for each spliterator.
				if (skipped < n) advanceNextSpliterator();
			}

			return skipped;
		}
	}


	/** Concatenates all spliterators contained in an array.
	 *
	 * <p>This method returns a spliterator that will enumerate in order the elements returned
	 * by all spliterators contained in the given array.
	 *
	 * <p> Note: Due to there being no way to ensure the {@link Comparator} is consistent
	 * between each inner spliterator, the returned spliterator's {@link Spliterator#getComparator()}
	 * will always throw {@link IllegalStateException}, even when if the current or even all
	 * the inner spliterators are {@linkplain Spliterator#SORTED sorted}.
	 *
	 * @param a an array of spliterators.
	 * @return a spliterator obtained by concatenation.
	 */
#if KEYS_REFERENCE
	@SafeVarargs  // Spliterators can only give K, never consume them, making this safe.
#endif
	public static KEY_GENERIC KEY_SPLITERATOR KEY_GENERIC concat(final KEY_SPLITERATOR KEY_EXTENDS_GENERIC... a) {
		return concat(a, 0, a.length);
	}


	/** Concatenates a sequence of spliterators contained in an array.
	 *
	 * <p>This method returns a spliterator that will enumerate in order the elements returned
	 * by {@code a[offset]}, then those returned
	 * by {@code a[offset + 1]}, and so on up to
	 * {@code a[offset + length - 1]}.
	 *
	 * <p> Note: Due to there being no way to ensure the {@link Comparator} is consistent
	 * between each inner spliterator, the returned spliterator's {@link Spliterator#getComparator()}
	 * will always throw {@link IllegalStateException}, even when if the current or even all
	 * the inner spliterators are {@linkplain Spliterator#SORTED sorted}.
	 *
	 * @param a an array of spliterators.
	 * @param offset the index of the first spliterator to concatenate.
	 * @param length the number of spliterators to concatenate.
	 * @return a spliterator obtained by concatenation of {@code length} elements of {@code a} starting at {@code offset}.
	 */

	public static KEY_GENERIC KEY_SPLITERATOR KEY_GENERIC concat(final KEY_SPLITERATOR KEY_EXTENDS_GENERIC a[], final int offset, final int length) {
		return new SpliteratorConcatenator KEY_GENERIC_DIAMOND(a, offset, length);
	}

	private static class SpliteratorFromIterator KEY_GENERIC implements KEY_SPLITERATOR KEY_GENERIC {
		// TODO Expose this arithmetically incrementing split size logic as an abstract class.
		// Like java.util.Spliterators.AbstractSpliterator?
		private static final int BATCH_INCREMENT_SIZE = 1024;
		private static final int BATCH_MAX_SIZE = 1 << 25;

		private final KEY_ITERATOR KEY_EXTENDS_GENERIC iter;
		final int characteristics;
		private final boolean knownSize;
		/** If {@code knownSize}, then has the remaining size left.
		 * Otherwise the value of this variable has no meaning.
		 */
		private long size = Long.MAX_VALUE;
		private int nextBatchSize = BATCH_INCREMENT_SIZE;

		/** Used to "finish off" elements once we hit the end while splitting. */
		private KEY_SPLITERATOR KEY_GENERIC delegate = null;

		SpliteratorFromIterator(final KEY_ITERATOR KEY_EXTENDS_GENERIC iter, int characteristics) {
			this.iter = iter;
			this.characteristics = BASE_SPLITERATOR_CHARACTERISTICS | characteristics;
			knownSize = false;
		}

		SpliteratorFromIterator(final KEY_ITERATOR KEY_EXTENDS_GENERIC iter, long size, int additionalCharacteristics) {
			this.iter = iter;
			knownSize = true;
			this.size = size;
			if ((additionalCharacteristics & Spliterator.CONCURRENT) != 0) {
				this.characteristics = BASE_SPLITERATOR_CHARACTERISTICS | additionalCharacteristics;
			} else {
				this.characteristics = Spliterator.SIZED | Spliterator.SUBSIZED | BASE_SPLITERATOR_CHARACTERISTICS | additionalCharacteristics;
			}
		}

		@Override
		public boolean tryAdvance(final METHOD_ARG_KEY_CONSUMER action) {
			if (delegate != null){
				boolean hadRemaining = delegate.tryAdvance(action);
				if (!hadRemaining) delegate = null;
				return hadRemaining;
			}
			if (!iter.hasNext()) return false;
			--size;
			action.accept(iter.NEXT_KEY());
			return true;
		}

		@Override
		public void forEachRemaining(final METHOD_ARG_KEY_CONSUMER action) {
			if (delegate != null) {
				delegate.forEachRemaining(action);
				delegate = null;
			}
			iter.forEachRemaining(action);
			size = 0;
		}

		@Override
		public long estimateSize() {
			if (delegate != null) return delegate.estimateSize();
			if (!iter.hasNext()) return 0;
			// Size can be less then or equal to zero yet still have next if the iterator
			// was concurrently modified, in which case we don't know anymore.
			return knownSize && size >= 0 ? size : Long.MAX_VALUE;
		}

		@Override
		public int characteristics() {
			return characteristics;
		}

		protected KEY_SPLITERATOR KEY_GENERIC makeForSplit(KEY_GENERIC_TYPE[] batch, int len) {
			return wrap(batch, 0, len, characteristics);
		}

		@Override
		public KEY_SPLITERATOR KEY_GENERIC trySplit() {
			if (!iter.hasNext()) return null;
			int batchSizeEst = knownSize && size > 0 ? (int)Math.min(nextBatchSize, size) : nextBatchSize;
			SUPPRESS_WARNINGS_KEY_UNCHECKED
			KEY_GENERIC_TYPE[] batch = KEY_GENERIC_ARRAY_CAST new KEY_TYPE[batchSizeEst];
			int actualSeen = 0;
			while (actualSeen < batchSizeEst && iter.hasNext()) {
				batch[actualSeen++] = iter.NEXT_KEY();
				--size;
			}
			// Check if the local size variable fell behind the backing source, and if so, fill up remaining of batch
			if (batchSizeEst < nextBatchSize && iter.hasNext()) {
				batch = java.util.Arrays.copyOf(batch, nextBatchSize);
				while (iter.hasNext() && actualSeen < nextBatchSize) {
					batch[actualSeen++] = iter.NEXT_KEY();
					--size;
				}
			}
			nextBatchSize = Math.min(BATCH_MAX_SIZE, nextBatchSize + BATCH_INCREMENT_SIZE);

			// If we have none remaining, then set our delegate to "finish off" the batch we just made.
			KEY_SPLITERATOR KEY_GENERIC split = makeForSplit(batch, actualSeen);
			if (!iter.hasNext()) {
				delegate = split;
				return split.trySplit();
			} else {
				return split;
			}
		}

		@Override
		public long skip(long n) {
			if (n < 0) throw new IllegalArgumentException("Argument must be nonnegative: " + n);
			if (iter instanceof KEY_BIG_LIST_ITERATOR) {
				long skipped = ((KEY_BIG_LIST_ITERATOR KEY_EXTENDS_GENERIC)iter).skip(n);
				size -= skipped;
				return skipped;
			} else {
				long skippedSoFar = 0;
				while (skippedSoFar < n && iter.hasNext()) {
					int skipped = iter.skip(SafeMath.safeLongToInt(Math.min(n, Integer.MAX_VALUE)));
					size -= skipped;
					skippedSoFar += skipped;
				}
				return skippedSoFar;
			}
		}
	}

	private static class SpliteratorFromIteratorWithComparator KEY_GENERIC extends SpliteratorFromIterator KEY_GENERIC {

		private final KEY_COMPARATOR KEY_SUPER_GENERIC comparator;

		SpliteratorFromIteratorWithComparator(final KEY_ITERATOR KEY_EXTENDS_GENERIC iter, int additionalCharacteristics, final KEY_COMPARATOR KEY_SUPER_GENERIC comparator) {
			super(iter, additionalCharacteristics | SORTED_CHARACTERISTICS);
			this.comparator = comparator;
		}

		SpliteratorFromIteratorWithComparator(final KEY_ITERATOR KEY_EXTENDS_GENERIC iter, long size, int additionalCharacteristics, final KEY_COMPARATOR KEY_SUPER_GENERIC comparator) {
			super(iter, size, additionalCharacteristics | SORTED_CHARACTERISTICS);
			this.comparator = comparator;
		}

		@Override
		public KEY_COMPARATOR KEY_SUPER_GENERIC getComparator() {
			return comparator;
		}

		@Override
		protected KEY_SPLITERATOR KEY_GENERIC makeForSplit(KEY_GENERIC_TYPE[] array, int len) {
			return wrapPreSorted(array, 0, len, characteristics, comparator);
		}
	}

	/** Wrap a type-specific {@link java.util.Iterator} of a known size as a type-specific {@link java.util.Spliterator}
	 *
	 * <p>The returned spliterator will report
	 * {@link Spliterator#characteristics() characteristics} {@code additionalCharacterisitcs},
	 * and for primitive types, {@link Spliterator#NONNULL}.
	 * It will also report {@link Spliterator#SIZED}, {@link Spliterator#SUBSIZED},
	 * unless {@link Spliterator#CONCURRENT} is to be reported, in which case these two
	 * are not implicitly reported.
	 *
	 * <p>Because {@link java.util.Iterator} is an inherently linear API, the returned spliterator will
	 * yield limited performance gains when run in parallel contexts, as the returned spliterator's
	 * {@link Spliterator#trySplit()} will have linear runtime.
	 *
	 * @param iter the type-specific {@code Iterator} to wrap
	 * @param size the number of elements the iterator will return
	 * @param additionalCharacterisitcs any additional characteristics to report
	 * @return a type-specific {@code Spliterator} that will give the same elements the iterator will return.
	 * @see java.util.Spliterators#spliterator(java.util.Iterator, long, int)
	 */
	public static KEY_GENERIC KEY_SPLITERATOR KEY_GENERIC asSpliterator(final KEY_ITERATOR KEY_EXTENDS_GENERIC iter, final long size, final int additionalCharacterisitcs) {
		return new SpliteratorFromIterator KEY_GENERIC_DIAMOND(iter, size, additionalCharacterisitcs);
	}

	/** Wrap a type-specific, sorted {@link java.util.Iterator} of a known size as a type-specific {@link java.util.Spliterator}
	 *
	 * <p>It is the caller's responsibility to ensure the iterator's order
	 * is actually sorted according to the comparator given.
	 *
	 * <p>The returned spliterator will report
	 * {@link Spliterator#characteristics() characteristics} {@code additionalCharacterisitcs},
	 * {@link Spliterator#ORDERED}, {@link Spliterator#SORTED}, and for primitive types,
	 * {@link Spliterator#NONNULL}.
	 * It will also report {@link Spliterator#SIZED}, {@link Spliterator#SUBSIZED},
	 * unless {@link Spliterator#CONCURRENT} is to be reported, in which case these two
	 * are not implicitly reported.
	 *
	 * <p>Because {@link java.util.Iterator} is an inherently linear API, the returned spliterator will
	 * yield limited performance gains when run in parallel contexts, as the returned spliterator's
	 * {@link Spliterator#trySplit()} will have linear runtime.
	 *
	 * @param iter the type-specific {@code Iterator} to wrap
	 * @param size the number of elements the iterator will return
	 * @param additionalCharacterisitcs any additional characteristics to report
	 * @param comparator the comparator the iterator is ordered on (or {@code null} for natural ordering)
	 * @return a type-specific {@code Spliterator} that will give the same elements the iterator will return.
	 */
	public static KEY_GENERIC KEY_SPLITERATOR KEY_GENERIC asSpliteratorFromSorted(
			final KEY_ITERATOR KEY_EXTENDS_GENERIC iter, final long size, final int additionalCharacterisitcs, final KEY_COMPARATOR KEY_SUPER_GENERIC comparator) {
		return new SpliteratorFromIteratorWithComparator KEY_GENERIC_DIAMOND(iter, size, additionalCharacterisitcs, comparator);
	}

	/** Wrap a type-specific {@link java.util.Iterator} of an unknown size as a type-specific {@link java.util.Spliterator}
	 *
	 * <p>The returned spliterator will report {@code additionalCharacterisitcs},
	 * and for primitive types, {@link Spliterator#NONNULL}.
	 *
	 * <p>Because {@link java.util.Iterator} is an inherently linear API, the returned spliterator will
	 * yield limited performance gains when run in parallel contexts, as the returned spliterator's
	 * {@link Spliterator#trySplit()} will have linear runtime.
	 *
	 * @param iter the type-specific {@code Iterator} to wrap
	 * @param characterisitcs the characteristics to report
	 * @return a type-specific {@code Spliterator} that will give the same elements the iterator will return.
	 * @see java.util.Spliterators#spliteratorUnknownSize(java.util.Iterator, int)
	 */
	public static KEY_GENERIC KEY_SPLITERATOR KEY_GENERIC asSpliteratorUnknownSize(final KEY_ITERATOR KEY_EXTENDS_GENERIC iter, final int characterisitcs) {
		return new SpliteratorFromIterator KEY_GENERIC_DIAMOND(iter, characterisitcs);
	}

	/** Wrap a type-specific, sorted {@link java.util.Iterator} of an unknown size as a type-specific {@link java.util.Spliterator}
	 *
	 * <p>It is the caller's responsibility to ensure the iterator's order
	 * is actually sorted according to the comparator given.
	 *
	 * <p>The returned spliterator will report
	 * {@link Spliterator#characteristics() characteristics} {@code additionalCharacterisitcs},
	 * {@link Spliterator#ORDERED}, {@link Spliterator#SORTED}, and for primitive types,
	 * {@link Spliterator#NONNULL}.
	 *
	 * <p>Because {@link java.util.Iterator} is an inherently linear API, the returned spliterator will
	 * yield limited performance gains when run in parallel contexts, as the returned spliterator's
	 * {@link Spliterator#trySplit()} will have linear runtime.
	 *
	 * @param iter the type-specific {@code Iterator} to wrap
	 * @param additionalCharacterisitcs the characteristics to report
	 * @param comparator the comparator the iterator is ordered on (or {@code null} for natural ordering)
	 * @return a type-specific {@code Spliterator} that will give the same elements the iterator will return.
	 */
	public static KEY_GENERIC KEY_SPLITERATOR KEY_GENERIC asSpliteratorFromSortedUnknownSize(final KEY_ITERATOR KEY_EXTENDS_GENERIC iter, final int additionalCharacterisitcs, final KEY_COMPARATOR KEY_SUPER_GENERIC comparator) {
		return new SpliteratorFromIteratorWithComparator KEY_GENERIC_DIAMOND(iter, additionalCharacterisitcs, comparator);
	}

	private static final class IteratorFromSpliterator KEY_GENERIC implements KEY_ITERATOR KEY_GENERIC, KEY_CONSUMER KEY_GENERIC {
		private final KEY_SPLITERATOR KEY_EXTENDS_GENERIC spliterator;
		private KEY_GENERIC_TYPE holder = KEY_NULL;
		/** Whether we have an element "peeked" from a hasNext that we have yet to return */
		private boolean hasPeeked = false;

		IteratorFromSpliterator(final KEY_SPLITERATOR KEY_EXTENDS_GENERIC spliterator) {
			this.spliterator = spliterator;
		}

		@Override
		public void accept(final KEY_GENERIC_TYPE item) {
			holder = item;
		}

		@Override
		public boolean hasNext() {
			if (hasPeeked) return true;
			boolean hadElement = spliterator.tryAdvance(this);
			if (!hadElement) return false;
			hasPeeked = true;
			return true;
		}

		@Override
		public KEY_GENERIC_TYPE NEXT_KEY() {
			if (hasPeeked) {
				hasPeeked = false;
				return holder;
			}
			boolean hadElement = spliterator.tryAdvance(this);
			if (!hadElement) throw new java.util.NoSuchElementException();
			return holder;
		}

		@Override
		public void forEachRemaining(final METHOD_ARG_KEY_CONSUMER action) {
			if (hasPeeked) {
				hasPeeked = false;
				action.accept(holder);
			}
			spliterator.forEachRemaining(action);
		}

		@Override
		public int skip(int n) {
			if (n < 0) throw new IllegalArgumentException("Argument must be nonnegative: " + n);
			int skipped = 0;
			if (hasPeeked) {
				hasPeeked = false;
				spliterator.skip(1);
				++skipped;
				--n;
			}
			if (n > 0) {
				skipped += SafeMath.safeLongToInt(spliterator.skip(n));
			}
			return skipped;
		}
	}

	/** Wrap a type-specific {@link java.util.Spliterator} as a type-specific {@link java.util.Iterator}
	 *
	 * @param spliterator the type-specific {@code Spliterator} to wrap
	 * @return a type-specific {@code Iterator} that will return the same elements the spliterator will give.
	 * @see java.util.Spliterators#iterator(java.util.Spliterator)
	 */
	public static KEY_GENERIC KEY_ITERATOR KEY_GENERIC asIterator(final KEY_SPLITERATOR KEY_EXTENDS_GENERIC spliterator) {
		return new IteratorFromSpliterator KEY_GENERIC_DIAMOND(spliterator);
	}

#if KEY_CLASS_Short || KEY_CLASS_Integer || KEY_CLASS_Long || KEY_CLASS_Float || KEY_CLASS_Double

	/** A wrapper promoting the results of a ByteSpliterator. */

	private static final class ByteSpliteratorWrapper implements KEY_SPLITERATOR {
		final it.unimi.dsi.fastutil.bytes.ByteSpliterator spliterator;

		public ByteSpliteratorWrapper(final it.unimi.dsi.fastutil.bytes.ByteSpliterator spliterator) {
			this.spliterator = spliterator;
		}

		@Override
		public boolean tryAdvance(final METHOD_ARG_KEY_CONSUMER action) {
			Objects.requireNonNull(action);
			return spliterator.tryAdvance(action::accept);
		}

		@Override
		public void forEachRemaining(final METHOD_ARG_KEY_CONSUMER action) {
			Objects.requireNonNull(action);
			spliterator.forEachRemaining(action::accept);
		}

		@Override
		public long estimateSize() { return spliterator.estimateSize(); }

		@Override
		public int characteristics() { return spliterator.characteristics(); }

		@Override
		public long skip(long n) { return spliterator.skip(n); }

		@Override
		public KEY_SPLITERATOR KEY_GENERIC trySplit() {
			it.unimi.dsi.fastutil.bytes.ByteSpliterator possibleSplit = spliterator.trySplit();
			if (possibleSplit == null) return null;
			return new ByteSpliteratorWrapper(possibleSplit);
		}
	}

	/** Returns a spliterator backed by the specified byte spliterator.
	 *
	 * <p>Note: Due to the incompatibility of primitive {@link Comparator} types,
	 * the returned spliterator's {@link Spliterator#getComparator()} will always
	 * throw {@link IllegalStateException}, even when the underlying spliterator is
	 * {@linkplain Spliterator#SORTED sorted}.
	 *
	 * @param spliterator a byte spliterator.
	 * @return a spliterator backed by the specified byte spliterator.
	 */
	public static KEY_SPLITERATOR wrap(final it.unimi.dsi.fastutil.bytes.ByteSpliterator spliterator) {
		return new ByteSpliteratorWrapper(spliterator);
	}
#endif

#if KEY_CLASS_Integer || KEY_CLASS_Long || KEY_CLASS_Float || KEY_CLASS_Double

	/** A wrapper promoting the results of a ShortSpliterator. */

	private static final class ShortSpliteratorWrapper implements KEY_SPLITERATOR {
		final it.unimi.dsi.fastutil.shorts.ShortSpliterator spliterator;

		public ShortSpliteratorWrapper(final it.unimi.dsi.fastutil.shorts.ShortSpliterator spliterator) {
			this.spliterator = spliterator;
		}

		@Override
		public boolean tryAdvance(final METHOD_ARG_KEY_CONSUMER action) {
			Objects.requireNonNull(action);
			return spliterator.tryAdvance(action::accept);
		}

		@Override
		public void forEachRemaining(final METHOD_ARG_KEY_CONSUMER action) {
			Objects.requireNonNull(action);
			spliterator.forEachRemaining(action::accept);
		}

		@Override
		public long estimateSize() { return spliterator.estimateSize(); }

		@Override
		public int characteristics() { return spliterator.characteristics(); }

		@Override
		public long skip(long n) { return spliterator.skip(n); }

		@Override
		public KEY_SPLITERATOR trySplit() {
			it.unimi.dsi.fastutil.shorts.ShortSpliterator possibleSplit = spliterator.trySplit();
			if (possibleSplit == null) return null;
			return new ShortSpliteratorWrapper(possibleSplit);
		}
	}

	/** Returns a spliterator backed by the specified short spliterator.
	 *
	 * <p>Note: Due to the incompatibility of primitive {@link Comparator} types,
	 * the returned spliterator's {@link Spliterator#getComparator()} will always
	 * throw {@link IllegalStateException}, even when the underlying spliterator is
	 * {@linkplain Spliterator#SORTED sorted}.
	 *
	 * @param spliterator a short spliterator.
	 * @return a spliterator backed by the specified short spliterator.
	 */
	public static KEY_SPLITERATOR wrap(final it.unimi.dsi.fastutil.shorts.ShortSpliterator spliterator) {
		return new ShortSpliteratorWrapper(spliterator);
	}

#endif

#if KEY_CLASS_Integer || KEY_CLASS_Long || KEY_CLASS_Float || KEY_CLASS_Double

	/** A wrapper promoting the results of a CharSpliterator. */

	private static final class CharSpliteratorWrapper implements KEY_SPLITERATOR {
		final it.unimi.dsi.fastutil.chars.CharSpliterator spliterator;

		public CharSpliteratorWrapper(final it.unimi.dsi.fastutil.chars.CharSpliterator spliterator) {
			this.spliterator = spliterator;
		}

		@Override
		public boolean tryAdvance(final METHOD_ARG_KEY_CONSUMER action) {
			Objects.requireNonNull(action);
			return spliterator.tryAdvance(action::accept);
		}

		@Override
		public void forEachRemaining(final METHOD_ARG_KEY_CONSUMER action) {
			Objects.requireNonNull(action);
			spliterator.forEachRemaining(action::accept);
		}

		@Override
		public long estimateSize() { return spliterator.estimateSize(); }

		@Override
		public int characteristics() { return spliterator.characteristics(); }

		@Override
		public long skip(long n) { return spliterator.skip(n); }

		@Override
		public KEY_SPLITERATOR trySplit() {
			it.unimi.dsi.fastutil.chars.CharSpliterator possibleSplit = spliterator.trySplit();
			if (possibleSplit == null) return null;
			return new CharSpliteratorWrapper(possibleSplit);
		}
	}

	/** Returns a spliterator backed by the specified char spliterator.
	 *
	 * <p><b>WARNING</b>: This is <em>not</em> the same as converting the source to a sequence
	 * of code points. This returned instance literally performs {@code (int)(charValue)} casts.
	 * Surrogate pairs will be left as separate elements instead of combined into a single element
	 * with the code point it represents.
	 *
	 * <p>Note: Due to the incompatibility of primitive {@link Comparator} types,
	 * the returned spliterator's {@link Spliterator#getComparator()} will always
	 * throw {@link IllegalStateException}, even when the underlying spliterator is
	 * {@linkplain Spliterator#SORTED sorted}.
	 *
	 * @param spliterator a char spliterator.
	 * @return a spliterator backed by the specified char spliterator.
	 */
	public static KEY_SPLITERATOR wrap(final it.unimi.dsi.fastutil.chars.CharSpliterator spliterator) {
		return new CharSpliteratorWrapper(spliterator);
	}

#endif

#if KEY_CLASS_Long || KEY_CLASS_Double

	/** A wrapper promoting the results of an IntSpliterator. */

	private static final class IntSpliteratorWrapper implements KEY_SPLITERATOR {
		final it.unimi.dsi.fastutil.ints.IntSpliterator spliterator;

		public IntSpliteratorWrapper(final it.unimi.dsi.fastutil.ints.IntSpliterator spliterator) {
			this.spliterator = spliterator;
		}

		@Override
		public boolean tryAdvance(final METHOD_ARG_KEY_CONSUMER action) {
			Objects.requireNonNull(action);
			return spliterator.tryAdvance(action::accept);
		}

		@Override
		public void forEachRemaining(final METHOD_ARG_KEY_CONSUMER action) {
			Objects.requireNonNull(action);
			spliterator.forEachRemaining(action::accept);
		}

		@Override
		public long estimateSize() { return spliterator.estimateSize(); }

		@Override
		public int characteristics() { return spliterator.characteristics(); }

		@Override
		public long skip(long n) { return spliterator.skip(n); }

		@Override
		public KEY_SPLITERATOR trySplit() {
			it.unimi.dsi.fastutil.ints.IntSpliterator possibleSplit = spliterator.trySplit();
			if (possibleSplit == null) return null;
			return new IntSpliteratorWrapper(possibleSplit);
		}
	}

	/** Returns a spliterator backed by the specified integer spliterator.
	 *
	 * <p>Note: Due to the incompatibility of primitive {@link Comparator} types,
	 * the returned spliterator's {@link Spliterator#getComparator()} will always
	 * throw {@link IllegalStateException}, even when the underlying spliterator is
	 * {@linkplain Spliterator#SORTED sorted}.
	 *
	 * @param spliterator an integer spliterator.
	 * @return a spliterator backed by the specified integer spliterator.
	 */

	public static KEY_SPLITERATOR wrap(final it.unimi.dsi.fastutil.ints.IntSpliterator spliterator) {
		return new IntSpliteratorWrapper(spliterator);
	}

#endif

#if KEY_CLASS_Double

	/** A wrapper promoting the results of a FloatSpliterator. */

	private static final class FloatSpliteratorWrapper implements KEY_SPLITERATOR {
		final it.unimi.dsi.fastutil.floats.FloatSpliterator spliterator;

		public FloatSpliteratorWrapper(final it.unimi.dsi.fastutil.floats.FloatSpliterator spliterator) {
			this.spliterator = spliterator;
		}

		@Override
		public boolean tryAdvance(final METHOD_ARG_KEY_CONSUMER action) {
			Objects.requireNonNull(action);
			return spliterator.tryAdvance(action::accept);
		}

		@Override
		public void forEachRemaining(final METHOD_ARG_KEY_CONSUMER action) {
			Objects.requireNonNull(action);
			spliterator.forEachRemaining(action::accept);
		}

		@Override
		public long estimateSize() { return spliterator.estimateSize(); }

		@Override
		public int characteristics() { return spliterator.characteristics(); }

		@Override
		public long skip(long n) { return spliterator.skip(n); }

		@Override
		public KEY_SPLITERATOR trySplit() {
			it.unimi.dsi.fastutil.floats.FloatSpliterator possibleSplit = spliterator.trySplit();
			if (possibleSplit == null) return null;
			return new FloatSpliteratorWrapper(possibleSplit);
		}
	}

	/** Returns a spliterator backed by the specified float spliterator.
	 *
	 * <p>Note: Due to the incompatibility of primitive {@link Comparator} types,
	 * the returned spliterator's {@link Spliterator#getComparator()} will always
	 * throw {@link IllegalStateException}, even when the underlying spliterator is
	 * {@linkplain Spliterator#SORTED sorted}.
	 *
	 * @param spliterator a float spliterator.
	 * @return a spliterator backed by the specified float spliterator.
	 */
	public static KEY_SPLITERATOR wrap(final it.unimi.dsi.fastutil.floats.FloatSpliterator spliterator) {
		return new FloatSpliteratorWrapper(spliterator);
	}
#endif
}
