package jenkins.slaves.restarter;

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 jenkins.model.Jenkins.MasterComputer;

import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Logger;

import static java.util.logging.Level.*;
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 {
    @Override
    public void onOnline(final Computer c, final TaskListener listener) throws IOException, InterruptedException {
        MasterComputer.threadPoolForRemoting.submit(new java.util.concurrent.Callable<Void>() {
            @Override
            public Void call() throws Exception {
                install(c, listener);
                return null;
            }
        });
    }

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

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

            List<SlaveRestarter> effective = ch.call(new MasterToSlaveCallable<List<SlaveRestarter>, IOException>() {
                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 _) {
                        return null;    // running with older version of remoting that doesn't support adding listener
                    }

                    // filter out ones that doesn't apply
                    for (Iterator<SlaveRestarter> itr = restarters.iterator(); itr.hasNext(); ) {
                        SlaveRestarter r =  itr.next();
                        if (!r.canWork())
                            itr.remove();
                    }

                    e.addListener(new EngineListenerAdapter() {
                        @Override
                        public void onReconnect() {
                            try {
                                for (SlaveRestarter r : restarters) {
                                    try {
                                        LOGGER.info("Restarting agent via "+r);
                                        r.restart();
                                    } catch (Exception x) {
                                        LOGGER.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();
                            }
                        }
                    });

                    return 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 final Logger LOGGER = Logger.getLogger(JnlpSlaveRestarterInstaller.class.getName());

    private static final long serialVersionUID = 1L;
}
