/*
 * Copyright 2015-2018 the original author or authors.
 *
 * All rights reserved. This program and the accompanying materials are
 * made available under the terms of the Eclipse Public License v2.0 which
 * accompanies this distribution and is available at
 *
 * http://www.eclipse.org/legal/epl-v20.html
 */

package org.junit.jupiter.params.provider;

import static java.util.Spliterators.spliteratorUnknownSize;
import static java.util.stream.StreamSupport.stream;
import static org.junit.jupiter.params.provider.Arguments.arguments;

import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Spliterator;
import java.util.function.BiFunction;
import java.util.stream.Stream;

import com.univocity.parsers.csv.CsvParser;
import com.univocity.parsers.csv.CsvParserSettings;

import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.params.support.AnnotationConsumer;
import org.junit.platform.commons.util.Preconditions;

/**
 * @since 5.0
 */
class CsvFileArgumentsProvider implements ArgumentsProvider, AnnotationConsumer<CsvFileSource> {

	private final BiFunction<Class<?>, String, InputStream> inputStreamProvider;

	private String[] resources;
	private Charset charset;
	private CsvParserSettings settings;
	private int numLinesToSkip;

	CsvFileArgumentsProvider() {
		this(Class::getResourceAsStream);
	}

	CsvFileArgumentsProvider(BiFunction<Class<?>, String, InputStream> inputStreamProvider) {
		this.inputStreamProvider = inputStreamProvider;
	}

	@Override
	public void accept(CsvFileSource annotation) {
		resources = annotation.resources();
		charset = Charset.forName(annotation.encoding());
		numLinesToSkip = annotation.numLinesToSkip();
		settings = new CsvParserSettings();
		settings.getFormat().setDelimiter(annotation.delimiter());
		settings.getFormat().setLineSeparator(annotation.lineSeparator());
		settings.getFormat().setQuote('"');
		settings.getFormat().setQuoteEscape('"');
		settings.setEmptyValue("");
		settings.setAutoConfigurationEnabled(false);
	}

	@Override
	public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
		// @formatter:off
		return Arrays.stream(resources)
				.map(resource -> openInputStream(context, resource))
				.map(this::createCsvParser)
				.flatMap(this::toStream);
		// @formatter:on
	}

	private InputStream openInputStream(ExtensionContext context, String resource) {
		Class<?> testClass = context.getRequiredTestClass();
		return Preconditions.notNull(inputStreamProvider.apply(testClass, resource),
			() -> "Classpath resource does not exist: " + resource);
	}

	private CsvParser createCsvParser(InputStream inputStream) {
		CsvParser csvParser = new CsvParser(settings);
		csvParser.beginParsing(inputStream, charset);
		return csvParser;
	}

	private Stream<Arguments> toStream(CsvParser csvParser) {
		return stream(spliteratorUnknownSize(new CsvParserIterator(csvParser), Spliterator.ORDERED), false) //
				.skip(numLinesToSkip) //
				.onClose(csvParser::stopParsing);
	}

	private static class CsvParserIterator implements Iterator<Arguments> {

		private final CsvParser csvParser;

		private Object[] nextCsvRecord;

		CsvParserIterator(CsvParser csvParser) {
			this.csvParser = csvParser;
			advance();
		}

		@Override
		public boolean hasNext() {
			return this.nextCsvRecord != null;
		}

		@Override
		public Arguments next() {
			Arguments result = arguments(this.nextCsvRecord);
			advance();
			return result;
		}

		private void advance() {
			this.nextCsvRecord = csvParser.parseNext();
		}
	}

}

@@ -1785,24 +1785,286 @@
 Settings();%0A
+%09%09// Do not use the built-in support for skipping rows/lines since it will%0A%09%09// throw an IllegalArgumentException if the file does not contain at least%0A%09%09// the number of specified lines to skip.%0A%09%09// settings.setNumberOfRowsToSkip(annotation.numLinesToSkip());%0A
 %09%09settings.g

Add note regarding CommonParserSettings.setNumberOfRowsToSkip()