/*
 * Copyright 2021 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 org.gradle.integtests.tooling.fixture;

import org.gradle.internal.Cast;
import org.gradle.util.GradleVersion;
import org.junit.platform.engine.ConfigurationParameters;
import org.junit.platform.engine.DiscoveryFilter;
import org.junit.platform.engine.DiscoverySelector;
import org.junit.platform.engine.EngineDiscoveryListener;
import org.junit.platform.engine.EngineDiscoveryRequest;
import org.junit.platform.engine.ExecutionRequest;
import org.junit.platform.engine.TestDescriptor;
import org.junit.platform.engine.TestEngine;
import org.junit.platform.engine.UniqueId;
import org.junit.platform.engine.discovery.ClassSelector;
import org.junit.platform.engine.discovery.DiscoverySelectors;
import org.junit.platform.engine.support.descriptor.EngineDescriptor;
import org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine;
import org.spockframework.runtime.SpockEngine;
import org.spockframework.runtime.SpockExecutionContext;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;


public class CrossVersionTestEngine extends HierarchicalTestEngine<SpockExecutionContext> {

    private static final GradleVersion MIN_LOADABLE_TAPI_VERSION = GradleVersion.version("2.6");

    private final TestEngine delegateEngine = new SpockEngine();

    @Override
    public String getId() {
        return "cross-version-test-engine";
    }

    @Override
    public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) {
        String toolingApiToLoad = System.getProperty("org.gradle.integtest.tooling-api-to-load");
        if (toolingApiToLoad == null) {
            return new EngineDescriptor(uniqueId, "skip");
        }
        System.setProperty("org.gradle.integtest.currentVersion", GradleVersion.current().getVersion());

        EngineDescriptor engineDescriptor = new EngineDescriptor(uniqueId, getId());
        engineDescriptor.addChild(delegateEngine.discover(discoveryRequest, uniqueId.append("classloader", "current")));

        if (isToolingApiVersionLoadable(toolingApiToLoad)) {
            EngineDiscoveryRequest tapiDiscoveryRequest = new ToolingApiClassloaderDiscoveryRequest(discoveryRequest, toolingApiToLoad);
            engineDescriptor.addChild(delegateEngine.discover(tapiDiscoveryRequest, uniqueId.append("classloader", "tapi")));
        }

        return engineDescriptor;
    }

    @Override
    protected SpockExecutionContext createExecutionContext(ExecutionRequest request) {
        return new SpockExecutionContext(request.getEngineExecutionListener());
    }

    private static boolean isToolingApiVersionLoadable(String candidateTapiVersion) {
        try {
            return GradleVersion.version(candidateTapiVersion).compareTo(MIN_LOADABLE_TAPI_VERSION) >= 0;
        } catch (IllegalArgumentException e) {
            // only test current -> current for quick/partial/latest selectors for now
            return false;
        }
    }
}

class ToolingApiClassloaderDiscoveryRequest implements EngineDiscoveryRequest {

    private final EngineDiscoveryRequest delegate;
    private final List<ClassSelector> selectors = new ArrayList<ClassSelector>();

    private ToolingApiDistribution toolingApi;

    ToolingApiClassloaderDiscoveryRequest(EngineDiscoveryRequest delegate, String versionToTestAgainst) {
        this.delegate = delegate;
        List<ClassSelector> testClasses = delegate.getSelectorsByType(ClassSelector.class);
        for (ClassSelector selector: testClasses) {
            if (ToolingApiSpecification.class.isAssignableFrom(selector.getJavaClass())) {
                ClassLoader classLoader = ToolingApiClassLoaderProvider.getToolingApiClassLoader(getToolingApi(versionToTestAgainst), selector.getJavaClass());
                try {
                    selectors.add(DiscoverySelectors.selectClass(classLoader.loadClass(selector.getClassName())));
                } catch (ClassNotFoundException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    private ToolingApiDistribution getToolingApi(String versionToTestAgainst) {
        if (toolingApi == null) {
            toolingApi = new ToolingApiDistributionResolver().withDefaultRepository().resolve(versionToTestAgainst);
        }
        return toolingApi;
    }

    @Override
    public <T extends DiscoverySelector> List<T> getSelectorsByType(Class<T> selectorType) {
        if (!selectorType.isAssignableFrom(ClassSelector.class)) {
            return Collections.emptyList();
        }
        return Cast.uncheckedCast(selectors);
    }

    @Override
    public <T extends DiscoveryFilter<?>> List<T> getFiltersByType(Class<T> filterType) {
        return delegate.getFiltersByType(filterType);
    }

    @Override
    public ConfigurationParameters getConfigurationParameters() {
        return delegate.getConfigurationParameters();
    }

    public EngineDiscoveryListener getDiscoveryListener() {
        return delegate.getDiscoveryListener();
    }
}

@@ -1495,27 +1495,130 @@
 runtime.
-SpockEngine
+RunContext;%0Aimport org.spockframework.runtime.SpockEngine;%0Aimport org.spockframework.runtime.SpockEngineDescriptor
 ;%0Aimport
@@ -2551,22 +2551,20 @@
 criptor 
-engine
+root
 Descript
@@ -2568,24 +2568,29 @@
 iptor = new 
+Spock
 EngineDescri
@@ -2604,21 +2604,30 @@
 iqueId, 
+RunContext.
 get
-Id
 ());%0A   
@@ -2623,38 +2623,36 @@
 get());%0A        
-engine
+Test
 Descriptor.addCh
@@ -2641,34 +2641,43 @@
 stDescriptor
-.addChild(
+ spockDescriptor = 
 delegateEngi
@@ -2748,18 +2748,138 @@
 rrent%22))
-)
 ;
+%0A        for (TestDescriptor test : spockDescriptor.getChildren()) %7B%0A            rootDescriptor.addChild(test);%0A        %7D
 %0A%0A      
@@ -3082,22 +3082,20 @@
         
-engine
+Test
 Descript
@@ -3096,26 +3096,39 @@
 scriptor
-.addChild(
+ spockTapiDescriptor = 
 delegate
@@ -3204,18 +3204,154 @@
 %22tapi%22))
-)
 ;
+%0A            for (TestDescriptor test : spockTapiDescriptor.getChildren()) %7B%0A                rootDescriptor.addChild(test);%0A            %7D
 %0A       
@@ -3369,22 +3369,20 @@
  return 
-engine
+root
 Descript

Simplify cross-version tests aggregation in custom test engine