/*
 * Copyright (C) 2004-2017 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.
 */



#if KEY_CLASS_Byte

// HORRIBLE kluges to work around bug #6478546

private static final int MAX_IO_LENGTH = 1024 * 1024;

private static int read(final InputStream is, final byte a[], final int offset, final int length) throws IOException {
	if (length == 0) return 0;

	int read = 0, result;
	do {
		result = is.read(a, offset + read, Math.min(length - read, MAX_IO_LENGTH));
		if (result < 0) return read;
		read += result;
	} while(read < length);

	return read;
}

private static void write(final OutputStream outputStream, final byte a[], final int offset, final int length) throws IOException {
	int written = 0;
	while(written < length) {
		outputStream.write(a, offset + written, Math.min(length - written, MAX_IO_LENGTH));
		written += Math.min(length - written, MAX_IO_LENGTH);
	}
}

private static void write(final DataOutput dataOutput, final byte a[], final int offset, final int length) throws IOException {
	int written = 0;
	while(written < length) {
		dataOutput.write(a, offset + written, Math.min(length - written, MAX_IO_LENGTH));
		written += Math.min(length - written, MAX_IO_LENGTH);
	}
}

// Additional read/write methods to work around the DataInput/DataOutput schizophrenia.

/** Loads bytes from a given input stream, storing them in a given array fragment.
 *
 * <p>Note that this method is going to be significantly faster than {@link #loadBytes(DataInput,byte[],int,int)}
 * as it uses {@link InputStream}'s bulk-read methods.
 *
 * @param inputStream an input stream.
 * @param array an array which will be filled with data from {@code inputStream}.
 * @param offset the index of the first element of {@code array} to be filled.
 * @param length the number of elements of {@code array} to be filled.
 * @return the number of elements actually read from {@code inputStream} (it might be less than {@code length} if {@code inputStream} ends).
 */
public static int LOAD_KEYS(final InputStream inputStream, final KEY_TYPE[] array, final int offset, final int length) throws IOException {
	return read(inputStream, array, offset, length);
}

/** Loads bytes from a given input stream, storing them in a given array.
 *
 * <p>Note that this method is going to be significantly faster than {@link #loadBytes(DataInput,byte[])}
 * as it uses {@link InputStream}'s bulk-read methods.
 *
 * @param inputStream an input stream.
 * @param array an array which will be filled with data from {@code inputStream}.
 * @return the number of elements actually read from {@code inputStream} (it might be less than the array length if {@code inputStream} ends).
 */
public static int LOAD_KEYS(final InputStream inputStream, final KEY_TYPE[] array) throws IOException {
	return read(inputStream, array, 0, array.length);
}

/** Stores an array fragment to a given output stream.
 *
 * <p>Note that this method is going to be significantly faster than {@link #storeBytes(byte[],int,int,DataOutput)}
 * as it uses {@link OutputStream}'s bulk-read methods.
 *
 * @param array an array whose elements will be written to {@code outputStream}.
 * @param offset the index of the first element of {@code array} to be written.
 * @param length the number of elements of {@code array} to be written.
 * @param outputStream an output stream.
 */
public static void STORE_KEYS(final KEY_TYPE array[], final int offset, final int length, final OutputStream outputStream) throws IOException {
	write(outputStream, array, offset, length);
}

/** Stores an array to a given output stream.
 *
 * <p>Note that this method is going to be significantly faster than {@link #storeBytes(byte[],DataOutput)}
 * as it uses {@link OutputStream}'s bulk-read methods.
 *
 * @param array an array whose elements will be written to {@code outputStream}.
 * @param outputStream an output stream.
 */
public static void STORE_KEYS(final KEY_TYPE array[], final OutputStream outputStream) throws IOException {
	write(outputStream, array, 0, array.length);
}


private static long read(final InputStream is, final byte a[][], final long offset, final long length) throws IOException {
	if (length == 0) return 0;

	long read = 0;
	int segment = segment(offset);
	int displacement = displacement(offset);
	int result;
	do {
		result = is.read(a[segment], displacement, (int)Math.min(a[segment].length - displacement, Math.min(length - read, MAX_IO_LENGTH)));
		if (result < 0) return read;
		read += result;
		displacement += result;
		if (displacement == a[segment].length) {
			segment++;
			displacement = 0;
		}
	} while(read < length);

	return read;
}

