/*
 * 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.server.content.metadata;

import java.util.HashSet;
import java.util.Set;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.rhq.core.domain.configuration.definition.ConfigurationDefinition;
import org.rhq.core.domain.content.ContentSource;
import org.rhq.core.domain.content.ContentSourceType;
import org.rhq.enterprise.server.RHQConstants;
import org.rhq.enterprise.server.configuration.metadata.ConfigurationMetadataManagerLocal;

/**
 * Used to work with metadata defining content source types.
 */
@Stateless
public class ContentSourceMetadataManagerBean implements ContentSourceMetadataManagerLocal {
    private final Log log = LogFactory.getLog(ContentSourceMetadataManagerBean.class);

    @PersistenceContext(unitName = RHQConstants.PERSISTENCE_UNIT_NAME)
    private EntityManager entityManager;

    @EJB
    private ConfigurationMetadataManagerLocal configurationMetadataManager;

    public void registerTypes(Set<ContentSourceType> typesToRegister) {
        Set<ContentSourceType> existingTypes = new HashSet<ContentSourceType>();

        Query q = entityManager.createNamedQuery(ContentSourceType.QUERY_FIND_ALL);
        existingTypes.addAll(q.getResultList());

        Set<ContentSourceType> typesToPersist = missingInFirstSet(existingTypes, typesToRegister);
        Set<ContentSourceType> typesToDelete = missingInFirstSet(typesToRegister, existingTypes);
        Set<ContentSourceType> typesToUpdate = intersection(existingTypes, typesToRegister); // order of params is important

        // persist any types that need to be registered but do not currently exist
        for (ContentSourceType typeToPersist : typesToPersist) {
            entityManager.persist(typeToPersist);
        }

        // Remove any types that currently exist but are not in the types to register.
        // Note that we only remove types that have no content sources associated with them.
        // If someone removes a plugin but fails to delete content sources associate with their types,
        // then we spit out a warning but leave the type definition in the database.
        for (ContentSourceType typeToDelete : typesToDelete) {
            Set<ContentSource> attachedSources = typeToDelete.getContentSources();
            if ((attachedSources == null) || (attachedSources.size() == 0)) {
                entityManager.remove(typeToDelete);
            } else {
                log.warn(typeToDelete.toString() + " is no longer supported by any deployed plugin, "
                    + "but it still has some content sources associated with it. "
                    + "Those content sources will no longer work.");
            }
        }

        // we now have to merge existing types with their new definitions
        for (ContentSourceType typeToUpdate : typesToUpdate) {
            for (ContentSourceType typeToRegister : typesToRegister) {
                if (typeToRegister.equals(typeToUpdate)) {
                    typeToUpdate.setDisplayName(typeToRegister.getDisplayName());
                    typeToUpdate.setDescription(typeToRegister.getDescription());
                    typeToUpdate.setContentSourceApiClass(typeToRegister.getContentSourceApiClass());
                    typeToUpdate.setDefaultSyncSchedule(typeToRegister.getDefaultSyncSchedule());
                    typeToUpdate.setDefaultLazyLoad(typeToRegister.isDefaultLazyLoad());
                    typeToUpdate.setDefaultDownloadMode(typeToRegister.getDefaultDownloadMode());
                    updateConfigurationDefinition(typeToRegister, typeToUpdate);
                    break;
                }
            }
        }

        return;
    }

    private void updateConfigurationDefinition(ContentSourceType newType, ContentSourceType existingType) {
        ConfigurationDefinition newConfigDef = newType.getContentSourceConfigurationDefinition();
        ConfigurationDefinition existingConfigDef = existingType.getContentSourceConfigurationDefinition();

        if (newConfigDef != null) {
            if (existingConfigDef == null) {
                // everything is new
                entityManager.persist(newConfigDef);
                existingType.setContentSourceConfigurationDefinition(newConfigDef);
            } else {
                // both new and existing had some kind of configuration, update the existing to match the new
                configurationMetadataManager.updateConfigurationDefinition(newConfigDef, existingConfigDef);
            }
        } else {
            // the new config is null -> remove the existing config
            if (existingConfigDef != null) {
                existingType.setContentSourceConfigurationDefinition(null);
                entityManager.remove(existingConfigDef);
            }
        }
        return;
    }

    /**
     * Return a set containing those element that are in reference, but not in first. Both input sets are not modified
     *
     * @param  first
     * @param  reference
     *
     * @return
     */
    private <T> Set<T> missingInFirstSet(Set<T> first, Set<T> reference) {
        Set<T> result = new HashSet<T>();

        if (reference != null) {
            // First collection is null -> everything is missing
            if (first == null) {
                result.addAll(reference);
                return result;
            }

            // else loop over the set and sort out the right items.
            for (T item : reference) {
                if (!first.contains(item)) {
                    result.add(item);
                }
            }
        }

        return result;
    }

    /**
     * Return a new Set with elements both in first and second passed collection.
     *
     * @param  first  First set
     * @param  second Second set
     *
     * @return a new set (depending on input type) with elements in first and second
     */
    private <T> Set<T> intersection(Set<T> first, Set<T> second) {
        Set<T> result = new HashSet<T>();

        if ((first != null) && (second != null)) {
            result.addAll(first);
            result.retainAll(second);
        }

        return result;
    }
}