/*
 * 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;

import static org.junit.gen5.commons.util.AnnotationUtils.findRepeatableAnnotations;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;

import org.junit.gen5.api.extension.ExtendWith;
import org.junit.gen5.api.extension.MethodParameterResolver;
import org.junit.gen5.api.extension.TestExecutionContext;
import org.junit.gen5.api.extension.TestExtension;
import org.junit.gen5.engine.TestDescriptor;
import org.junit.gen5.engine.junit5.descriptor.ClassTestDescriptor;
import org.junit.gen5.engine.junit5.descriptor.MethodTestDescriptor;

/**
 * @author Sam Brannen
 * @since 5.0
 */
class DescriptorBasedTestExecutionContext implements TestExecutionContext {

	private final Class<?> testClass;

	private final Object testInstance;

	private final Method testMethod;

	private final Map<String, Object> attributes = new HashMap<>();

	private final String displayName;

	private final TestExecutionContext parent;

	protected final TestExtensionRegistry registry;

	DescriptorBasedTestExecutionContext(TestDescriptor descriptor, TestExecutionContext parent, Object testInstance) {

		this.testInstance = testInstance;
		this.displayName = descriptor.getDisplayName();
		this.parent = parent;

		this.registry = createRegistry();

		if (descriptor instanceof ClassTestDescriptor) {
			// Also handles ContextTestDescriptor which extends ClassTestDescriptor.
			this.testClass = ((ClassTestDescriptor) descriptor).getTestClass();
			this.testMethod = null;
			fillRegistryWithTestExtensions(this.testClass);
		}
		else if (descriptor instanceof MethodTestDescriptor) {
			MethodTestDescriptor methodTestDescriptor = (MethodTestDescriptor) descriptor;
			this.testClass = ((ClassTestDescriptor) methodTestDescriptor.getParent().get()).getTestClass();
			this.testMethod = methodTestDescriptor.getTestMethod();
			fillRegistryWithTestExtensions(this.testMethod);
		}
		else {
			this.testClass = null;
			this.testMethod = null;
		}
	}

	private void fillRegistryWithTestExtensions(AnnotatedElement annotatedElement) {
		// @formatter:off
		findRepeatableAnnotations(annotatedElement, ExtendWith.class).stream()
				.map(ExtendWith::value)
				.flatMap(Arrays::stream)
				.filter(MethodParameterResolver.class::isAssignableFrom)
				.forEach(clazz -> {
					registry.addExtension(clazz);
				});
		// @formatter:off

	}

	private TestExtensionRegistry createRegistry() {
		if (this.parent == null)
			return new TestExtensionRegistry();

		if (! (this.parent instanceof DescriptorBasedTestExecutionContext))
			return new TestExtensionRegistry();

		//TODO Get rid of casting. Maybe move getRegistry into TestExecutionInterface?
		//     Would require TestExtensionsRegistry to move to junit-engine-api.
		DescriptorBasedTestExecutionContext parentContext = (DescriptorBasedTestExecutionContext) parent;
		return new TestExtensionRegistry(parentContext.registry);
	}

	@Override
	public Optional<Class<?>> getTestClass() {
		return Optional.ofNullable(this.testClass);
	}

	@Override
	public Optional<Object> getTestInstance() {
		return Optional.ofNullable(this.testInstance);
	}

	@Override
	public Optional<Method> getTestMethod() {
		return Optional.ofNullable(this.testMethod);
	}

	@Override
	public Map<String, Object> getAttributes() {
		return this.attributes;
	}

	@Override
	public String getDisplayName() {
		return this.displayName;
	}

	@Override
	public Optional<TestExecutionContext> getParent() {
		return Optional.ofNullable(this.parent);
	}

	@Override
	public Set<TestExtension> getExtensions() {
		return registry.getExtensions();
	}

	@Override
	public int hashCode() {
		return Objects.hash(this.testClass, this.testMethod, this.testInstance);
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}

		if (!(obj instanceof DescriptorBasedTestExecutionContext)) {
			return false;
		}

		DescriptorBasedTestExecutionContext that = (DescriptorBasedTestExecutionContext) obj;

		// @formatter:off
		return Objects.equals(this.testClass, that.testClass)
				&& Objects.equals(this.testMethod, that.testMethod)
				&& Objects.equals(this.testInstance, that.testInstance);
		// @formatter:on
	}

	@Override
	public String toString() {
		return "TestExecutionContext for " + getDisplayName();
	}

}

@@ -714,69 +714,8 @@
 th;%0A
-import org.junit.gen5.api.extension.MethodParameterResolver;%0A
 impo
@@ -768,16 +768,16 @@
 ontext;%0A
+
 import o
@@ -1922,32 +1922,24 @@
 ull;%0A%09%09%09
-fillRegistryWith
+populate
 TestExte
@@ -1935,33 +1935,40 @@
 ateTestExtension
-s
+Registry
 (this.testClass)
@@ -2273,32 +2273,24 @@
 d();%0A%09%09%09
-fillRegistryWith
+populate
 TestExte
@@ -2294,17 +2294,24 @@
 xtension
-s
+Registry
 (this.te
@@ -2413,24 +2413,16 @@
 oid 
-fillRegistryWith
+populate
 Test
@@ -2430,17 +2430,24 @@
 xtension
-s
+Registry
 (Annotat
@@ -2636,100 +2636,28 @@
 %09%09.f
-ilter(MethodParameterResolver.class::isAssignableFrom)%0A%09%09%09%09.forEach(clazz -%3E %7B%0A%09%09%09%09%09
+orEach(this.
 registry
 .add
@@ -2652,17 +2652,18 @@
 registry
-.
+::
 addExten
@@ -2670,22 +2670,8 @@
 sion
-(clazz);%0A%09%09%09%09%7D
 );%0A%09
@@ -2689,17 +2689,16 @@
 ter:off%0A
-%0A
 %09%7D%0A%0A%09pri
@@ -2747,75 +2747,8 @@
 ) %7B%0A
-%09%09if (this.parent == null)%0A%09%09%09return new TestExtensionRegistry();%0A%0A
 %09%09if
@@ -2857,16 +2857,17 @@
 );%0A%0A%09%09//
+ 
 TODO Get
@@ -2898,19 +2898,22 @@
 ove 
-get
+create
 Registry
  int
@@ -2908,16 +2908,18 @@
 Registry
+()
  into Te
@@ -2933,23 +2933,22 @@
 tion
-Interface
+Context
 ?%0A%09%09//
+ 
     
@@ -2975,17 +2975,16 @@
 xtension
-s
 Registry
@@ -3822,24 +3822,24 @@
 ensions() %7B%0A
-
 %09%09return reg
@@ -3835,16 +3835,21 @@
 %09return 
+this.
 registry

Enable registration of any type of TestExtension