private static void write(final OutputStream outputStream, final byte a[][], final long offset, final long length) throws IOException {
	if (length == 0) return;
	long written = 0;
	int toWrite;
	int segment = segment(offset);
	int displacement = displacement(offset);
	do {
		toWrite = (int)Math.min(a[segment].length - displacement, Math.min(length - written, MAX_IO_LENGTH));
		outputStream.write(a[segment], displacement, toWrite);
		written += toWrite;
		displacement += toWrite;
		if (displacement == a[segment].length) {
			segment++;
			displacement = 0;
		}
	} while(written < length);
}

private static void write(final DataOutput dataOutput, final byte a[][], final long offset, final long length) throws IOException {
	if (length == 0) return;
	long written = 0;
	int toWrite;
	int segment = segment(offset);
	int displacement = displacement(offset);
	do {
		toWrite = (int)Math.min(a[segment].length - displacement, Math.min(length - written, MAX_IO_LENGTH));
		dataOutput.write(a[segment], displacement, toWrite);
		written += toWrite;
		displacement += toWrite;
		if (displacement == a[segment].length) {
			segment++;
			displacement = 0;
		}
	} while(written < length);
}

// Additional read/write methods to work around the DataInput/DataOutput schizophrenia.


/** Loads bytes from a given input stream, storing them in a given big-array fragment.
 *
 * <p>Note that this method is going to be significantly faster than {@link #loadBytes(DataInput,byte[][],long,long)}
 * as it uses {@link InputStream}'s bulk-read methods.
 *
 * @param inputStream an input stream.
 * @param array a big array which will be filled with data from {@code inputStream}.
 * @param offset the index of the first element of {@code array} to be filled.
 * @param length the number of elements of {@code array} to be filled.
 * @return the number of elements actually read from {@code inputStream} (it might be less than {@code length} if {@code inputStream} ends).
 */
public static long LOAD_KEYS(final InputStream inputStream, final KEY_TYPE[][] array, final long offset, final long length) throws IOException {
	return read(inputStream, array, offset, length);
}

/** Loads bytes from a given input stream, storing them in a given big array.
 *
 * <p>Note that this method is going to be significantly faster than {@link #loadBytes(DataInput,byte[][])}
 * as it uses {@link InputStream}'s bulk-read methods.
 *
 * @param inputStream an input stream.
 * @param array a big array which will be filled with data from {@code inputStream}.
 * @return the number of elements actually read from {@code inputStream} (it might be less than the array length if {@code inputStream} ends).
 */
public static long LOAD_KEYS(final InputStream inputStream, final KEY_TYPE[][] array) throws IOException {
	return read(inputStream, array, 0, BIG_ARRAYS.length(array));
}

/** Stores a big-array fragment to a given output stream.
 *
 * <p>Note that this method is going to be significantly faster than {@link #storeBytes(byte[][],long,long,DataOutput)}
 * as it uses {@link OutputStream}'s bulk-read methods.
 *
 * @param array a big array whose elements will be written to {@code outputStream}.
 * @param offset the index of the first element of {@code array} to be written.
 * @param length the number of elements of {@code array} to be written.
 * @param outputStream an output stream.
 */
public static void STORE_KEYS(final KEY_TYPE array[][], final long offset, final long length, final OutputStream outputStream) throws IOException {
	write(outputStream, array, offset, length);
}

/** Stores a big array to a given output stream.
 *
 * <p>Note that this method is going to be significantly faster than {@link #storeBytes(byte[][],DataOutput)}
 * as it uses {@link OutputStream}'s bulk-read methods.
 *
 * @param array a big array whose elements will be written to {@code outputStream}.
 * @param outputStream an output stream.
 */
public static void STORE_KEYS(final KEY_TYPE array[][], final OutputStream outputStream) throws IOException {
	write(outputStream, array, 0, BIG_ARRAYS.length(array));
}

