/*
 * Copyright 2015-2023 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
 *
 * https://www.eclipse.org/legal/epl-v20.html
 */

package org.junit.jupiter.api;

import static java.util.Spliterator.ORDERED;
import static java.util.Spliterators.spliteratorUnknownSize;
import static org.apiguardian.api.API.Status.MAINTAINED;

import java.net.URI;
import java.util.Iterator;
import java.util.function.Function;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import org.apiguardian.api.API;
import org.junit.jupiter.api.function.Executable;
import org.junit.jupiter.api.function.ThrowingConsumer;
import org.junit.platform.commons.util.Preconditions;

/**
 * A {@code DynamicTest} is a test case generated at runtime.
 *
 * <p>It is composed of a {@linkplain DynamicNode#getDisplayName display name}
 * and an {@link #getExecutable Executable}.
 *
 * <p>Instances of {@code DynamicTest} must be generated by factory methods
 * annotated with {@link TestFactory @TestFactory}.
 *
 * <p>Note that dynamic tests are quite different from standard {@link Test @Test}
 * cases since callbacks such as {@link BeforeEach @BeforeEach} and
 * {@link AfterEach @AfterEach} methods are not executed for dynamic tests.
 *
 * @since 5.0
 * @see #dynamicTest(String, Executable)
 * @see #stream(Iterator, Function, ThrowingConsumer)
 * @see Test
 * @see TestFactory
 * @see DynamicContainer
 * @see Executable
 */
@API(status = MAINTAINED, since = "5.3")
public class DynamicTest extends DynamicNode {

	/**
	 * Factory for creating a new {@code DynamicTest} for the supplied display
	 * name and executable code block.
	 *
	 * @param displayName the display name for the dynamic test; never
	 * {@code null} or blank
	 * @param executable the executable code block for the dynamic test;
	 * never {@code null}
	 * @see #stream(Iterator, Function, ThrowingConsumer)
	 */
	public static DynamicTest dynamicTest(String displayName, Executable executable) {
		return new DynamicTest(displayName, null, executable);
	}

	/**
	 * Factory for creating a new {@code DynamicTest} for the supplied display
	 * name, custom test source {@link URI}, and executable code block.
	 *
	 * @param displayName the display name for the dynamic test; never
	 * {@code null} or blank
	 * @param testSourceUri a custom test source URI for the dynamic test; may
	 * be {@code null} if the framework should generate the test source based on
	 * the {@code @TestFactory} method
	 * @param executable the executable code block for the dynamic test;
	 * never {@code null}
	 * @since 5.3
	 * @see #stream(Iterator, Function, ThrowingConsumer)
	 */
	public static DynamicTest dynamicTest(String displayName, URI testSourceUri, Executable executable) {
		return new DynamicTest(displayName, testSourceUri, executable);
	}

	/**
	 * Generate a stream of dynamic tests based on the given generator and test
	 * executor.
	 *
	 * <p>Use this method when the set of dynamic tests is nondeterministic in
	 * nature or when the input comes from an existing {@link Iterator}. See
	 * {@link #stream(Stream, Function, ThrowingConsumer)} as an alternative.
	 *
	 * <p>The given {@code inputGenerator} is responsible for generating
	 * input values. A {@link DynamicTest} will be added to the resulting
	 * stream for each dynamically generated input value, using the given
	 * {@code displayNameGenerator} and {@code testExecutor}.
	 *
	 * @param inputGenerator an {@code Iterator} that serves as a dynamic
	 * <em>input generator</em>; never {@code null}
	 * @param displayNameGenerator a function that generates a display name
	 * based on an input value; never {@code null}
	 * @param testExecutor a consumer that executes a test based on an input
	 * value; never {@code null}
	 * @param <T> the type of <em>input</em> generated by the {@code inputGenerator}
	 * and used by the {@code displayNameGenerator} and {@code testExecutor}
	 * @return a stream of dynamic tests based on the given generator and
	 * executor; never {@code null}
	 * @see #dynamicTest(String, Executable)
	 * @see #stream(Stream, Function, ThrowingConsumer)
	 */
	public static <T> Stream<DynamicTest> stream(Iterator<T> inputGenerator,
			Function<? super T, String> displayNameGenerator, ThrowingConsumer<? super T> testExecutor) {

		Preconditions.notNull(inputGenerator, "inputGenerator must not be null");

		return stream(StreamSupport.stream(spliteratorUnknownSize(inputGenerator, ORDERED), false),
			displayNameGenerator, testExecutor);
	}

