/*
 * 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.enterprise.agent.promptcmd;

import java.io.File;
import java.io.PrintWriter;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import mazz.i18n.Msg;

import org.rhq.core.clientapi.descriptor.AgentPluginDescriptorUtil;
import org.rhq.core.clientapi.descriptor.plugin.PluginDescriptor;
import org.rhq.core.clientapi.server.core.CoreServerService;
import org.rhq.core.domain.plugin.Plugin;
import org.rhq.core.pc.PluginContainerConfiguration;
import org.rhq.core.util.MessageDigestGenerator;
import org.rhq.core.util.exception.ThrowableUtil;
import org.rhq.enterprise.agent.AgentMain;
import org.rhq.enterprise.agent.PluginUpdate;
import org.rhq.enterprise.agent.i18n.AgentI18NFactory;
import org.rhq.enterprise.agent.i18n.AgentI18NResourceKeys;
import org.rhq.enterprise.communications.command.client.ClientCommandSender;
import org.rhq.enterprise.communications.command.client.ClientRemotePojoFactory;

/**
 * Allows you to manually update the plugins.
 *
 * @author John Mazzitelli
 */
public class PluginsPromptCommand implements AgentPromptCommand {
    private static final Msg MSG = AgentI18NFactory.getMsg();

    /**
     * @see AgentPromptCommand#getPromptCommandString()
     */
    public String getPromptCommandString() {
        return MSG.getMsg(AgentI18NResourceKeys.PLUGINS);
    }

    /**
     * @see AgentPromptCommand#execute(AgentMain, String[])
     */
    public boolean execute(AgentMain agent, String[] args) {
        PrintWriter out = agent.getOut();

        if (args.length != 2) {
            out.println(MSG.getMsg(AgentI18NResourceKeys.HELP_SYNTAX_LABEL, getSyntax()));
            return true;
        }

        try {
            if (args[1].equals(MSG.getMsg(AgentI18NResourceKeys.PLUGINS_ARG_UPDATE))) {
                doUpdate(agent);
            } else if (args[1].equals(MSG.getMsg(AgentI18NResourceKeys.PLUGINS_ARG_INFO))) {
                doInfo(agent);
            } else {
                out.println(MSG.getMsg(AgentI18NResourceKeys.HELP_SYNTAX_LABEL, getSyntax()));
            }
        } catch (Exception e) {
            out.println(MSG.getMsg(AgentI18NResourceKeys.PLUGINS_ERROR_UPDATING, ThrowableUtil.getAllMessages(e)));
        }

        return true;
    }

    /**
     * @see AgentPromptCommand#getSyntax()
     */
    public String getSyntax() {
        return MSG.getMsg(AgentI18NResourceKeys.PLUGINS_SYNTAX);
    }

    /**
     * @see AgentPromptCommand#getHelp()
     */
    public String getHelp() {
        return MSG.getMsg(AgentI18NResourceKeys.PLUGINS_HELP);
    }

    /**
     * @see AgentPromptCommand#getDetailedHelp()
     */
    public String getDetailedHelp() {
        return MSG.getMsg(AgentI18NResourceKeys.PLUGINS_DETAILED_HELP);
    }

    /**
     * Performs the info command which will output information on the currently installed plugins to the agent's output.
     *
     * @param  agent
     *
     * @throws Exception
     */
    private void doInfo(AgentMain agent) throws Exception {
        PrintWriter out = agent.getOut();
        PluginUpdate plugin_update = getPluginUpdateObject(agent);
        List<File> current_plugins = plugin_update.getCurrentPluginFiles();
        List<String> disabled_plugins = plugin_update.getPluginContainerConfiguration().getDisabledPlugins();
        List<String> installed_plugins = new ArrayList<String>();

        if (current_plugins.size() > 0) {
            out.println(MSG.getMsg(AgentI18NResourceKeys.PLUGINS_LISTING_PLUGINS_DETAILS));

            for (File current_plugin : current_plugins) {
                String plugin_name;
                String plugin_display_name;
                try {
                    URL url = current_plugin.toURI().toURL();
                    PluginDescriptor descriptor = AgentPluginDescriptorUtil.loadPluginDescriptorFromUrl(url);
                    plugin_name = descriptor.getName();
                    plugin_display_name = descriptor.getDisplayName();
                    installed_plugins.add(plugin_name);
                } catch (Throwable t) {
                    plugin_name = "?cannot-parse-descriptor?";
                    plugin_display_name = "?cannot-parse-descriptor?";
                }

                String filename = current_plugin.getName();
                Date last_mod = new Date(current_plugin.lastModified());
                long filesize = current_plugin.length();
                String md5 = MessageDigestGenerator.getDigestString(current_plugin);

                out.println();
                out.println(MSG.getMsg(AgentI18NResourceKeys.PLUGINS_PLUGINS_INFO_FILENAME, filename));
                out.print('\t');
                out.println(MSG.getMsg(AgentI18NResourceKeys.PLUGINS_PLUGINS_INFO_NAME, plugin_name));
                out.print('\t');
                out.println(MSG.getMsg(AgentI18NResourceKeys.PLUGINS_PLUGINS_INFO_DISPLAY_NAME, plugin_display_name));
                out.print('\t');
                out.println(MSG.getMsg(AgentI18NResourceKeys.PLUGINS_PLUGINS_INFO_LASTMOD, last_mod));
                out.print('\t');
                out.println(MSG.getMsg(AgentI18NResourceKeys.PLUGINS_PLUGINS_INFO_FILESIZE, filesize));
                out.print('\t');
                out.println(MSG.getMsg(AgentI18NResourceKeys.PLUGINS_PLUGINS_INFO_MD5, md5));
            }

            out.println();
            out.println(MSG.getMsg(AgentI18NResourceKeys.PLUGINS_LISTING_PLUGINS_SUMMARY, installed_plugins));

            if (!disabled_plugins.isEmpty()) {
                out.println();
                out.println(MSG.getMsg(AgentI18NResourceKeys.PLUGINS_LISTING_PLUGINS_DISABLED, disabled_plugins));
            }

            out.println();
            out.println(MSG.getMsg(AgentI18NResourceKeys.PLUGINS_NUM_CURRENT_PLUGINS, current_plugins.size()));
        } else {
            out.println(MSG.getMsg(AgentI18NResourceKeys.PLUGINS_NO_CURRENT_PLUGINS));
        }

        return;
    }