#endif


/** Loads elements from a given data input, storing them in a given array fragment.
 *
 * @param dataInput a data input.
 * @param array an array which will be filled with data from {@code dataInput}.
 * @param offset the index of the first element of {@code array} to be filled.
 * @param length the number of elements of {@code array} to be filled.
 * @return the number of elements actually read from {@code dataInput} (it might be less than {@code length} if {@code dataInput} ends).
 */
public static int LOAD_KEYS(final DataInput dataInput, final KEY_TYPE[] array, final int offset, final int length) throws IOException {
	PACKAGE.ARRAYS.ensureOffsetLength(array, offset, length);
	int i = 0;
	try {
		for(i = 0; i < length; i++) array[i + offset] = dataInput.READ_KEY();
	}
	catch(EOFException itsOk) {}
	return i;
}

/** Loads elements from a given data input, storing them in a given array.
 *
 * @param dataInput a data input.
 * @param array an array which will be filled with data from {@code dataInput}.
 * @return the number of elements actually read from {@code dataInput} (it might be less than the array length if {@code dataInput} ends).
 */
public static int LOAD_KEYS(final DataInput dataInput, final KEY_TYPE[] array) throws IOException {
	int i = 0;
	try {
		final int length = array.length;
		for(i = 0; i < length; i++) array[i] = dataInput.READ_KEY();
	}
	catch(EOFException itsOk) {}
	return i;
}

/** Loads elements from a file given by a {@link File} object, storing them in a given array fragment.
 *
 * @param file a file.
 * @param array an array which will be filled with data from the specified file.
 * @param offset the index of the first element of {@code array} to be filled.
 * @param length the number of elements of {@code array} to be filled.
 * @return the number of elements actually read from the given file (it might be less than {@code length} if the file is too short).
 */
public static int LOAD_KEYS(final File file, final KEY_TYPE[] array, final int offset, final int length) throws IOException {
	PACKAGE.ARRAYS.ensureOffsetLength(array, offset, length);

	final FileInputStream fis = new FileInputStream(file);
#if KEY_CLASS_Byte
	final int result = read(fis, array, offset, length);
	fis.close();
	return result;
#else
	final DataInputStream dis = new DataInputStream(new FastBufferedInputStream(fis));

	int i = 0;
	try {
		for(i = 0; i < length; i++) array[i + offset] = dis.READ_KEY();
	}
	catch(EOFException itsOk) {}

	dis.close();
	return i;
#endif
}

/** Loads elements from a file given by a pathname, storing them in a given array fragment.
 *
 * @param filename a filename.
 * @param array an array which will be filled with data from the specified file.
 * @param offset the index of the first element of {@code array} to be filled.
 * @param length the number of elements of {@code array} to be filled.
 * @return the number of elements actually read from the given file (it might be less than {@code length} if the file is too short).
 */
public static int LOAD_KEYS(final CharSequence filename, final KEY_TYPE[] array, final int offset, final int length) throws IOException {
	return LOAD_KEYS(new File(filename.toString()), array, offset, length);
}

/** Loads elements from a file given by a {@link File} object, storing them in a given array.
 *
 * @param file a file.
 * @param array an array which will be filled with data from the specified file.
 * @return the number of elements actually read from the given file (it might be less than the array length if the file is too short).
 */
public static int LOAD_KEYS(final File file, final KEY_TYPE[] array) throws IOException {
	final FileInputStream fis = new FileInputStream(file);
#if KEY_CLASS_Byte
	final int result = read(fis, array, 0, array.length);
	fis.close();
	return result;
#else
	final DataInputStream dis = new DataInputStream(new FastBufferedInputStream(fis));

	int i = 0;
	try {
		final int length = array.length;
		for(i = 0; i < length; i++) array[i] = dis.READ_KEY();
	}
	catch(EOFException itsOk) {}

	dis.close();

	return i;
#endif
}

/** Loads elements from a file given by a pathname, storing them in a given array.
 *
 * @param filename a filename.
 * @param array an array which will be filled with data from the specified file.
 * @return the number of elements actually read from the given file (it might be less than the array length if the file is too short).
 */