	/**
	 * Generate a stream of dynamic tests based on the given input stream and
	 * test executor.
	 *
	 * <p>Use this method when the set of dynamic tests is nondeterministic in
	 * nature or when the input comes from an existing {@link Stream}. See
	 * {@link #stream(Iterator, Function, ThrowingConsumer)} as an alternative.
	 *
	 * <p>The given {@code inputStream} is responsible for supplying input values.
	 * A {@link DynamicTest} will be added to the resulting stream for each
	 * dynamically supplied input value, using the given {@code displayNameGenerator}
	 * and {@code testExecutor}.
	 *
	 * @param inputStream a {@code Stream} that supplies dynamic input values;
	 * never {@code null}
	 * @param displayNameGenerator a function that generates a display name
	 * based on an input value; never {@code null}
	 * @param testExecutor a consumer that executes a test based on an input
	 * value; never {@code null}
	 * @param <T> the type of <em>input</em> supplied by the {@code inputStream}
	 * and used by the {@code displayNameGenerator} and {@code testExecutor}
	 * @return a stream of dynamic tests based on the given generator and
	 * executor; never {@code null}
	 * @since 5.7
	 * @see #dynamicTest(String, Executable)
	 * @see #stream(Iterator, Function, ThrowingConsumer)
	 */
	@API(status = MAINTAINED, since = "5.7")
	public static <T> Stream<DynamicTest> stream(Stream<T> inputStream,
			Function<? super T, String> displayNameGenerator, ThrowingConsumer<? super T> testExecutor) {

		Preconditions.notNull(inputStream, "inputStream must not be null");
		Preconditions.notNull(displayNameGenerator, "displayNameGenerator must not be null");
		Preconditions.notNull(testExecutor, "testExecutor must not be null");

		return inputStream //
				.map(input -> dynamicTest(displayNameGenerator.apply(input), () -> testExecutor.accept(input)));
	}

	/**
	 * Generate a stream of dynamic tests based on the given generator and test
	 * executor.
	 *
	 * <p>Use this method when the set of dynamic tests is nondeterministic in
	 * nature or when the input comes from an existing {@link Iterator}. See
	 * {@link #stream(Stream, ThrowingConsumer)} as an alternative.
	 *
	 * <p>The given {@code inputGenerator} is responsible for generating
	 * input values and display names. A {@link DynamicTest} will be added to
	 * the resulting stream for each dynamically generated input value,
	 * using the given {@code testExecutor}.
	 *
	 * @param inputGenerator an {@code Iterator} with {@code Named} values
	 * that serves as a dynamic <em>input generator</em>; never {@code null}
	 * @param testExecutor a consumer that executes a test based on an input
	 * value; never {@code null}
	 * @param <T> the type of <em>input</em> generated by the {@code inputGenerator}
	 * and used by the {@code testExecutor}
	 * @return a stream of dynamic tests based on the given generator and
	 * executor; never {@code null}
	 * @since 5.8
	 *
	 * @see #dynamicTest(String, Executable)
	 * @see #stream(Stream, ThrowingConsumer)
	 * @see Named
	 */
	@API(status = MAINTAINED, since = "5.8")
	public static <T> Stream<DynamicTest> stream(Iterator<? extends Named<T>> inputGenerator,
			ThrowingConsumer<? super T> testExecutor) {
		Preconditions.notNull(inputGenerator, "inputGenerator must not be null");

		return stream(StreamSupport.stream(spliteratorUnknownSize(inputGenerator, ORDERED), false), testExecutor);
	}

	/**
	 * Generate a stream of dynamic tests based on the given input stream and
	 * test executor.
	 *
	 * <p>Use this method when the set of dynamic tests is nondeterministic in
	 * nature or when the input comes from an existing {@link Stream}. See
	 * {@link #stream(Iterator, ThrowingConsumer)} as an alternative.
	 *
	 * <p>The given {@code inputStream} is responsible for supplying input values
	 * and display names. A {@link DynamicTest} will be added to the resulting stream for
	 * each dynamically supplied input value, using the given {@code testExecutor}.
	 *
	 * @param inputStream a {@code Stream} that supplies dynamic {@code Named}
	 * input values; never {@code null}
	 * @param testExecutor a consumer that executes a test based on an input
	 * value; never {@code null}
	 * @param <T> the type of <em>input</em> supplied by the {@code inputStream}
	 * and used by the {@code displayNameGenerator} and {@code testExecutor}
	 * @return a stream of dynamic tests based on the given generator and
	 * executor; never {@code null}
	 * @since 5.8
	 *
	 * @see #dynamicTest(String, Executable)
	 * @see #stream(Iterator, ThrowingConsumer)
	 * @see Named
	 */
	@API(status = MAINTAINED, since = "5.8")
	public static <T> Stream<DynamicTest> stream(Stream<? extends Named<T>> inputStream,
			ThrowingConsumer<? super T> testExecutor) {
		Preconditions.notNull(inputStream, "inputStream must not be null");
		Preconditions.notNull(testExecutor, "testExecutor must not be null");

		return inputStream //
				.map(input -> dynamicTest(input.getName(), () -> testExecutor.accept(input.getPayload())));
	}

	private final Executable executable;

	private DynamicTest(String displayName, URI testSourceUri, Executable executable) {
		super(displayName, testSourceUri);
		this.executable = Preconditions.notNull(executable, "executable must not be null");
	}

	/**
	 * Get the {@code executable} code block associated with this {@code DynamicTest}.
	 */
	public Executable getExecutable() {
		return this.executable;
	}

}
