/*
 * Copyright 2015 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 v1.0 which
 * accompanies this distribution and is available at
 *
 * http://www.eclipse.org/legal/epl-v10.html
 */

package org.junit.gen5.engine.junit5.execution.injection;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import org.junit.gen5.engine.junit5.execution.TestExecutionContext;

/**
 * @author Sam Brannen
 * @since 5.0
 */
public class MethodArgumentResolverEngine {

	// TODO When introducing the extension mechanism this instance will have to come from
	// outside
	private final MethodArgumentResolverRegistry resolverRegistry = new PrimitiveMethodArgumentResolverRegistry();

	/**
	 * prepare a list of objects as arguments for the execution of this test method
	 *
	 * @param testExecutionContext the test execution context for the underlying (test) method
	 * @return a list of Objects to be used as arguments in the method call - will be an empty list in case of no-arg methods
	 * @throws ArgumentResolutionException
	 */
	public List<Object> prepareArguments(TestExecutionContext testExecutionContext) throws ArgumentResolutionException {
		return this.doPrepareArguments(testExecutionContext);
	}

	private List<Object> doPrepareArguments(TestExecutionContext testExecutionContext)
			throws ArgumentResolutionException {

		Method testMethod = testExecutionContext.getDescriptor().getTestMethod();

		List<Object> arguments = new ArrayList<>();

		if (testMethod.getParameterCount() > 0) {
			Parameter[] parameters = testMethod.getParameters();
			for (Parameter parameter : parameters) {
				Object newInstance = this.resolveArgumentForMethodParameter(parameter, testExecutionContext);
				arguments.add(newInstance);
			}
		}

		return arguments;
	}

	private Object resolveArgumentForMethodParameter(Parameter parameter, TestExecutionContext testExecutionContext)
			throws ArgumentResolutionException {

		try {
			// @formatter:off
			List<MethodArgumentResolver> matchingResolvers = this.resolverRegistry.getMethodArgumentResolvers().stream()
					.filter(resolver -> resolver.supports(parameter))
					.collect(Collectors.toList());
			// @formatter:oN

			if (matchingResolvers.size() > 1) {
				throw new ArgumentResolutionException("Too many resolvers found for parameter: " + parameter);
			}
			if (matchingResolvers.size() == 0) {
				throw new ArgumentResolutionException("No resolver found for parameter: " + parameter);
			}
			return matchingResolvers.get(0).resolveArgument(parameter, testExecutionContext);
		}
		catch (Exception ex) {
			if (ex instanceof ArgumentResolutionException) {
				throw (ArgumentResolutionException) ex;
			}
			throw new ArgumentResolutionException(ex);
		}
	}

}

@@ -2418,11 +2418,12 @@
 e() 
-%3E 1
+== 0
 ) %7B%0A
@@ -2468,32 +2468,50 @@
 ion(
-%22Too many resolvers foun
+%0A%09%09%09%09%09%22No MethodArgumentResolver registere
 d fo
@@ -2580,105 +2580,371 @@
 e() 
-== 0) %7B%0A%09%09%09%09throw new ArgumentResolutionException(%22No resolver found for parameter: %22 + parameter
+%3E 1) %7B%0A%09%09%09%09// @formatter:off%0A%09%09%09%09List%3CString%3E resolverNames = matchingResolvers.stream()%0A%09%09%09%09%09%09.map(resolver -%3E resolver.getClass().getName())%0A%09%09%09%09%09%09.collect(toList());%0A%09%09%09%09// @formatter:on%0A%09%09%09%09throw new ArgumentResolutionException(%0A%09%09%09%09%09String.format(%22Discovered multiple competing MethodArgumentResolvers for parameter %5B%25s%5D: %25s%22,%0A%09%09%09%09%09%09parameter, resolverNames)
 );%0A%09
@@ -3203,16 +3203,152 @@
 ception(
+String.format(%22Failed to resolve parameter %5B%25s%5D for method %5B%25s%5D%22,%0A%09%09%09%09parameter, testExecutionContext.getDescriptor().getTestMethod()), 
 ex);%0A%09%09%7D

Improve exception messages in MethodArgumentResolverEngine