package jenkins.slaves.restarter;

import static java.util.logging.Level.FINE;
import static java.util.logging.Level.SEVERE;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.Extension;
import hudson.Functions;
import hudson.model.Computer;
import hudson.model.TaskListener;
import hudson.remoting.Engine;
import hudson.remoting.EngineListener;
import hudson.remoting.EngineListenerAdapter;
import hudson.remoting.VirtualChannel;
import hudson.slaves.ComputerListener;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.logging.Logger;
import jenkins.security.MasterToSlaveCallable;

/**
 * Actual agent restart logic.
 *
 * <p>
 * Use {@link ComputerListener} to install {@link EngineListener}, which in turn gets executed when
 * the agent gets disconnected.
 *
 * @author Kohsuke Kawaguchi
 */
@Extension
public class JnlpSlaveRestarterInstaller extends ComputerListener implements Serializable {
    @SuppressFBWarnings(value = "RV_RETURN_VALUE_IGNORED_BAD_PRACTICE", justification = "method signature does not permit plumbing through the return value")
    @Override
    public void onOnline(final Computer c, final TaskListener listener) throws IOException, InterruptedException {
        Computer.threadPoolForRemoting.submit(new Install(c, listener));
    }

    private static class Install implements Callable<Void> {
        private final Computer c;
        private final TaskListener listener;

        Install(Computer c, TaskListener listener) {
            this.c = c;
            this.listener = listener;
        }

        @Override
        public Void call() throws Exception {
            install(c, listener);
            return null;
        }
    }

    private static void install(Computer c, TaskListener listener) {
        try {
            final List<SlaveRestarter> restarters = new ArrayList<>(SlaveRestarter.all());

            VirtualChannel ch = c.getChannel();
            if (ch == null) return;  // defensive check

            List<SlaveRestarter> effective = ch.call(new FindEffectiveRestarters(restarters));

            LOGGER.log(FINE, "Effective SlaveRestarter on {0}: {1}", new Object[] {c.getName(), effective});
        } catch (Throwable e) {
            Functions.printStackTrace(e, listener.error("Failed to install restarter"));
        }
    }

    private static class FindEffectiveRestarters extends MasterToSlaveCallable<List<SlaveRestarter>, IOException> {
        private final List<SlaveRestarter> restarters;

        FindEffectiveRestarters(List<SlaveRestarter> restarters) {
            this.restarters = restarters;
        }

        @Override
        public List<SlaveRestarter> call() throws IOException {
            Engine e = Engine.current();
            if (e == null) return null;    // not running under Engine

            try {
                Engine.class.getMethod("addListener", EngineListener.class);
            } catch (NoSuchMethodException ignored) {
                return null;    // running with older version of remoting that doesn't support adding listener
            }

            // filter out ones that doesn't apply
            restarters.removeIf(r -> !r.canWork());

            e.addListener(new EngineListenerAdapterImpl(restarters));

            return restarters;
        }
    }

    private static final class EngineListenerAdapterImpl extends EngineListenerAdapter {
        private final List<SlaveRestarter> restarters;

        EngineListenerAdapterImpl(List<SlaveRestarter> restarters) {
            this.restarters = restarters;
        }

        @Override
        public void onReconnect() {
            try {
                for (SlaveRestarter r : restarters) {
                    try {
                        Logger.getGlobal().info("Restarting agent via " + r);
                        r.restart();
                    } catch (Exception x) {
                        Logger.getGlobal().log(SEVERE, "Failed to restart agent with " + r, x);
                    }
                }
            } finally {
                // if we move on to the reconnection without restart,
                // don't let the current implementations kick in when the agent loses connection again
                restarters.clear();
            }
        }
    }

    private static final Logger LOGGER = Logger.getLogger(JnlpSlaveRestarterInstaller.class.getName());

    private static final long serialVersionUID = 1L;
}

@@ -1788,39 +1788,37 @@
 null;%0A        %7D%0A
+%0A
     
-%7D%0A%0A
     private stat
@@ -1870,38 +1870,46 @@
 ener) %7B%0A        
+   
+ 
 try %7B%0A
+    
             fina
@@ -1996,16 +1996,20 @@
         
+ 
+   
 VirtualC
@@ -2044,24 +2044,28 @@
             
+   
+ 
 if (ch == nu
@@ -2097,16 +2097,20 @@
  check%0A%0A
+    
         
@@ -2209,16 +2209,20 @@
         
+ 
+   
 LOGGER.l
@@ -2310,16 +2310,20 @@
 tive%7D);%0A
+    
         
@@ -2358,16 +2358,20 @@
         
+    
 Function
@@ -2435,24 +2435,38 @@
 starter%22));%0A
+            %7D%0A
         %7D%0A  
@@ -2961,283 +2961,8 @@
 ne%0A%0A
-            try %7B%0A                Engine.class.getMethod(%22addListener%22, EngineListener.class);%0A            %7D catch (NoSuchMethodException ignored) %7B%0A                return null;    // running with older version of remoting that doesn't support adding listener%0A            %7D%0A%0A
     

Clean up `JnlpSlaveRestarterInstaller` (#7302)