/*
 * Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package org.graalvm.visualvm.modules.killapp;

import org.graalvm.visualvm.application.Application;
import org.graalvm.visualvm.core.datasource.DataSource;
import org.graalvm.visualvm.core.datasupport.DataRemovedListener;
import org.graalvm.visualvm.core.datasupport.Stateful;
import org.graalvm.visualvm.core.ui.actions.ActionUtils;
import org.graalvm.visualvm.core.ui.actions.MultiDataSourceAction;
import org.graalvm.visualvm.host.Host;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import javax.swing.SwingUtilities;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
import org.openide.util.RequestProcessor;
import org.openide.util.Utilities;

/**
 *
 * @author Tomas Hurka
 */
public final class KillAction extends MultiDataSourceAction<Application> {
    
    private final Set<Application> lastSelectedApplications;
    private final RequestProcessor killRP;
    private final PropertyChangeListener stateListener;

    @NbBundle.Messages({"CTL_KillAction=Kill Application"})
    public KillAction() {
        super(Application.class);
        putValue(NAME, Bundle.CTL_KillAction());
        putValue("noIconInMenu", Boolean.TRUE); // NOI18N
        lastSelectedApplications = new HashSet();
        killRP = new RequestProcessor("KillAction processor", 5);   // NOI18N
        stateListener = new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent evt) {
                updateState(ActionUtils.getSelectedDataSources(Application.class));
            }
        };
    }

    @Override
    protected void actionPerformed(Set<Application> dataSources, ActionEvent ae) {
        for (Application dataSource : dataSources) {
            killApplication((Application)dataSource);
        }
    }

    @Override
    protected boolean isEnabled(Set<Application> dataSources) {
        for (DataSource dataSource : dataSources) {
            Application application = (Application)dataSource;
                lastSelectedApplications.add(application);
            application.addPropertyChangeListener(Stateful.PROPERTY_STATE, stateListener);
            if (application.getState() != Stateful.STATE_AVAILABLE) return false;
            if (!isEnabled(application)) return false;
        }
        return true;
    }

    @Override
    protected void updateState(Set<Application> dataSources) {
        if (!lastSelectedApplications.isEmpty())
            for (Application application : lastSelectedApplications)
                application.removePropertyChangeListener(Stateful.PROPERTY_STATE, stateListener);
        lastSelectedApplications.clear();
        super.updateState(dataSources);
    }

    private void killApplication(final Application app) {
        final String pidString = String.valueOf(app.getPid());
        final String[] command = getCommand(pidString, false);

        if (command == null) {
            return;
        }

        final Progress handle = new Progress(pidString);
        app.notifyWhenRemoved(handle);
        killRP.post(new Runnable() {
            public void run() {
                try {
                    Runtime.getRuntime().exec(command);
                    Thread.sleep(5000);
                    if (app.getState() == Stateful.STATE_AVAILABLE) {
                        // application is still alive, try to kill it hard way
                        Runtime.getRuntime().exec(getCommand(pidString, true));
                        refreshJvms();
                    }
                } catch (IOException ex) {
                    Exceptions.printStackTrace(ex);
                } catch (InterruptedException ex) {
                    Exceptions.printStackTrace(ex);
                } finally {
                    handle.finish();
                }
            }
        });
    }

    private void refreshJvms() throws IOException {
        String javaSub = Utilities.isWindows() ? "bin\\java.exe" : "bin/java"; // NOI18N
        File java = new File(System.getProperty("java.home"), javaSub); // NOI18N

        if (java.isFile()) {
            String command[] = {java.getAbsolutePath(), "-version"};
            Runtime.getRuntime().exec(command);
        }
    }

    private boolean isEnabled(Application application) {
        if (Application.CURRENT_APPLICATION.equals(application)) {
            // don't commit suicide
            return false;
        }
        if (!Host.LOCALHOST.equals(application.getHost())) {
            // we cannot kill remote applications
            return false;
        }
        return true;
    }

    private String[] getCommand(String pidString, boolean force) {
        if (Utilities.isWindows()) {
            if (force) {
                return new String[]{"taskkill", "/F", "/PID", pidString};    // NOI18N                
            } else {
                return new String[]{"taskkill", "/PID", pidString};    // NOI18N
            }
        } else if (Utilities.isUnix()) {
            if (force) {
                return new String[]{"kill", "-9", pidString};   // NOI18N                
            } else {
                return new String[]{"kill", pidString};   // NOI18N
            }
        } else {
            assert false : "strange os";  // NOI18N
            return null;
        }
    }

    @NbBundle.Messages({"MSG_Kill=Killing application with PID {0}"})
    private static class Progress implements DataRemovedListener<Application>{

        ProgressHandle handle;
        boolean running;
        
        private Progress(String pid) {
            handle = ProgressHandleFactory.createHandle(Bundle.MSG_Kill(pid));
            handle.setInitialDelay(500);
            handle.start();
            running = true;
        }

        private synchronized void finish() {
            if (running) {
                running = false;
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        handle.finish();
                        handle = null;
                    }
                });
            }
        }

        public void dataRemoved(Application x) {
            finish();
        }
    }
}
