package io.cucumber.junit.platform.engine;

import io.cucumber.core.eventbus.EventBus;
import io.cucumber.core.gherkin.Feature;
import io.cucumber.core.gherkin.Pickle;
import io.cucumber.core.plugin.PluginFactory;
import io.cucumber.core.plugin.Plugins;
import io.cucumber.core.runner.Runner;
import io.cucumber.core.runtime.BackendServiceLoader;
import io.cucumber.core.runtime.BackendSupplier;
import io.cucumber.core.runtime.ObjectFactoryServiceLoader;
import io.cucumber.core.runtime.ObjectFactorySupplier;
import io.cucumber.core.runtime.ScanningTypeRegistryConfigurerSupplier;
import io.cucumber.core.runtime.ThreadLocalObjectFactorySupplier;
import io.cucumber.core.runtime.ThreadLocalRunnerSupplier;
import io.cucumber.core.runtime.TimeServiceEventBus;
import io.cucumber.core.runtime.TypeRegistryConfigurerSupplier;
import io.cucumber.plugin.event.TestRunFinished;
import io.cucumber.plugin.event.TestRunStarted;
import io.cucumber.plugin.event.TestSourceRead;
import org.junit.platform.commons.logging.Logger;
import org.junit.platform.commons.logging.LoggerFactory;
import org.junit.platform.engine.ConfigurationParameters;
import org.junit.platform.engine.support.hierarchical.EngineExecutionContext;

import java.time.Clock;
import java.util.UUID;
import java.util.function.Supplier;

class CucumberEngineExecutionContext implements EngineExecutionContext {

    private static final Logger logger = LoggerFactory.getLogger(CucumberEngineExecutionContext.class);
    private final ThreadLocalRunnerSupplier runnerSupplier;
    private final EventBus bus;

    CucumberEngineExecutionContext(ConfigurationParameters configurationParameters) {

        Supplier<ClassLoader> classLoader = CucumberEngineExecutionContext.class::getClassLoader;
        logger.debug(() -> "Parsing options");
        CucumberEngineOptions options = new CucumberEngineOptions(configurationParameters);
        ObjectFactoryServiceLoader objectFactoryServiceLoader = new ObjectFactoryServiceLoader(options);
        ObjectFactorySupplier objectFactorySupplier = new ThreadLocalObjectFactorySupplier(objectFactoryServiceLoader);
        BackendSupplier backendSupplier = new BackendServiceLoader(classLoader, objectFactorySupplier);
        this.bus = new TimeServiceEventBus(Clock.systemUTC(), UUID::randomUUID);
        TypeRegistryConfigurerSupplier typeRegistryConfigurerSupplier = new ScanningTypeRegistryConfigurerSupplier(classLoader, options);
        Plugins plugins = new Plugins(new PluginFactory(), options);
        if (options.isParallelExecutionEnabled()) {
            plugins.setSerialEventBusOnEventListenerPlugins(bus);
        } else {
            plugins.setEventBusOnEventListenerPlugins(bus);
        }
        this.runnerSupplier = new ThreadLocalRunnerSupplier(options, bus, backendSupplier, objectFactorySupplier, typeRegistryConfigurerSupplier);
    }

    void startTestRun() {
        logger.debug(() -> "Sending run test started event");
        bus.send(new TestRunStarted(bus.getInstant()));
    }

    void beforeFeature(Feature feature) {
        logger.debug(() -> "Sending test source read event for " + feature.getUri());
        bus.send(new TestSourceRead(bus.getInstant(), feature.getUri(), feature.getSource()));
    }

    void runTestCase(Pickle pickle) {
        Runner runner = getRunner();
        try (TestCaseResultObserver observer = TestCaseResultObserver.observe(runner.getBus())) {
            logger.debug(() -> "Executing test case " + pickle.getName());
            runner.runPickle(pickle);
            logger.debug(() -> "Finished test case " + pickle.getName());
            observer.assertTestCasePassed();
        }
    }

    void finishTestRun() {
        logger.debug(() -> "Sending test run finished event");
        bus.send(new TestRunFinished(bus.getInstant()));
    }

    private Runner getRunner() {
        try {
            return runnerSupplier.get();
        } catch (Throwable e) {
            logger.error(e, () -> "Unable to start Cucumber");
            throw e;
        }
    }

}


@@ -540,36 +540,205 @@
 ime.
-ScanningTypeRegistryConfigur
+RunnerSupplier;%0Aimport io.cucumber.core.runtime.ScanningTypeRegistryConfigurerSupplier;%0Aimport io.cucumber.core.runtime.SingletonObjectFactorySupplier;%0Aimport io.cucumber.core.runtime.SingletonRunn
 erSu
@@ -1656,27 +1656,16 @@
 e final 
-ThreadLocal
 RunnerSu
@@ -2153,232 +2153,8 @@
 s);%0A
-        ObjectFactorySupplier objectFactorySupplier = new ThreadLocalObjectFactorySupplier(objectFactoryServiceLoader);%0A        BackendSupplier backendSupplier = new BackendServiceLoader(classLoader, objectFactorySupplier);%0A
     
@@ -2429,32 +2429,33 @@
 ry(), options);%0A
+%0A
         if (opti
@@ -2548,32 +2548,415 @@
 erPlugins(bus);%0A
+            ObjectFactorySupplier objectFactorySupplier = new ThreadLocalObjectFactorySupplier(objectFactoryServiceLoader);%0A            BackendSupplier backendSupplier = new BackendServiceLoader(classLoader, objectFactorySupplier);%0A            this.runnerSupplier = new ThreadLocalRunnerSupplier(options, bus, backendSupplier, objectFactorySupplier, typeRegistryConfigurerSupplier);%0A
         %7D else %7B
@@ -3028,55 +3028,277 @@
     
-%7D%0A
     
-    this.runnerSupplier = new ThreadLocal
+ObjectFactorySupplier objectFactorySupplier = new SingletonObjectFactorySupplier(objectFactoryServiceLoader);%0A            BackendSupplier backendSupplier = new BackendServiceLoader(classLoader, objectFactorySupplier);%0A            this.runnerSupplier = new Singleton
 Runn
@@ -3391,24 +3391,34 @@
 rSupplier);%0A
+        %7D%0A
     %7D%0A%0A    v
@@ -3691,35 +3691,85 @@
 Uri());%0A        
-bus
+// Invoked concurrently.%0A        getRunner().getBus()
 .send(new TestSo

Fix concurrent modification of event queue