public static int LOAD_KEYS(final CharSequence filename, final KEY_TYPE[] array) throws IOException {
	return LOAD_KEYS(new File(filename.toString()), array);
}

/** Loads elements from a file given by a {@link File} object, storing them in a new array.
 *
 * <p>Note that the length of the returned array will be computed
 * dividing the specified file size by the number of bytes used to
 * represent each element.
 *
 * @param file a file.
 * @return an array filled with the content of the specified file.
 */
public static KEY_TYPE[] LOAD_KEYS(final File file) throws IOException {
	final FileInputStream fis = new FileInputStream(file);

#if KEY_CLASS_Boolean
	final long length = fis.getChannel().size();
#else
	final long length = fis.getChannel().size() / (KEY_CLASS.SIZE / 8);
#endif

	if (length > Integer.MAX_VALUE) {
		fis.close();
		throw new IllegalArgumentException("File too long: " + fis.getChannel().size()+ " bytes (" + length + " elements)");
	}

	final KEY_TYPE[] array = new KEY_TYPE[(int)length];

#if KEY_CLASS_Byte
	if (read(fis, array, 0, (int)length) < length) throw new EOFException();
	fis.close();
#else
	final DataInputStream dis = new DataInputStream(new FastBufferedInputStream(fis));
	for(int i = 0; i < length; i++) array[i] = dis.READ_KEY();
	dis.close();
#endif
	return array;
}

/** Loads elements from a file given by a filename, storing them in a new array.
 *
 * <p>Note that the length of the returned array will be computed
 * dividing the specified file size by the number of bytes used to
 * represent each element.
 *
 * @param filename a filename.
 * @return an array filled with the content of the specified file.
 */
public static KEY_TYPE[] LOAD_KEYS(final CharSequence filename) throws IOException {
	return LOAD_KEYS(new File(filename.toString()));
}

/** Stores an array fragment to a given data output.
 *
 * @param array an array whose elements will be written to {@code dataOutput}.
 * @param offset the index of the first element of {@code array} to be written.
 * @param length the number of elements of {@code array} to be written.
 * @param dataOutput a data output.
 */
public static void STORE_KEYS(final KEY_TYPE array[], final int offset, final int length, final DataOutput dataOutput) throws IOException {
	PACKAGE.ARRAYS.ensureOffsetLength(array, offset, length);
#if KEY_CLASS_Byte
	write(dataOutput, array, offset, length);
#else
	for(int i = 0; i < length; i++) dataOutput.WRITE_KEY(array[offset + i]);
#endif
}

/** Stores an array to a given data output.
 *
 * @param array an array whose elements will be written to {@code dataOutput}.
 * @param dataOutput a data output.
 */
public static void STORE_KEYS(final KEY_TYPE array[], final DataOutput dataOutput) throws IOException {
#if KEY_CLASS_Byte
	write(dataOutput, array, 0, array.length);
#else
	final int length = array.length;
	for(int i = 0; i < length; i++) dataOutput.WRITE_KEY(array[i]);
#endif
}

/** Stores an array fragment to a file given by a {@link File} object.
 *
 * @param array an array whose elements will be written to {@code filename}.
 * @param offset the index of the first element of {@code array} to be written.
 * @param length the number of elements of {@code array} to be written.
 * @param file a file.
 */
public static void STORE_KEYS(final KEY_TYPE array[], final int offset, final int length, final File file) throws IOException {
	PACKAGE.ARRAYS.ensureOffsetLength(array, offset, length);
#if KEY_CLASS_Byte
	final OutputStream os = new FastBufferedOutputStream(new FileOutputStream(file));
	write(os, array, offset, length);
	os.close();
#else
	final DataOutputStream dos = new DataOutputStream(new FastBufferedOutputStream(new FileOutputStream(file)));
	for(int i = 0; i < length; i++) dos.WRITE_KEY(array[offset + i]);
	dos.close();
#endif
}

