/*
 * Copyright 2020 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.plugin.devel.internal.precompiled;

import groovy.lang.Script;
import org.gradle.api.Project;
import org.gradle.api.initialization.Settings;
import org.gradle.api.internal.GradleInternal;
import org.gradle.api.internal.SettingsInternal;
import org.gradle.api.internal.initialization.ClassLoaderScope;
import org.gradle.api.internal.initialization.ScriptHandlerFactory;
import org.gradle.api.internal.initialization.ScriptHandlerInternal;
import org.gradle.api.internal.plugins.PluginManagerInternal;
import org.gradle.api.internal.project.ProjectInternal;
import org.gradle.api.invocation.Gradle;
import org.gradle.groovy.scripts.BasicScript;
import org.gradle.groovy.scripts.ScriptRunner;
import org.gradle.groovy.scripts.ScriptSource;
import org.gradle.groovy.scripts.internal.CompiledScript;
import org.gradle.groovy.scripts.internal.ScriptRunnerFactory;
import org.gradle.internal.resource.StringTextResource;
import org.gradle.internal.resource.TextResource;
import org.gradle.internal.service.ServiceRegistry;
import org.gradle.plugin.use.internal.PluginRequestApplicator;
import org.gradle.plugin.use.internal.PluginsAwareScript;

import javax.annotation.Nullable;

public class PrecompiledScriptRunner {

    private final Object target;
    private final ServiceRegistry serviceRegistry;

    private final ScriptRunnerFactory scriptRunnerFactory;
    private final ScriptHandlerFactory scriptHandlerFactory;
    private final PluginRequestApplicator pluginRequestApplicator;

    private final ClassLoaderScope classLoaderScope;
    private final PluginManagerInternal pluginManager;

    @SuppressWarnings("unused")
    public PrecompiledScriptRunner(Project project) {
        this(project, ((ProjectInternal) project).getServices(), ((ProjectInternal) project).getClassLoaderScope(), ((ProjectInternal) project).getPluginManager());
    }

    @SuppressWarnings("unused")
    public PrecompiledScriptRunner(Settings settings) {
        this(settings, ((SettingsInternal) settings).getGradle().getServices(), ((SettingsInternal) settings).getClassLoaderScope(), null);
    }

    @SuppressWarnings("unused")
    public PrecompiledScriptRunner(Gradle gradle) {
        this(gradle, ((GradleInternal) gradle).getServices(), ((GradleInternal) gradle).getClassLoaderScope(), null);
    }

    private PrecompiledScriptRunner(Object target, ServiceRegistry serviceRegistry, ClassLoaderScope classLoaderScope, @Nullable PluginManagerInternal pluginManager) {
        this.target = target;
        this.serviceRegistry = serviceRegistry;

        this.scriptRunnerFactory = serviceRegistry.get(ScriptRunnerFactory.class);
        this.scriptHandlerFactory = serviceRegistry.get(ScriptHandlerFactory.class);
        this.pluginRequestApplicator = serviceRegistry.get(PluginRequestApplicator.class);

        this.classLoaderScope = classLoaderScope.createChild("pre-compiled-script").lock();
        this.pluginManager = pluginManager;
    }

    public void run(@Nullable Class<?> pluginsBlockClass, @Nullable Class<?> scriptClass) {
        if (pluginsBlockClass != null) {
            applyPlugins(pluginsBlockClass);
        }

        if (scriptClass != null) {
            executeScript(scriptClass);
        }
    }

    private void applyPlugins(Class<?> pluginsBlockClass) {
        ScriptSource scriptSource = new PrecompiledScriptSource(pluginsBlockClass);
        ScriptRunner<PluginsAwareScript, ?> runner = scriptRunnerFactory.create(compiledPluginsBlock(pluginsBlockClass), scriptSource, classLoaderScope.getExportClassLoader());
        runner.run(target, serviceRegistry);

        ScriptHandlerInternal scriptHandler = scriptHandlerFactory.create(scriptSource, classLoaderScope);
        pluginRequestApplicator.applyPlugins(runner.getScript().getPluginRequests(), scriptHandler, pluginManager, classLoaderScope);
    }

    private CompiledScript<PluginsAwareScript, ?> compiledPluginsBlock(Class<?> pluginsBlockClass) {
        return new CompiledGroovyPlugin<>(pluginsBlockClass, PluginsAwareScript.class);
    }

    private void executeScript(Class<?> scriptClass) {
        ScriptSource scriptSource = new PrecompiledScriptSource(scriptClass);
        scriptRunnerFactory.create(compiledScript(scriptClass), scriptSource, classLoaderScope.getExportClassLoader())
            .run(target, serviceRegistry);
    }

    private CompiledScript<BasicScript, ?> compiledScript(Class<?> precompiledScriptClass) {
        return new CompiledGroovyPlugin<>(precompiledScriptClass, BasicScript.class);
    }

    private class CompiledGroovyPlugin<T extends Script> implements CompiledScript<T, Object> {

        private final Class<? extends T> compiledClass;

        private CompiledGroovyPlugin(Class<?> scriptClass, Class<T> scriptBaseClass) {
            this.compiledClass = scriptClass.asSubclass(scriptBaseClass);
        }

        @Override
        public boolean getRunDoesSomething() {
            return true;
        }

        @Override
        public boolean getHasMethods() {
            return false;
        }

        @Override
        public Class<? extends T> loadClass() {
            return compiledClass;
        }

        @Nullable
        @Override
        public Object getData() {
            return null;
        }

        @Override
        public void onReuse() {
            classLoaderScope.onReuse();
        }
    }

    private static class PrecompiledScriptSource implements ScriptSource {

        private final Class<?> scriptClass;

        private PrecompiledScriptSource(Class<?> scriptClass) {
            this.scriptClass = scriptClass;
        }

        @Override
        public String getClassName() {
            return scriptClass.getSimpleName();
        }

        @Override
        public TextResource getResource() {
            return new StringTextResource(getClassName(), "");
        }

        @Override
        public String getFileName() {
            return getClassName() + ".class";
        }

        @Override
        public String getDisplayName() {
            return scriptClass.toString();
        }
    }
}

@@ -1362,24 +1362,83 @@
 riptSource;%0A
+import org.gradle.groovy.scripts.TextResourceScriptSource;%0A
 import org.g
@@ -1475,32 +1475,32 @@
 CompiledScript;%0A
-
 import org.gradl
@@ -1606,58 +1606,8 @@
 ce;%0A
-import org.gradle.internal.resource.TextResource;%0A
 impo
@@ -3962,32 +3962,17 @@
 ource = 
-new PrecompiledS
+s
 criptSou
@@ -4721,86 +4721,8 @@
 ) %7B%0A
-        ScriptSource scriptSource = new PrecompiledScriptSource(scriptClass);%0A
     
@@ -4785,32 +4785,45 @@
 s), scriptSource
+(scriptClass)
 , classLoaderSco
@@ -5077,32 +5077,210 @@
 .class);%0A    %7D%0A%0A
+    private static ScriptSource scriptSource(Class%3C?%3E scriptClass) %7B%0A        return new TextResourceScriptSource(new StringTextResource(scriptClass.getSimpleName(), %22%22));%0A    %7D%0A%0A
     private clas
@@ -6114,729 +6114,6 @@
  %7D%0A%0A
-    private static class PrecompiledScriptSource implements ScriptSource %7B%0A%0A        private final Class%3C?%3E scriptClass;%0A%0A        private PrecompiledScriptSource(Class%3C?%3E scriptClass) %7B%0A            this.scriptClass = scriptClass;%0A        %7D%0A%0A        @Override%0A        public String getClassName() %7B%0A            return scriptClass.getSimpleName();%0A        %7D%0A%0A        @Override%0A        public TextResource getResource() %7B%0A            return new StringTextResource(getClassName(), %22%22);%0A        %7D%0A%0A        @Override%0A        public String getFileName() %7B%0A            return getClassName() + %22.class%22;%0A        %7D%0A%0A        @Override%0A        public String getDisplayName() %7B%0A            return scriptClass.toString();%0A        %7D%0A    %7D%0A
 %7D%0A

Remove excess PrecompiledScriptSource implementation