package alien4cloud.tosca.container;

import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import alien4cloud.csar.services.ICsarDependencyLoader;
import alien4cloud.exception.NotFoundException;
import alien4cloud.model.components.CSARDependency;
import alien4cloud.utils.VersionUtil;

import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

@Getter
@Setter
@Slf4j
@SuppressWarnings("PMD.UnusedPrivateField")
public class ToscaTypeLoader {

    private Map<CSARDependency, Set<String>> dependenciesMap = Maps.newHashMap();

    private Map<String, Integer> typeUsagesMap = Maps.newHashMap();

    private ICsarDependencyLoader dependencyLoader;

    public ToscaTypeLoader(ICsarDependencyLoader dependencyLoader) {
        this.dependencyLoader = dependencyLoader;
    }

    public Set<CSARDependency> getLoadedDependencies() {
        return dependenciesMap.keySet();
    }

    /**
     * Try to unload the given type
     * 
     * @param type name of the type
     */
    public void unloadType(String type) {
        if (log.isDebugEnabled()) {
            log.debug("Unload type [" + type + "]");
        }
        Integer currentUsageCount = typeUsagesMap.get(type);
        if (currentUsageCount != null) {
            if (currentUsageCount <= 1) {
                // Not used anymore in the topology
                typeUsagesMap.remove(type);
                Iterator<Map.Entry<CSARDependency, Set<String>>> dependencyIterator = dependenciesMap.entrySet().iterator();
                while (dependencyIterator.hasNext()) {
                    Map.Entry<CSARDependency, Set<String>> entry = dependencyIterator.next();
                    entry.getValue().remove(type);
                    if (entry.getValue().isEmpty()) {
                        if (log.isDebugEnabled()) {
                            log.debug("Dependency is not used anymore and will be removed [" + entry.getKey() + "] due to unload of type [" + type + "]");
                            log.debug("Type usage [" + typeUsagesMap + "]");
                            log.debug("Dependencies usage [" + dependenciesMap + "]");
                        }
                        dependencyIterator.remove();
                    }
                }
            } else {
                // Still used
                typeUsagesMap.put(type, currentUsageCount - 1);
            }
        } else {
            log.error("Unload a type which is not used [" + type + "]");
            throw new NotFoundException("Remove a type which is not used [" + type + "]");
        }
        if (log.isDebugEnabled()) {
            log.debug("Type usage [" + typeUsagesMap + "]");
            log.debug("Dependencies usage [" + dependenciesMap + "]");
        }
    }

    private CSARDependency getDependencyWithName(String archiveName) {
        Iterator<Map.Entry<CSARDependency, Set<String>>> dependencyIterator = dependenciesMap.entrySet().iterator();
        while (dependencyIterator.hasNext()) {
            Map.Entry<CSARDependency, Set<String>> entry = dependencyIterator.next();
            if (entry.getKey().getName().equals(archiveName)) {
                return entry.getKey();
            }
        }
        return null;
    }

    private void addNewDependency(CSARDependency dependency, String type) {
        CSARDependency currentDependency = getDependencyWithName(dependency.getName());
        // New dependency that never exists before
        if (currentDependency == null) {
            dependenciesMap.put(dependency, Sets.newHashSet(type));
            return;
        }
        // Dependency that already existed
        if (VersionUtil.compare(dependency.getVersion(), currentDependency.getVersion()) > 0) {
            // The new version is more recent, we will override with new version with warning
            Set<String> typesLoadedByConflictingArchive = dependenciesMap.remove(currentDependency);
            dependenciesMap.put(dependency, typesLoadedByConflictingArchive);
            log.warn("Version conflicting for archive [" + dependency.getName() + "] override current version [" + currentDependency.getVersion() + "] with ["
                    + dependency.getVersion() + "]");
        } else {
            log.warn("Version conflicting for archive [" + dependency.getName() + "] do not override and use current version ["
                    + currentDependency.getVersion()
                    + "] ignore old version [" + dependency.getVersion() + "]");
            dependenciesMap.get(currentDependency).add(type);
        }
    }

    /**
     * Add directDependency for the given type from the given archive.
     * If the directDependency is of an deprecated version ( < than found in the existing dependencies), ignore it.
     * If the directDependency is of a more recent version, force the dependency of the topology to the more recent one
     * 
     * @param directDependency the direct directDependency to load the type
     * @param type name of the type
     */
    public void loadType(String type, CSARDependency directDependency) {
        if (log.isDebugEnabled()) {
            log.debug("Load type [" + type + "] from dependency [" + directDependency + "]");
        }
        Set<String> typesLoadedByDependency = dependenciesMap.get(directDependency);
        // Increment usage count
        Integer currentUsageCount = typeUsagesMap.get(type);
        if (currentUsageCount == null) {
            currentUsageCount = Integer.valueOf(0);
        }
        typeUsagesMap.put(type, currentUsageCount + 1);
        if (typesLoadedByDependency != null) {
            typesLoadedByDependency.add(type);
        } else {
            addNewDependency(directDependency, type);
        }
        Set<CSARDependency> transitiveDependencies = dependencyLoader.getDependencies(directDependency.getName(), directDependency.getVersion());
        for (CSARDependency transitiveDependency : transitiveDependencies) {
            Set<String> transitiveTypesLoadedByDependency = dependenciesMap.get(transitiveDependency);
            if (transitiveTypesLoadedByDependency == null) {
                addNewDependency(transitiveDependency, type);
            } else {
                transitiveTypesLoadedByDependency.add(type);
            }
        }
        if (log.isDebugEnabled()) {
            log.debug("Type usage [" + typeUsagesMap + "]");
            log.debug("Dependencies usage [" + dependenciesMap + "]");
        }
    }
}

@@ -3997,32 +3997,87 @@
 entDependency);%0A
+            typesLoadedByConflictingArchive.add(type);%0A
             depe

Fix a bug in topology dependencies management.