/** Stores an array fragment to a file given by a pathname.
 *
 * @param array an array whose elements will be written to {@code filename}.
 * @param offset the index of the first element of {@code array} to be written.
 * @param length the number of elements of {@code array} to be written.
 * @param filename a filename.
 */
public static void STORE_KEYS(final KEY_TYPE array[], final int offset, final int length, final CharSequence filename) throws IOException {
	STORE_KEYS(array, offset, length, new File(filename.toString()));
}

/** Stores an array to a file given by a {@link File} object.
 *
 * @param array an array whose elements will be written to {@code filename}.
 * @param file a file.
 */
public static void STORE_KEYS(final KEY_TYPE array[], final File file) throws IOException {
#if KEY_CLASS_Byte
	final OutputStream os = new FastBufferedOutputStream(new FileOutputStream(file));
	write(os, array, 0, array.length);
	os.close();
#else
	final int length = array.length;
	final DataOutputStream dos = new DataOutputStream(new FastBufferedOutputStream(new FileOutputStream(file)));
	for(int i = 0; i < length; i++) dos.WRITE_KEY(array[i]);
	dos.close();
#endif
}

/** Stores an array to a file given by a pathname.
 *
 * @param array an array whose elements will be written to {@code filename}.
 * @param filename a filename.
 */
public static void STORE_KEYS(final KEY_TYPE array[], final CharSequence filename) throws IOException {
	STORE_KEYS(array, new File(filename.toString()));
}





/** Loads elements from a given data input, storing them in a given big-array fragment.
 *
 * @param dataInput a data input.
 * @param array a big array which will be filled with data from {@code dataInput}.
 * @param offset the index of the first element of {@code bigArray} to be filled.
 * @param length the number of elements of {@code bigArray} to be filled.
 * @return the number of elements actually read from {@code dataInput} (it might be less than {@code length} if {@code dataInput} ends).
 */
public static long LOAD_KEYS(final DataInput dataInput, final KEY_TYPE[][] array, final long offset, final long length) throws IOException {
	PACKAGE.BIG_ARRAYS.ensureOffsetLength(array, offset, length);
	long c = 0;
	try {
		for(int i = segment(offset); i < segment(offset + length + SEGMENT_MASK); i++) {
			final KEY_TYPE[] t = array[i];
			final int l = (int)Math.min(t.length, offset + length - start(i));
			for(int d = (int)Math.max(0, offset - start(i)); d < l; d++) {
				t[d] = dataInput.READ_KEY();
				c++;
			}
		}
	}
	catch(EOFException itsOk) {}
	return c;
}

/** Loads elements from a given data input, storing them in a given big array.
 *
 * @param dataInput a data input.
 * @param array a big array which will be filled with data from {@code dataInput}.
 * @return the number of elements actually read from {@code dataInput} (it might be less than the array length if {@code dataInput} ends).
 */
public static long LOAD_KEYS(final DataInput dataInput, final KEY_TYPE[][] array) throws IOException {
	long c = 0;
	try {
		for(int i = 0; i < array.length; i++) {
			final KEY_TYPE[] t = array[i];
			final int l = t.length;
			for(int d = 0; d < l; d++) {
				t[d] = dataInput.READ_KEY();
				c++;
			}
		}
	}
	catch(EOFException itsOk) {}
	return c;
}

/** Loads elements from a file given by a {@link File} object, storing them in a given big-array fragment.
 *
 * @param file a file.
 * @param array a big array which will be filled with data from the specified file.
 * @param offset the index of the first element of {@code array} to be filled.
 * @param length the number of elements of {@code array} to be filled.
 * @return the number of elements actually read from the given file (it might be less than {@code length} if the file is too short).
 */
