/*
 * 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, 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.enterprise.server.plugin.pc.content.metadata;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

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

import org.rhq.core.clientapi.agent.metadata.ConfigurationMetadataParser;
import org.rhq.core.domain.configuration.definition.ConfigurationDefinition;
import org.rhq.core.domain.content.ContentSourceType;
import org.rhq.core.domain.content.DownloadMode;
import org.rhq.enterprise.server.xmlschema.generated.serverplugin.content.ContentPluginDescriptorType;
import org.rhq.enterprise.server.xmlschema.generated.serverplugin.content.ContentSourceTypeDefinition;

/**
 * This is meant to provide an interface to the underlying metadata of a server plugin for content. It will load,
 * translate and cache the metadata for the rest of the services in the form of the domain object classes and the jaxb
 * version of the descriptors.
 *
 * @author John Mazzitelli
 */
public class ContentSourcePluginMetadataManager {
    private Log log = LogFactory.getLog(ContentSourcePluginMetadataManager.class);

    // outer map is keyed by plugin name; inner map keyed by content source type name
    private Map<String, Map<String, ContentSourceType>> loadedPlugins;

    public ContentSourcePluginMetadataManager() {
        loadedPlugins = new HashMap<String, Map<String, ContentSourceType>>();
    }

    /**
     * This will remove all knowledge of the given plugin - that is, all content source types deployed by the plugin
     * will be removed from this manager.
     *
     * @param pluginName the plugin whose types are to be removed
     */
    public synchronized void unloadPlugin(String pluginName) {
        loadedPlugins.remove(pluginName);
    }

    /**
     * Transforms the pluginDescriptor into domain object form and stores those objects.
     *
     * @param  pluginDescriptor the descriptor to transform
     *
     * @return the new content source types represented by this descriptor (not a copy)
     */
    public synchronized Collection<ContentSourceType> loadPlugin(ContentPluginDescriptorType pluginDescriptor) {
        try {
            Map<String, ContentSourceType> pluginTypes = loadedPlugins.get(pluginDescriptor.getName());

            if (pluginTypes != null) {
                pluginTypes.clear(); // this is a redeploy, delete the original types
            } else {
                pluginTypes = new HashMap<String, ContentSourceType>();
                loadedPlugins.put(pluginDescriptor.getName(), pluginTypes);
            }

            for (ContentSourceTypeDefinition newType : pluginDescriptor.getContentSourceType()) {
                // TODO: right now, content source types cannot have the same name as any other one
                //       even if defined in another plugin (i.e plugin's content types are not scoped)
                //       Do we care?  This just means one plugin dev cannot name a content source type
                //       something that another plugin dev used already in another plugin.
                if (getContentSourceType(newType.getName()) == null) {
                    // create the new domain object represented by the XML
                    ContentSourceType contentSourceType = new ContentSourceType();
                    contentSourceType.setName(newType.getName());
                    contentSourceType.setDisplayName(newType.getDisplayName());
                    contentSourceType.setDescription(newType.getDescription());
                    contentSourceType.setPluginName(pluginDescriptor.getName());
                    contentSourceType.setDefaultLazyLoad(newType.isLazyLoad());
                    contentSourceType.setDefaultDownloadMode(DownloadMode.valueOf(newType.getDownloadMode()
                        .toUpperCase()));
                    contentSourceType.setDefaultSyncSchedule(newType.getSyncSchedule());
                    contentSourceType.setContentSourceApiClass(newType.getApiClass());

                    ConfigurationDefinition configDef = ConfigurationMetadataParser.parse(newType.getName(), newType
                        .getConfiguration());
                    contentSourceType.setContentSourceConfigurationDefinition(configDef);

                    // add the new domain object to the list of domain objects for this plugin
                    pluginTypes.put(contentSourceType.getName(), contentSourceType);
                } else {
                    throw new Exception("Cannot redefine an existing content source type:" + newType.getName());
                }
            }

            return pluginTypes.values();
        } catch (Exception e) {
            log.error("Error transforming plugin descriptor [" + pluginDescriptor.getName() + "]", e);
        }

        return null;
    }

    /**
     * Returns the {@link ContentSourceType} that has the given name. If no plugin defined a type with that name, <code>
     * null</code> is returned.
     *
     * @param  typeName the name of the content source type to return
     *
     * @return the content source type with the given name or <code>null</code> if none exists
     */
    public synchronized ContentSourceType getContentSourceType(String typeName) {
        for (Map<String, ContentSourceType> pluginTypes : loadedPlugins.values()) {
            ContentSourceType type = pluginTypes.get(typeName);
            if (type != null) {
                return type;
            }
        }

        return null;
    }

    /**
     * Returns all the {@link ContentSourceType}s that have been defined in all plugins.
     *
     * @return all content source types defined; will be empty if there are none
     */
    public synchronized Set<ContentSourceType> getAllContentSourceTypes() {
        Set<ContentSourceType> allTypes = new HashSet<ContentSourceType>();

        for (Map<String, ContentSourceType> pluginTypes : loadedPlugins.values()) {
            allTypes.addAll(pluginTypes.values());
        }

        return allTypes;
    }

    /**
     * Returns the names of all plugins that defined content source types.
     *
     * @return all plugin names
     */
    public synchronized Set<String> getPluginNames() {
        return this.loadedPlugins.keySet();
    }

    /**
     * Returns the {@link ContentSourceType}s defined by the given plugin. If there is no plugin with that name, <code>
     * null</code> is returned.
     *
     * @param  pluginName the name of the plugin whose types are to be returned
     *
     * @return the content source types defined by the given plugin or <code>null</code> if no plugin exists with that
     *         name - the returned collection is not a copy, its backed by this manager
     */
    public synchronized Collection<ContentSourceType> getContentSourceTypesByPlugin(String pluginName) {
        Map<String, ContentSourceType> pluginTypes = loadedPlugins.get(pluginName);

        if (pluginTypes == null) {
            return null;
        }

        return pluginTypes.values();
    }

    /**
     * Returns the name of the plugin that deployed the given content source type. <code>null</code> will be returned if
     * unknown.
     *
     * @param  type the name of the plugin that deployed this type is to be returned
     *
     * @return name of the plugin
     */
    public synchronized String getPluginNameFromContentSourceType(ContentSourceType type) {
        Set<String> pluginNames = loadedPlugins.keySet();
        for (String pluginName : pluginNames) {
            if (loadedPlugins.get(pluginName).containsValue(type)) {
                return pluginName;
            }
        }

        return null;
    }
}