    /**
     * Performs the update command which will update all plugins and output to the agent's <code>out</code> stream what
     * was updated.
     *
     * @param  agent
     *
     * @throws Exception
     */
    private void doUpdate(AgentMain agent) throws Exception {
        // if the PC is already started, we need to shut it down and restart it after we get the new plugins
        boolean recyclePC = agent.isPluginContainerStarted();

        PrintWriter out = agent.getOut();

        // make sure our agent is currently in communications with the server
        ClientCommandSender clientCommandSender = agent.getClientCommandSender();
        if (clientCommandSender == null || !clientCommandSender.isSending()) {
            out.println(MSG.getMsg(AgentI18NResourceKeys.PLUGINS_ERROR_NOT_SENDING));
            return;
        }

        // use the PC prompt command to stop the PC - needed because the PC cannot hot-deploy plugins
        if (recyclePC) {
            executePCCommand(agent, "stop");
        }

        // catch any exceptions and keep going so we can restart the PC, even if plugins failed to update
        try {
            out.println(MSG.getMsg(AgentI18NResourceKeys.UPDATING_PLUGINS));

            List<Plugin> updated_plugins = updatePlugins(agent);

            if ((updated_plugins != null) && (updated_plugins.size() > 0)) {
                for (Plugin plugin : updated_plugins) {
                    if (plugin.isEnabled()) {
                        out.println(MSG.getMsg(AgentI18NResourceKeys.DOWNLOADING_PLUGIN_COMPLETE, plugin.getName(),
                            plugin.getPath()));
                    } else {
                        out.println(MSG.getMsg(AgentI18NResourceKeys.DOWNLOADING_PLUGIN_SKIPPED, plugin.getName()));
                    }
                }
            } else {
                out.println(MSG.getMsg(AgentI18NResourceKeys.UPDATING_PLUGINS_ALREADY_UPTODATE));
            }

            out.println(MSG.getMsg(AgentI18NResourceKeys.UPDATING_PLUGINS_COMPLETE));
        } catch (Throwable t) {
            out.println(MSG.getMsg(AgentI18NResourceKeys.UPDATING_PLUGINS_FAILURE));
            out.println(ThrowableUtil.getAllMessages(t));
        }

        if (recyclePC) {
            executePCCommand(agent, "start");
        }

        return;
    }

    /**
     * Updates plugins with their latest versions. If our plugins are already up-to-date, this leaves them as-is.
     *
     * @param  agent
     *
     * @return the list of plugins that were updated (those not listed were already up-to-date)
     *
     * @throws Exception if failed to update the plugins
     */
    private List<Plugin> updatePlugins(AgentMain agent) throws Exception {
        return getPluginUpdateObject(agent).updatePlugins();
    }

    /**
     * Builds a {@link PluginUpdate} object that can be used to update the plugins.
     *
     * @param  agent
     *
     * @return plugin updater
     */
    private PluginUpdate getPluginUpdateObject(AgentMain agent) {
        ClientCommandSender sender = agent.getClientCommandSender();
        CoreServerService server = null;

        if (sender != null) {
            ClientRemotePojoFactory factory = sender.getClientRemotePojoFactory();
            server = factory.getRemotePojo(CoreServerService.class);
        }

        PluginContainerConfiguration pc_config = agent.getConfiguration().getPluginContainerConfiguration();
        PluginUpdate plugin_update = new PluginUpdate(server, pc_config);

        return plugin_update;
    }

    private void executePCCommand(AgentMain agent, String startOrStop) {
        new PluginContainerPromptCommand().execute(agent, new String[] { "pc", startOrStop });
    }
}