/*
 * Copyright 2015-2016 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.descriptor;

import static org.junit.gen5.api.Assertions.*;

import java.util.HashMap;
import java.util.Optional;

import org.junit.gen5.api.Assertions;
import org.junit.gen5.api.Test;
import org.junit.gen5.api.extension.ExtensionContext;
import org.junit.gen5.engine.*;
import org.mockito.Mockito;

/**
 * Microtests for implementors of {@linkplain ExtensionContext}: {@linkplain ClassBasedContainerExtensionContext} and
 * {@linkplain MethodBasedTestExtensionContext}
 */
public class ExtensionContextTests {

	@Test
	public void fromClassTestDescriptor() {
		ClassTestDescriptor nestedClassDescriptor = nestedClassDescriptor();
		ClassTestDescriptor outerClassDescriptor = outerClassDescriptor(nestedClassDescriptor);

		ClassBasedContainerExtensionContext outerExtensionContext = new ClassBasedContainerExtensionContext(null, null,
			outerClassDescriptor);
		Assertions.assertAll("outerContext", //
			() -> assertEquals(OuterClass.class, outerExtensionContext.getTestClass()), //
			() -> assertEquals(outerClassDescriptor.getDisplayName(), outerExtensionContext.getDisplayName()), //
			() -> assertEquals(Optional.empty(), outerExtensionContext.getParent()) //
		);

		ClassBasedContainerExtensionContext nestedExtensionContext = new ClassBasedContainerExtensionContext(
			outerExtensionContext, null, nestedClassDescriptor);
		Assertions.assertSame(outerExtensionContext, nestedExtensionContext.getParent().get());
	}

	@Test
	public void fromMethodTestDescriptor() {
		MethodTestDescriptor methodTestDescriptor = methodDescriptor();
		ClassTestDescriptor classTestDescriptor = outerClassDescriptor(methodTestDescriptor);

		ClassBasedContainerExtensionContext classExtensionContext = new ClassBasedContainerExtensionContext(null, null,
			classTestDescriptor);
		MethodBasedTestExtensionContext testExtensionContext = new MethodBasedTestExtensionContext(
			classExtensionContext, null, methodTestDescriptor, new OuterClass());
		Assertions.assertAll("methodContext", //
			() -> assertEquals(OuterClass.class, testExtensionContext.getTestClass()), //
			() -> assertEquals(methodTestDescriptor.getDisplayName(), testExtensionContext.getDisplayName()), //
			() -> assertEquals(classExtensionContext, testExtensionContext.getParent().get()), //
			() -> assertEquals(OuterClass.class, testExtensionContext.getTestInstance().getClass()) //
		);

	}

	@Test
	public void flatAttributeAccess() {
		ClassTestDescriptor classTestDescriptor = outerClassDescriptor(null);

		ExtensionContext parentContext = new ClassBasedContainerExtensionContext(null, null, classTestDescriptor);

		assertNull(parentContext.getAttribute("not set"));

		parentContext.putAttribute("attr1", "value1");
		assertEquals("value1", parentContext.getAttribute("attr1"));

		assertEquals("value1", parentContext.removeAttribute("attr1"));
		assertNull(parentContext.getAttribute("attr1"));

	}

	@Test
	public void nestedAttributeAccess() {
		MethodTestDescriptor methodTestDescriptor = methodDescriptor();
		ClassTestDescriptor classTestDescriptor = outerClassDescriptor(methodTestDescriptor);

		ExtensionContext parentContext = new ClassBasedContainerExtensionContext(null, null, classTestDescriptor);

		MethodBasedTestExtensionContext childContext = new MethodBasedTestExtensionContext(parentContext, null,
			methodTestDescriptor, new OuterClass());

		parentContext.putAttribute("attr1", "value1");
		assertEquals("value1", childContext.getAttribute("attr1"));

		childContext.putAttribute("attr1", "value1 changed");
		assertEquals("value1 changed", childContext.getAttribute("attr1"));
		assertEquals("value1", parentContext.getAttribute("attr1"));

		childContext.removeAttribute("attr1");
		assertEquals("value1", childContext.getAttribute("attr1"));

	}

	@Test
	public void reportEntriesArePublishedToExecutionContext() {
		ClassTestDescriptor classTestDescriptor = outerClassDescriptor(null);

		EngineExecutionListener engineExecutionListener = Mockito.spy(EngineExecutionListener.class);

		ExtensionContext extensionContext = new ClassBasedContainerExtensionContext(null, engineExecutionListener,
			classTestDescriptor);

		HashMap<String, String> reportEntry1 = new HashMap<>();
		HashMap<String, String> reportEntry2 = new HashMap<String, String>() {
			{
				this.put("key", "value");
			}
		};

		extensionContext.publishReportEntry(reportEntry1);
		extensionContext.publishReportEntry(reportEntry2);

		Mockito.verify(engineExecutionListener, Mockito.times(1)).reportingEntryPublished(classTestDescriptor,
			reportEntry1);
		Mockito.verify(engineExecutionListener, Mockito.times(1)).reportingEntryPublished(classTestDescriptor,
			reportEntry2);

	}

	private ClassTestDescriptor nestedClassDescriptor() {
		return new ClassTestDescriptor("NestedClass", OuterClass.NestedClass.class);
	}

	private ClassTestDescriptor outerClassDescriptor(TestDescriptor child) {
		ClassTestDescriptor classTestDescriptor = new ClassTestDescriptor("OuterClass", OuterClass.class);
		if (child != null)
			classTestDescriptor.addChild(child);
		return classTestDescriptor;
	}

	private MethodTestDescriptor methodDescriptor() {
		try {
			return new MethodTestDescriptor("aMethod", OuterClass.class, OuterClass.class.getDeclaredMethod("aMethod"));
		}
		catch (NoSuchMethodException e) {
			throw new RuntimeException(e);
		}
	}

	static class OuterClass {

		class NestedClass {

		}

		void aMethod() {

		}
	}

}

@@ -405,33 +405,126 @@
 ons.
-*;%0A%0Aimport java.util.Hash
+assertEquals;%0Aimport static org.junit.gen5.api.Assertions.assertNull;%0A%0Aimport java.util.Collections;%0Aimport java.util.
 Map;
@@ -705,17 +705,84 @@
 .engine.
-*
+EngineExecutionListener;%0Aimport org.junit.gen5.engine.TestDescriptor
 ;%0Aimport
@@ -2859,17 +2859,16 @@
 //%0A%09%09);%0A
-%0A
 %09%7D%0A%0A%09@Te
@@ -3366,33 +3366,32 @@
 bute(%22attr1%22));%0A
-%0A
 %09%7D%0A%0A%09@Test%0A%09publ
@@ -4245,17 +4245,16 @@
 tr1%22));%0A
-%0A
 %09%7D%0A%0A%09@Te
@@ -4624,20 +4624,16 @@
 or);%0A%0A%09%09
-Hash
 Map%3CStri
@@ -4663,31 +4663,34 @@
 1 = 
-new Hash
+Collections.empty
 Map
-%3C%3E
 ();%0A%09%09
-Hash
 Map%3C
@@ -4724,57 +4724,32 @@
 2 = 
-new HashMap%3CString, String%3E() %7B%0A%09%09%09%7B%0A%09%09%09%09this.put
+Collections.singletonMap
 (%22ke
@@ -4765,18 +4765,8 @@
 e%22);
-%0A%09%09%09%7D%0A%09%09%7D;
 %0A%0A%09%09
@@ -5116,17 +5116,16 @@
 ntry2);%0A
-%0A
 %09%7D%0A%0A%09pri

Clean up warnings