/*
 * The MIT License
 *
 * Copyright 2015 Jesse Glick.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package org.jenkinsci.plugins.workflow.cps;

import hudson.Extension;
import hudson.FilePath;
import hudson.model.Action;
import hudson.model.Computer;
import hudson.model.Job;
import hudson.model.Queue;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.model.TopLevelItem;
import hudson.scm.SCM;
import hudson.scm.SCMDescriptor;
import hudson.slaves.WorkspaceList;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import javax.inject.Inject;
import jenkins.model.Jenkins;
import org.jenkinsci.plugins.workflow.cps.persistence.PersistIn;
import static org.jenkinsci.plugins.workflow.cps.persistence.PersistenceContext.JOB;
import org.jenkinsci.plugins.workflow.flow.FlowDefinition;
import org.jenkinsci.plugins.workflow.flow.FlowDefinitionDescriptor;
import org.jenkinsci.plugins.workflow.flow.FlowExecutionOwner;
import org.jenkinsci.plugins.workflow.steps.scm.GenericSCMStep;
import org.jenkinsci.plugins.workflow.steps.scm.SCMStep;
import org.jenkinsci.plugins.workflow.support.actions.WorkspaceActionImpl;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;

@PersistIn(JOB)
public class CpsScmFlowDefinition extends FlowDefinition {

    private final SCM scm;
    private final String scriptPath;

    @DataBoundConstructor public CpsScmFlowDefinition(SCM scm, String scriptPath) {
        this.scm = scm;
        this.scriptPath = scriptPath;
    }

    public SCM getScm() {
        return scm;
    }

    public String getScriptPath() {
        return scriptPath;
    }

    @Override public CpsFlowExecution create(FlowExecutionOwner owner, TaskListener listener, List<? extends Action> actions) throws Exception {
        for (Action a : actions) {
            if (a instanceof CpsFlowFactoryAction2) {
                return ((CpsFlowFactoryAction2) a).create(this, owner, actions);
            }
        }
        FilePath dir;
        Queue.Executable _build = owner.getExecutable();
        if (!(_build instanceof Run)) {
            throw new IOException("can only check out SCM into a Run");
        }
        Run<?,?> build = (Run<?,?>) _build;
        Jenkins jenkins = Jenkins.getInstance();
        if (jenkins == null) {
            throw new IOException("Jenkins is not running");
        }
        if (build.getParent() instanceof TopLevelItem) {
            FilePath baseWorkspace = jenkins.getWorkspaceFor((TopLevelItem) build.getParent());
            assert baseWorkspace != null : "this override should actually be @Nonnull";
            dir = baseWorkspace.withSuffix("@script");
        } else { // should not happen, but just in case:
            dir = new FilePath(owner.getRootDir());
        }
        String script;
        Computer masterComputer = jenkins.toComputer();
        if (masterComputer == null) {
            throw new IOException("Master computer not available");
        }
        SCMStep delegate = new GenericSCMStep(scm);
        delegate.setPoll(true);
        delegate.setChangelog(true);
        WorkspaceList.Lease lease = masterComputer.getWorkspaceList().acquire(dir);
        try {
            delegate.checkout(build, dir, listener, jenkins.createLauncher(listener));
            FilePath scriptFile = dir.child(scriptPath);
            if (!scriptFile.absolutize().getRemote().replace('\\', '/').startsWith(dir.absolutize().getRemote().replace('\\', '/') + '/')) { // TODO need some FilePath.isInside(FilePath) method
                throw new IOException(scriptFile + " is not inside " + dir);
            }
            script = scriptFile.readToString();
        } finally {
            lease.release();
        }
        CpsFlowExecution exec = new CpsFlowExecution(script, true, owner);
        exec.flowStartNodeActions.add(new WorkspaceActionImpl(dir, null));
        return exec;
    }

    @Extension public static class DescriptorImpl extends FlowDefinitionDescriptor {

        @Inject public Snippetizer snippetizer;

        @Override public String getDisplayName() {
            return "Groovy CPS DSL from SCM";
        }

        public Collection<? extends SCMDescriptor<?>> getApplicableDescriptors() {
            StaplerRequest req = Stapler.getCurrentRequest();
            Job job = req != null ? req.findAncestorObject(Job.class) : null;
            return job != null ? SCM._for(job) : /* TODO 1.599+ does this for job == null */ SCM.all();
        }

    }

}

@@ -1306,16 +1306,42 @@
 el.Job;%0A
+import hudson.model.Node;%0A
 import h
@@ -3361,23 +3361,17 @@
     
-Jenkins jenkins
+Node node
  = J
@@ -3387,24 +3387,84 @@
 tInstance();
+ // TODO offer to select a slave based on a configured Label
 %0A        if 
@@ -3464,23 +3464,20 @@
     if (
-jenkins
+node
  == null
@@ -3645,23 +3645,20 @@
 space = 
-jenkins
+node
 .getWork
@@ -3717,15 +3717,12 @@
     
-assert 
+if (
 base
@@ -3735,62 +3735,114 @@
 ace 
-!
+=
 = null
- : %22this override should actually be @Nonnull%22;
+) %7B%0A                throw new IOException(node.getDisplayName() + %22 may be offline%22);%0A            %7D
 %0A   
@@ -4052,23 +4052,17 @@
 omputer 
-masterC
+c
 omputer 
@@ -4063,23 +4063,20 @@
 puter = 
-jenkins
+node
 .toCompu
@@ -4094,23 +4094,17 @@
     if (
-masterC
+c
 omputer 
@@ -4152,37 +4152,47 @@
 ion(
-%22Master computer not availabl
+node.getDisplayName() + %22 may be offlin
 e%22);
@@ -4363,15 +4363,9 @@
 e = 
-masterC
+c
 ompu
@@ -4467,23 +4467,20 @@
 stener, 
-jenkins
+node
 .createL

Make it clearer that there is no fundamental restriction of using the master computer to do the checkout.