public static long LOAD_KEYS(final File file, final KEY_TYPE[][] array, final long offset, final long length) throws IOException {
	PACKAGE.BIG_ARRAYS.ensureOffsetLength(array, offset, length);

	final FileInputStream fis = new FileInputStream(file);
#if KEY_CLASS_Byte
	final long result = read(fis, array, offset, length);
	fis.close();
	return result;
#else
	final DataInputStream dis = new DataInputStream(new FastBufferedInputStream(fis));

	long c = 0;
	try {
		for(int i = segment(offset); i < segment(offset + length + SEGMENT_MASK); i++) {
			final KEY_TYPE[] t = array[i];
			final int l = (int)Math.min(t.length, offset + length - start(i));
			for(int d = (int)Math.max(0, offset - start(i)); d < l; d++) {
				t[d] = dis.READ_KEY();
				c++;
			}
		}
	}
	catch(EOFException itsOk) {}
	dis.close();
	return c;
#endif
}

/** Loads elements from a file given by a pathname, storing them in a given big-array fragment.
 *
 * @param filename a filename.
 * @param array an array which will be filled with data from the specified file.
 * @param offset the index of the first element of {@code array} to be filled.
 * @param length the number of elements of {@code array} to be filled.
 * @return the number of elements actually read from the given file (it might be less than {@code length} if the file is too short).
 */
public static long LOAD_KEYS(final CharSequence filename, final KEY_TYPE[][] array, final long offset, final long length) throws IOException {
	return LOAD_KEYS(new File(filename.toString()), array, offset, length);
}

/** Loads elements from a file given by a {@link File} object, storing them in a given big array.
 *
 * @param file a file.
 * @param array a big array which will be filled with data from the specified file.
 * @return the number of elements actually read from the given file (it might be less than the array length if the file is too short).
 */
public static long LOAD_KEYS(final File file, final KEY_TYPE[][] array) throws IOException {
	final FileInputStream fis = new FileInputStream(file);
#if KEY_CLASS_Byte
	final long result = read(fis, array, 0, BIG_ARRAYS.length(array));
	fis.close();
	return result;
#else
	final DataInputStream dis = new DataInputStream(new FastBufferedInputStream(fis));

	long c = 0;
	try {
		for(int i = 0; i < array.length; i++) {
			final KEY_TYPE[] t = array[i];
			final int l = t.length;
			for(int d = 0; d < l; d++) {
				t[d] = dis.READ_KEY();
				c++;
			}
		}
	}
	catch(EOFException itsOk) {}

	dis.close();

	return c;
#endif
}

/** Loads elements from a file given by a pathname, storing them in a given big array.
 *
 * @param filename a filename.
 * @param array a big array which will be filled with data from the specified file.
 * @return the number of elements actually read from the given file (it might be less than the array length if the file is too short).
 */
public static long LOAD_KEYS(final CharSequence filename, final KEY_TYPE[][] array) throws IOException {
	return LOAD_KEYS(new File(filename.toString()), array);
}

/** Loads elements from a file given by a {@link File} object, storing them in a new big array.
 *
 * <p>Note that the length of the returned big array will be computed
 * dividing the specified file size by the number of bytes used to
 * represent each element.
 *
 * @param file a file.
 * @return a big array filled with the content of the specified file.
 */
public static KEY_TYPE[][] LOAD_KEYS_BIG(final File file) throws IOException {
	final FileInputStream fis = new FileInputStream(file);

#if KEY_CLASS_Boolean
	final long length = fis.getChannel().size();
#else
	final long length = fis.getChannel().size() / (KEY_CLASS.SIZE / 8);
#endif

	final KEY_TYPE[][] array = BIG_ARRAYS.newBigArray(length);

#if KEY_CLASS_Byte
	if (read(fis, array, 0, length) < length) throw new EOFException();
	fis.close();
#else
	final DataInputStream dis = new DataInputStream(new FastBufferedInputStream(fis));

	for(int i = 0; i < array.length; i++) {
		final KEY_TYPE[] t = array[i];
		final int l = t.length;
		for(int d = 0; d < l; d++) t[d] = dis.READ_KEY();
	}

	dis.close();
#endif
	return array;
}

/** Loads elements from a file given by a filename, storing them in a new big array.
 *
 * <p>Note that the length of the returned big array will be computed
 * dividing the specified file size by the number of bytes used to
 * represent each element.
 *
 * @param filename a filename.
 * @return a big array filled with the content of the specified file.
 */
