/*
 * RHQ Management Platform
 * Copyright (C) 2005-2008 Red Hat, Inc.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation version 2 of the License.
 *
 * This program 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 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package org.rhq.plugins.agent;

import java.io.File;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.configuration.PropertySimple;
import org.rhq.core.domain.measurement.AvailabilityType;
import org.rhq.core.pluginapi.inventory.InvalidPluginConfigurationException;
import org.rhq.core.pluginapi.inventory.ResourceComponent;
import org.rhq.core.pluginapi.inventory.ResourceContext;
import org.rhq.core.pluginapi.operation.OperationFacet;
import org.rhq.core.pluginapi.operation.OperationResult;
import org.rhq.core.system.ProcessExecution;
import org.rhq.core.system.ProcessExecutionResults;
import org.rhq.core.system.SystemInfo;

/**
 * The component that represents the agent's launcher script.
 *
 * @author John Mazzitelli
 */
public class AgentLauncherScriptComponent implements ResourceComponent<AgentServerComponent<?>>, OperationFacet {

    private Log log = LogFactory.getLog(AgentLauncherScriptComponent.class);

    private ResourceContext<AgentServerComponent<?>> resourceContext;

    private File launcherScript;

    public void start(ResourceContext<AgentServerComponent<?>> rc) throws Exception {

        this.resourceContext = rc;
        Configuration pc = this.resourceContext.getPluginConfiguration();
        PropertySimple prop;

        prop = pc.getSimple(AgentLauncherScriptDiscoveryComponent.PLUGINCONFIG_PATHNAME);
        if (prop == null) {
            throw new InvalidPluginConfigurationException("Missing path to launcher script");
        }
        if (prop.getStringValue() == null) {
            throw new InvalidPluginConfigurationException("Launcher script pathname property value is null");
        }

        launcherScript = new File(prop.getStringValue());
        if (!launcherScript.exists()) {
            throw new InvalidPluginConfigurationException("Launcher Script [" + launcherScript + "] does not exist");
        }

        log.debug("Starting agent launcher script component: " + launcherScript);
        return;
    }

    public void stop() {
        // nothing to do
        return;
    }

    public AvailabilityType getAvailability() {
        return (launcherScript.exists()) ? AvailabilityType.UP : AvailabilityType.DOWN;
    }

    public OperationResult invokeOperation(String name, Configuration params) throws Exception {

        log.info("Launcher script executing [" + name + "]");

        OperationResult result = null;
        try {
            if (name.equals("Status")) {
                Map<Integer, String> output = executeLauncherScript("status");
                result = new OperationResult();
                Integer exitCode = output.keySet().iterator().next();
                result.getComplexResults().put(new PropertySimple("exitCode", exitCode));
                result.getComplexResults().put(new PropertySimple("output", output.values().toArray()[0]));
            } else if (name.equals("Restart")) {
                executeLauncherScriptInThread("quiet-restart");
            } else if (name.equals("Stop")) {
                executeLauncherScriptInThread("stop");
            } else if (name.equals("Kill")) {
                executeLauncherScriptInThread("kill");
            } else {
                throw new UnsupportedOperationException("Invalid operation name: " + name);
            }
        } catch (Exception e) {
            throw new RuntimeException("Failed to invoke operation [" + name + "]", e);
        }

        log.info("Launcher script executed [" + name + "]: " + result);

        return result;
    }

    private Map<Integer, String> executeLauncherScript(String arg) throws Exception {
        if (!this.launcherScript.exists()) {
            throw new Exception("Launcher script [" + this.launcherScript + "] does not exist");
        }

        Map<String, String> envvars = new HashMap<String, String>(System.getenv());
        envvars.put("RHQ_AGENT_DEBUG", "false"); // we don't want all that debug output in the beginning

        ProcessExecution exe = new ProcessExecution(this.launcherScript.getAbsolutePath());
        exe.setArguments(new String[] { arg });
        exe.setWorkingDirectory(this.launcherScript.getParent());
        exe.setCaptureOutput(true);
        exe.setWaitForCompletion(30000L);
        exe.setEnvironmentVariables(envvars);
        ProcessExecutionResults results = this.resourceContext.getSystemInformation().executeProcess(exe);
        Throwable error = results.getError();
        if (error != null) {
            throw new Exception("Failed to invoke [" + this.launcherScript + ' ' + arg + "]", error);
        }

        HashMap<Integer, String> map = new HashMap<Integer, String>();
        Integer exitCode = results.getExitCode();
        String output = results.getCapturedOutput();
        map.put((exitCode != null) ? exitCode : Integer.valueOf(-1), (output != null) ? output : "");
        return map;
    }

    /**
     * This will execute the launcher script in a separate thread. This separate thread
     * will sleep for a few seconds before executing to give the caller enough time to
     * return itself. This is used when the launcher script being executed will quickly
     * kill the agent VM process in which we are running.
     * 
     * @param arg the command to pass to the launcher script
     *
     * @throws Exception if failed to even get a chance to spawn the thread and execute the launcher
     */
    private void executeLauncherScriptInThread(final String arg) throws Exception {
        if (!this.launcherScript.exists()) {
            throw new Exception("Launcher script [" + this.launcherScript + "] does not exist");
        }

        final File script = this.launcherScript;
        final SystemInfo sysInfo = this.resourceContext.getSystemInformation();

        Thread thread = new Thread(new Runnable() {
            public void run() {
                try {
                    Thread.sleep(10000L); // this should be enough to return our operation results back
                    ProcessExecution exe = new ProcessExecution(script.getAbsolutePath());
                    exe.setArguments(new String[] { arg });
                    exe.setWorkingDirectory(script.getParent());
                    ProcessExecutionResults results = sysInfo.executeProcess(exe);
                    if (results != null && results.getError() != null) {
                        throw results.getError();
                    }
                } catch (Throwable t) {
                    log.error("Failed to invoke [" + script + ' ' + arg + "] in a thread", t);
                }
            }
        }, "RHQ Agent Plugin Launcher Script Thread");
        thread.setDaemon(true);

        // after we start, do not linger; return fast so we can return our operation results before we die
        thread.start();
        return;
    }
}