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



/** Loads elements from a given fast buffered reader, storing them in a given array fragment.
 *
 * @param reader a buffered reader.
 * @param array an array which will be filled with data from {@code reader}.
 * @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 reader} (it might be less than {@code length} if {@code reader} ends).
 */
public static int LOAD_KEYS(final BufferedReader reader, final KEY_TYPE[] array, final int offset, final int length) throws IOException {
	PACKAGE.ARRAYS.ensureOffsetLength(array, offset, length);
	int i = 0;
	String s;
	try {
		for(i = 0; i < length; i++)
			if ((s = reader.readLine()) != null) array[i + offset] = KEY_CLASS.PARSE_KEY(s.trim());
			else break;
	}
	catch(EOFException itsOk) {}
	return i;
}

/** Loads elements from a given buffered reader, storing them in a given array.
 *
 * @param reader a buffered reader.
 * @param array an array which will be filled with data from {@code reader}.
 * @return the number of elements actually read from {@code reader} (it might be less than the array length if {@code reader} ends).
 */
public static int LOAD_KEYS(final BufferedReader reader, final KEY_TYPE[] array) throws IOException {
	return LOAD_KEYS(reader, array, 0, array.length);
}

/** 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 {
	final BufferedReader reader = new BufferedReader(new FileReader(file));
	final int result = LOAD_KEYS(reader, array, offset, length);
	reader.close();

	return result;
}

/** Loads elements from a file given by a filename, 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 {
	return LOAD_KEYS(file, array, 0, array.length);
}

/** Loads elements from a file given by a filename, 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(filename, array, 0, array.length);
}

/** Stores an array fragment to a given print stream.
 *
 * @param array an array whose elements will be written to {@code stream}.
 * @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 stream a print stream.
 */
public static void STORE_KEYS(final KEY_TYPE array[], final int offset, final int length, final PrintStream stream) {
	PACKAGE.ARRAYS.ensureOffsetLength(array, offset, length);
	for(int i = 0; i < length; i++) stream.println(array[offset + i]);
}

/** Stores an array to a given print stream.
 *
 * @param array an array whose elements will be written to {@code stream}.
 * @param stream a print stream.
 */
public static void STORE_KEYS(final KEY_TYPE array[], final PrintStream stream) {
	STORE_KEYS(array, 0, array.length, stream);
}

/** 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 {
	final PrintStream stream = new PrintStream(new FastBufferedOutputStream(new FileOutputStream(file)));
	STORE_KEYS(array, offset, length, stream);
	stream.close();
}

/** 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 {
	STORE_KEYS(array, 0, array.length, file);
}

/** 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, 0, array.length, filename);
}

/** Stores the element returned by an iterator to a given print stream.
 *
 * @param i an iterator whose output will be written to {@code stream}.
 * @param stream a print stream.
 */
public static void STORE_KEYS(final KEY_ITERATOR i, final PrintStream stream) {
	while(i.hasNext()) stream.println(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 PrintStream stream = new PrintStream(new FastBufferedOutputStream(new FileOutputStream(file)));
	STORE_KEYS(i, stream);
	stream.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()));
}


/** Loads elements from a given fast buffered reader, storing them in a given big-array fragment.
 *
 * @param reader a buffered reader.
 * @param array a big array which will be filled with data from {@code reader}.
 * @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 reader} (it might be less than {@code length} if {@code reader} ends).
 */
public static long LOAD_KEYS(final BufferedReader reader, final KEY_TYPE[][] array, final long offset, final long length) throws IOException {
	ensureOffsetLength(array, offset, length);
	long c = 0;
	String s;
	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++) {
				if ((s = reader.readLine()) != null) t[d] = KEY_CLASS.PARSE_KEY(s.trim());
				else return c;
				c++;
			}
		}
	}
	catch(EOFException itsOk) {}
	return c;
}

/** Loads elements from a given buffered reader, storing them in a given array.
 *
 * @param reader a buffered reader.
 * @param array a big array which will be filled with data from {@code reader}.
 * @return the number of elements actually read from {@code reader} (it might be less than the array length if {@code reader} ends).
 */
public static long LOAD_KEYS(final BufferedReader reader, final KEY_TYPE[][] array) throws IOException {
	return LOAD_KEYS(reader, array, 0, length(array));
}

/** 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 {
	final BufferedReader reader = new BufferedReader(new FileReader(file));
	final long result = LOAD_KEYS(reader, array, offset, length);
	reader.close();

	return result;
}

/** Loads elements from a file given by a filename, storing them in a given big-array fragment.
 *
 * @param filename a filename.
 * @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 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 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 {
	return LOAD_KEYS(file, array, 0, length(array));
}

/** Loads elements from a file given by a filename, storing them in a given 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(filename, array, 0, length(array));
}

/** Stores a big-array fragment to a given print stream.
 *
 * @param array a big array whose elements will be written to {@code stream}.
 * @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 stream a print stream.
 */
public static void STORE_KEYS(final KEY_TYPE array[][], final long offset, final long length, final PrintStream stream) {
	ensureOffsetLength(array, offset, length);

	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++) stream.println(t[d]);
	}
}

/** Stores a big array to a given print stream.
 *
 * @param array a big array whose elements will be written to {@code stream}.
 * @param stream a print stream.
 */
public static void STORE_KEYS(final KEY_TYPE array[][], final PrintStream stream) {
	STORE_KEYS(array, 0, length(array), stream);
}

/** 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 {
	final PrintStream stream = new PrintStream(new FastBufferedOutputStream(new FileOutputStream(file)));
	STORE_KEYS(array, offset, length, stream);
	stream.close();
}

/** 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 a big array to a file given by a {@link File} object.
 *
 * @param array a big 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 {
	STORE_KEYS(array, 0, length(array), file);
}

/** 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, 0, length(array), filename);
}


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

private static final class KEY_READER_WRAPPER implements KEY_ITERATOR {
	private final BufferedReader reader;
	private boolean toAdvance = true;
	private String s;
	private KEY_TYPE next;

	public KEY_READER_WRAPPER(final BufferedReader reader) {
		this.reader = reader;
	}

	@Override
	public boolean hasNext() {
		if (! toAdvance) return s != null;

		toAdvance = false;

		try {
			s = reader.readLine();
		}
		catch(EOFException itsOk) {}
		catch(IOException rethrow) { throw new RuntimeException(rethrow); }

		if (s == null) return false;

		next = KEY_CLASS.PARSE_KEY(s.trim());
		return true;
	}

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



/** Wraps the given buffered reader into an iterator.
 *
 * @param reader a buffered reader.
 */
public static KEY_ITERATOR AS_KEY_ITERATOR(final BufferedReader reader) {
	return new KEY_READER_WRAPPER(reader);
}

/** 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_READER_WRAPPER(new BufferedReader(new FileReader(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); }
	};
}