public static KEY_TYPE[][] LOAD_KEYS_BIG(final CharSequence filename) throws IOException {
	return LOAD_KEYS_BIG(new File(filename.toString()));
}

/** Stores an array fragment to a given data output.
 *
 * @param array an array whose elements will be written to {@code dataOutput}.
 * @param offset the index of the first element of {@code array} to be written.
 * @param length the number of elements of {@code array} to be written.
 * @param dataOutput a data output.
 */
public static void STORE_KEYS(final KEY_TYPE array[][], final long offset, final long length, final DataOutput dataOutput) throws IOException {
	PACKAGE.BIG_ARRAYS.ensureOffsetLength(array, offset, length);
#if KEY_CLASS_Byte
	write(dataOutput, array, offset, length);
#else
	for(int i = segment(offset); i < segment(offset + length + SEGMENT_MASK); i++) {
		final KEY_TYPE[] t = array[i];
		final int l = (int)Math.min(t.length, offset + length - start(i));
		for(int d = (int)Math.max(0, offset - start(i)); d < l; d++) dataOutput.WRITE_KEY(t[d]);
	}
#endif
}

/** Stores a big array to a given data output.
 *
 * @param array a big array whose elements will be written to {@code dataOutput}.
 * @param dataOutput a data output.
 */
public static void STORE_KEYS(final KEY_TYPE array[][], final DataOutput dataOutput) throws IOException {
#if KEY_CLASS_Byte
	write(dataOutput, array, 0, BIG_ARRAYS.length(array));
#else
	for(int i = 0; i < array.length; i++) {
		final KEY_TYPE[] t = array[i];
		final int l = t.length;
		for(int d = 0; d < l; d++) dataOutput.WRITE_KEY(t[d]);
	}
#endif
}

/** Stores a big-array fragment to a file given by a {@link File} object.
 *
 * @param array a big array whose elements will be written to {@code filename}.
 * @param offset the index of the first element of {@code array} to be written.
 * @param length the number of elements of {@code array} to be written.
 * @param file a file.
 */
public static void STORE_KEYS(final KEY_TYPE array[][], final long offset, final long length, final File file) throws IOException {
	PACKAGE.BIG_ARRAYS.ensureOffsetLength(array, offset, length);
#if KEY_CLASS_Byte
	final OutputStream os = new FastBufferedOutputStream(new FileOutputStream(file));
	write(os, array, offset, length);
	os.close();
#else
	final DataOutputStream dos = new DataOutputStream(new FastBufferedOutputStream(new FileOutputStream(file)));
	for(int i = segment(offset); i < segment(offset + length + SEGMENT_MASK); i++) {
		final KEY_TYPE[] t = array[i];
		final int l = (int)Math.min(t.length, offset + length - start(i));
		for(int d = (int)Math.max(0, offset - start(i)); d < l; d++) dos.WRITE_KEY(t[d]);
	}
	dos.close();

#endif
}

/** Stores a big-array fragment to a file given by a pathname.
 *
 * @param array a big array whose elements will be written to {@code filename}.
 * @param offset the index of the first element of {@code array} to be written.
 * @param length the number of elements of {@code array} to be written.
 * @param filename a filename.
 */
public static void STORE_KEYS(final KEY_TYPE array[][], final long offset, final long length, final CharSequence filename) throws IOException {
	STORE_KEYS(array, offset, length, new File(filename.toString()));
}

/** Stores an array to a file given by a {@link File} object.
 *
 * @param array an array whose elements will be written to {@code filename}.
 * @param file a file.
 */
public static void STORE_KEYS(final KEY_TYPE array[][], final File file) throws IOException {
#if KEY_CLASS_Byte
	final OutputStream os = new FastBufferedOutputStream(new FileOutputStream(file));
	write(os, array, 0, BIG_ARRAYS.length(array));
	os.close();
#else
	final DataOutputStream dos = new DataOutputStream(new FastBufferedOutputStream(new FileOutputStream(file)));
	for(int i = 0; i < array.length; i++) {
		final KEY_TYPE[] t = array[i];
		final int l = t.length;
		for(int d = 0; d < l; d++) dos.WRITE_KEY(t[d]);
	}
	dos.close();
#endif
}

