/*
 * Copyright 2012-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package extensions;

import static org.junit.jupiter.api.extension.ConditionEvaluationResult.disabled;
import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled;
import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated;
import static org.junit.platform.commons.support.ModifierSupport.isNotStatic;

import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
import org.junit.jupiter.api.extension.ExecutionCondition;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.junit.jupiter.api.extension.ExtensionContext.Store;
import org.junit.jupiter.api.extension.TestWatcher;

/**
 * Extension that tracks whether a test failed in a test class, stores that
 * information in the root {@link ExtensionContext}, and skips
 * {@link Nested @Nested} test classes if they are annotated with
 * {@link SkipOnFailuresInEnclosingClass @SkipOnFailuresInEnclosingClass} and
 * the enclosing test class had test failures.
 *
 * @author Sam Brannen
 * @see SkipOnFailuresInEnclosingClass
 */
public class SkipOnFailuresInEnclosingClassExtension implements TestWatcher, ExecutionCondition {

	@Override
	public void testFailed(ExtensionContext context, Throwable cause) {
		// Track failures by test class name instead of test Class in order to
		// avoid holding onto Class references in the root ExecutionContext.Store
		// for the duration of the test suite.
		getStore(context).put(context.getRequiredTestClass().getName(), true);
	}

	@Override
	public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
		Class<?> testClass = context.getRequiredTestClass();

		if (isInnerClass(testClass) && isAnnotated(testClass, SkipOnFailuresInEnclosingClass.class)) {
			String enclosingClassName = testClass.getEnclosingClass().getName();
			boolean failureInEnclosingClass = getStore(context).getOrDefault(enclosingClassName, boolean.class, false);
			if (failureInEnclosingClass) {
				return disabled("Failures detected in enclosing test class: " + enclosingClassName);
			}
			return enabled("No failures detected in enclosing test class: " + enclosingClassName);
		}

		return enabled(testClass.getName() + " is not a @SkipOnFailuresInEnclosingClass candidate");
	}

	private Store getStore(ExtensionContext context) {
		return context.getRoot().getStore(Namespace.create(getClass()));
	}

	private static boolean isInnerClass(Class<?> clazz) {
		return isNotStatic(clazz) && clazz.isMemberClass();
	}

}