/*
 * RHQ Management Platform
 * Copyright (C) 2005-2011 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, version 2, as
 * published by the Free Software Foundation, and/or the GNU Lesser
 * General Public License, version 2.1, also as published by the Free
 * Software Foundation.
 *
 * 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 and the GNU Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License
 * and the GNU Lesser General Public License along with this program;
 * if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */
package org.rhq.core.clientapi.descriptor;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.rhq.core.domain.plugin.Plugin;
import org.rhq.core.clientapi.descriptor.plugin.PluginDescriptor;
import org.rhq.core.clientapi.descriptor.plugin.Help;
import org.rhq.core.util.MessageDigestGenerator;

import java.net.URL;
import java.io.IOException;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
import java.util.jar.Manifest;
import java.util.jar.Attributes;

/**
 * Transforms some input into a {@link org.rhq.core.domain.plugin.Plugin} object.
 */
public class PluginTransformer {

    private static final Log LOG = LogFactory.getLog(PluginTransformer.class);

    /**
     * Takes the given plugin descriptor and plugin JAR file URL and converts them into a
     * {@link org.rhq.core.domain.plugin.Plugin} object. This method does not set the <code>content</code> property
     * of the plugin.
     *
     * @param pluginDescriptor The plugin descriptor from which to create the plugin
     * @param pluginURL The URL of the plugin JAR file from which to create the plugin
     * @return A new plugin object create from the descriptor and plugin URL
     * @throws PluginTransformException if any IO errors occur trying to read the JAR file or if a version not in the
     * plugin descriptor or in the plugin JAR manifest
     */
    public Plugin toPlugin(PluginDescriptor pluginDescriptor, URL pluginURL) {
        try {
            Plugin plugin = new Plugin();
            plugin.setName(pluginDescriptor.getName());

            if (pluginDescriptor.getDisplayName() == null) {
                plugin.setDisplayName(pluginDescriptor.getName());
            }
            else {
                plugin.setDisplayName(pluginDescriptor.getDisplayName());
            }

            plugin.setAmpsVersion(getAmpsVersion(pluginDescriptor));
            plugin.setDescription(pluginDescriptor.getDescription());
            plugin.setPath(pluginURL.getPath());
            plugin.setMtime(pluginURL.openConnection().getLastModified());
            plugin.setHelp(getHelp(pluginDescriptor));
            plugin.setMd5(getMd5(pluginURL));
            plugin.setVersion(getVersion(pluginDescriptor, pluginURL));

            return plugin;
        }
        catch (IOException e) {
            throw new PluginTransformException("Failed to create plugin.", e);
        }
    }

    private String getAmpsVersion(PluginDescriptor pluginDescriptor) {
        if (pluginDescriptor.getAmpsVersion() == null) {
            return "2.0";
        }
        return pluginDescriptor.getAmpsVersion();
    }

    private String getHelp(PluginDescriptor pluginDescriptor) {
        Help help = pluginDescriptor.getHelp();
        if (help == null || help.getContent().isEmpty()) {
            return null;
        }
        return help.getContent().get(0).toString();
    }

    private String getMd5(URL pluginURL) throws IOException {
        return MessageDigestGenerator.getDigestString(pluginURL);
    }

    String getVersion(PluginDescriptor pluginDescriptor, URL pluginURL) throws IOException {
        String version = pluginDescriptor.getVersion();
        if (version == null) {
            version = getVersionFromPluginJarManifest(pluginURL);
        }
        
        if (version == null) {
            throw new PluginTransformException("No version is defined for plugin jar [" + pluginURL
                + "]. A version must be defined either via the MANIFEST.MF '" + Attributes.Name.IMPLEMENTATION_VERSION
                + "' attribute or via the plugin descriptor 'version' attribute.");
        }

        return version;
    }

    private String getVersionFromPluginJarManifest(URL pluginJarUrl) throws IOException {
        JarInputStream jarInputStream = new JarInputStream(pluginJarUrl.openStream());
        Manifest manifest = jarInputStream.getManifest();
        if (manifest == null) {
            // BZ 682116 (ips, 03/25/11): The manifest file is not in the standard place as the 2nd entry of the JAR,
            // but we want to be flexible and support JARs that have a manifest file somewhere else, so scan the entire
            // JAR for one.
            JarEntry jarEntry;
            while((jarEntry = jarInputStream.getNextJarEntry()) != null) {
                if (JarFile.MANIFEST_NAME.equalsIgnoreCase(jarEntry.getName())) {
                    manifest = new Manifest(jarInputStream);
                    break;
                }
            }
        }
        try {
            jarInputStream.close();
        } catch (IOException e) {
            LOG.error("Failed to close plugin jar input stream for plugin jar [" + pluginJarUrl + "].", e);
        }
        if (manifest != null) {
            Attributes attributes = manifest.getMainAttributes();
            return attributes.getValue(Attributes.Name.IMPLEMENTATION_VERSION);
        } else {
            return null;
        }
    }

}