/** Stores a big array to a file given by a pathname.
 *
 * @param array a big array whose elements will be written to {@code filename}.
 * @param filename a filename.
 */
public static void STORE_KEYS(final KEY_TYPE array[][], final CharSequence filename) throws IOException {
	STORE_KEYS(array, new File(filename.toString()));
}

/** Stores the element returned by an iterator to a given data output.
 *
 * @param i an iterator whose output will be written to {@code dataOutput}.
 * @param dataOutput a filename.
 */
public static void STORE_KEYS(final KEY_ITERATOR i, final DataOutput dataOutput) throws IOException {
	while(i.hasNext()) dataOutput.WRITE_KEY(i.NEXT_KEY());
}

/** Stores the element returned by an iterator to a file given by a {@link File} object.
 *
 * @param i an iterator whose output will be written to {@code filename}.
 * @param file a file.
 */
public static void STORE_KEYS(final KEY_ITERATOR i, final File file) throws IOException {
	final DataOutputStream dos = new DataOutputStream(new FastBufferedOutputStream(new FileOutputStream(file)));
	while(i.hasNext()) dos.WRITE_KEY(i.NEXT_KEY());
	dos.close();
}

/** Stores the element returned by an iterator to a file given by a pathname.
 *
 * @param i an iterator whose output will be written to {@code filename}.
 * @param filename a filename.
 */
public static void STORE_KEYS(final KEY_ITERATOR i, final CharSequence filename) throws IOException {
	STORE_KEYS(i, new File(filename.toString()));
}

/** A wrapper that exhibits the content of a data input stream as a type-specific iterator. */

private static final class KEY_DATA_INPUT_WRAPPER implements KEY_ITERATOR {
	private final DataInput dataInput;
	private boolean toAdvance = true;
	private boolean endOfProcess = false;
	private KEY_TYPE next;

	public KEY_DATA_INPUT_WRAPPER(final DataInput dataInput) {
		this.dataInput = dataInput;
	}

	@Override
	public boolean hasNext() {
		if (! toAdvance) return ! endOfProcess;

		toAdvance = false;

		try { next = dataInput.READ_KEY(); }
		catch(EOFException eof) { endOfProcess = true; }
		catch(IOException rethrow) { throw new RuntimeException(rethrow); }

		return ! endOfProcess;
	}

	@Override
	public KEY_TYPE NEXT_KEY() {
		if (! hasNext()) throw new NoSuchElementException();
		toAdvance = true;
		return next;
	}
}

/** Wraps the given data input stream into an iterator.
 *
 * @param dataInput a data input.
 */
public static KEY_ITERATOR AS_KEY_ITERATOR(final DataInput dataInput) {
	return new KEY_DATA_INPUT_WRAPPER(dataInput);
}

/** Wraps a file given by a {@link File} object into an iterator.
 *
 * @param file a file.
 */
public static KEY_ITERATOR AS_KEY_ITERATOR(final File file) throws IOException {
	return new KEY_DATA_INPUT_WRAPPER(new DataInputStream(new FastBufferedInputStream(new FileInputStream(file))));
}

/** Wraps a file given by a pathname into an iterator.
 *
 * @param filename a filename.
 */
public static KEY_ITERATOR AS_KEY_ITERATOR(final CharSequence filename) throws IOException {
	return AS_KEY_ITERATOR(new File(filename.toString()));
}

/** Wraps a file given by a {@link File} object into an iterable object.
 *
 * @param file a file.
 */
public static KEY_ITERABLE AS_KEY_ITERABLE(final File file) {
	return () -> {
		try { return AS_KEY_ITERATOR(file); }
		catch(IOException e) { throw new RuntimeException(e); }
	};
}

/** Wraps a file given by a pathname into an iterable object.
 *
 * @param filename a filename.
 */
public static KEY_ITERABLE AS_KEY_ITERABLE(final CharSequence filename) {
	return () -> {
		try { return AS_KEY_ITERATOR(filename); }
		catch(IOException e) { throw new RuntimeException(e); }
	};
}

