package org.junit.gen5.engine.junit5;

import org.junit.gen5.api.Test;
import org.junit.gen5.engine.TestDescriptor;
import org.junit.gen5.engine.TestEngine;
import org.junit.gen5.engine.TestListener;
import org.opentestalliance.TestAbortedException;
import org.opentestalliance.TestSkippedException;

import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;

import static java.lang.String.format;
import static org.junit.gen5.commons.util.ReflectionUtils.invokeMethod;
import static org.junit.gen5.commons.util.ReflectionUtils.newInstance;
import static org.junit.gen5.engine.TestListenerRegistry.notifyListeners;
import org.junit.gen5.engine.TestPlanSpecification;

public class JUnit5TestEngine implements TestEngine {
  // TODO - SBE - could be replace by JUnit5TestEngine.class.getCanonicalName();
  private static final String JUNIT5_ENGINE_ID = "junit5";

  @Override
  public String getId() {
    return JUNIT5_ENGINE_ID;
  }

  @Override
  public List<TestDescriptor> discoverTests(TestPlanSpecification specification) {
    List<Class<?>> testClasses = fetchTestClasses(specification);

    List<TestDescriptor> testDescriptors = testClasses.parallelStream()
        .map(Class::getDeclaredMethods)
        .flatMap(Arrays::stream)
        .filter(method -> method.isAnnotationPresent(Test.class))
        .map(method -> new JavaTestDescriptor(getId(), method))
        .peek(testDescriptor -> notifyListeners(testListener -> testListener.testFound(testDescriptor)))
        .collect(Collectors.toList());

    testDescriptors.addAll(
        configuration.getUniqueIds().parallelStream()
            .map(JavaTestDescriptor::from)
            .peek(testDescriptor -> notifyListeners(testListener -> testListener.testFound(testDescriptor)))
            .collect(Collectors.toList())
    );

    return testDescriptors;
  }

  private List<Class<?>> fetchTestClasses(TestPlanSpecification testPlanSpecification) {
    List<Class<?>> testClasses = new LinkedList<>();

    // Add specified test classes directly
    testClasses.addAll(testPlanSpecification.getClasses());

    // Add test classes by name
    for (String className : testPlanSpecification.getClassNames()) {
      try {
        testClasses.add(Class.forName(className));
      } catch (ClassNotFoundException e) {
        String msg = "Could not find test class '%s' in the classpath!";
        throw new IllegalArgumentException(format(msg, className));
      }
    }

    // TODO - SBE - Add classes for packages
    // TODO - SBE - Add classes for package names
    // TODO - SBE - Add classes for paths
    // TODO - SBE - Add classes for file names

    return testClasses;
  }

  @Override
  public boolean supports(TestDescriptor testDescriptor) {
    return testDescriptor instanceof JavaTestDescriptor;
  }

  @Override
  public void execute(Collection<TestDescriptor> testDescriptors) {
    for (TestDescriptor testDescriptor : testDescriptors) {
      try {
        notifyListeners(testListener -> testListener.testStarted(testDescriptor));
        JavaTestDescriptor javaTestDescriptor = (JavaTestDescriptor) testDescriptor;
        Object testInstance = newInstance(javaTestDescriptor.getTestClass());
        invokeMethod(javaTestDescriptor.getTestMethod(), testInstance);
        notifyListeners(testListener -> testListener.testSucceeded(testDescriptor));
      } catch (InvocationTargetException e) {
        if (e.getTargetException() instanceof TestSkippedException) {
          notifyListeners(testListener -> testListener.testSkipped(testDescriptor, e.getTargetException()));
        } else if (e.getTargetException() instanceof TestAbortedException) {
          notifyListeners(testListener -> testListener.testAborted(testDescriptor, e.getTargetException()));
        } else {
          notifyListeners(testListener -> testListener.testFailed(testDescriptor, e.getTargetException()));
        }
      } catch (NoSuchMethodException | InstantiationException | IllegalAccessException e) {
        throw new IllegalArgumentException(
            String.format("Test %s not well-formed and cannot be executed! ", testDescriptor.getUniqueId()));
      }
    }
  }
}
@@ -43,30 +43,37 @@
 ort 
-org.junit.gen5.api.Tes
+static java.lang.String.forma
 t;%0Ai
@@ -70,32 +70,39 @@
 .format;%0Aimport 
+static 
 org.junit.gen5.e
@@ -104,29 +104,49 @@
 en5.
-engine.TestDescriptor
+commons.util.ReflectionUtils.invokeMethod
 ;%0Aim
@@ -142,32 +142,39 @@
 eMethod;%0Aimport 
+static 
 org.junit.gen5.e
@@ -176,24 +176,47 @@
 en5.
-engine.TestEngin
+commons.util.ReflectionUtils.newInstanc
 e;%0Ai
@@ -213,32 +213,39 @@
 nstance;%0Aimport 
+static 
 org.junit.gen5.e
@@ -266,108 +266,32 @@
 ener
-;%0Aimport org.opentestalliance.TestAbortedException;%0Aimport org.opentestalliance.TestSkippedException
+Registry.notifyListeners
 ;%0A%0Ai
@@ -495,37 +495,30 @@
 ort 
-static java.lang.String.forma
+org.junit.gen5.api.Tes
 t;%0Ai
@@ -515,39 +515,32 @@
 pi.Test;%0Aimport 
-static 
 org.junit.gen5.c
@@ -542,49 +542,29 @@
 en5.
-commons.util.ReflectionUtils.invokeMethod
+engine.TestDescriptor
 ;%0Aim
@@ -560,39 +560,32 @@
 criptor;%0Aimport 
-static 
 org.junit.gen5.c
@@ -587,47 +587,24 @@
 en5.
-commons.util.ReflectionUtils.newInstanc
+engine.TestEngin
 e;%0Ai
@@ -601,39 +601,32 @@
 tEngine;%0Aimport 
-static 
 org.junit.gen5.e
@@ -639,88 +639,121 @@
 Test
-ListenerRegistry.notifyListeners;%0Aimport org.junit.gen5.engine.TestPlanSpecifica
+PlanSpecification;%0Aimport org.opentestalliance.TestAbortedException;%0Aimport org.opentestalliance.TestSkippedExcep
 tion
@@ -1239,25 +1239,17 @@
 Classes.
-parallelS
+s
 tream()%0A
@@ -1636,16 +1636,16 @@
     
-configur
+specific
 atio
@@ -1665,17 +1665,9 @@
 s().
-parallelS
+s
 trea

Use stream() for now