package org.alien4cloud.tosca.editor.processors.nodetemplate;

import static alien4cloud.utils.AlienUtils.safe;

import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.inject.Inject;

import org.alien4cloud.tosca.editor.operations.nodetemplate.DeleteNodeOperation;
import org.alien4cloud.tosca.editor.processors.IEditorCommitableProcessor;
import org.alien4cloud.tosca.model.Csar;
import org.alien4cloud.tosca.model.definitions.DeploymentArtifact;
import org.alien4cloud.tosca.model.templates.NodeTemplate;
import org.alien4cloud.tosca.model.templates.PolicyTemplate;
import org.alien4cloud.tosca.model.templates.RelationshipTemplate;
import org.alien4cloud.tosca.model.templates.SubstitutionTarget;
import org.alien4cloud.tosca.model.templates.Topology;
import org.springframework.stereotype.Component;

import com.google.common.collect.Lists;

import alien4cloud.component.repository.ArtifactRepositoryConstants;
import alien4cloud.component.repository.IFileRepository;
import alien4cloud.paas.wf.WorkflowsBuilderService;
import alien4cloud.topology.TopologyService;
import org.alien4cloud.tosca.utils.TopologyUtils;
import lombok.extern.slf4j.Slf4j;

/**
 * Process a {@link DeleteNodeOperation}
 */
@Slf4j
@Component
public class DeleteNodeProcessor extends AbstractNodeProcessor<DeleteNodeOperation> implements IEditorCommitableProcessor<DeleteNodeOperation> {
    @Inject
    private TopologyService topologyService;
    @Inject
    private IFileRepository artifactRepository;
    @Inject
    private WorkflowsBuilderService workflowBuilderService;

    @Override
    protected void processNodeOperation(Csar csar, Topology topology, DeleteNodeOperation operation, NodeTemplate template) {
        Map<String, NodeTemplate> nodeTemplates = TopologyUtils.getNodeTemplates(topology);

        // Prepare to cleanup files (store artifacts reference in the operation and process deletion on before commit operation).
        Map<String, DeploymentArtifact> artifacts = template.getArtifacts();
        operation.setArtifacts(artifacts);

        List<String> typesTobeUnloaded = Lists.newArrayList();
        // Clean up dependencies of the topology
        removeRelationShipReferences(operation.getNodeName(), csar, topology, typesTobeUnloaded);
        topologyService.unloadType(topology, typesTobeUnloaded.toArray(new String[typesTobeUnloaded.size()]));

        // Cleanup from policies
        removeNodeFromPolicies(operation.getNodeName(), topology);

        nodeTemplates.remove(operation.getNodeName());
        removeOutputs(operation.getNodeName(), topology);
        if (topology.getSubstitutionMapping() != null) {
            removeNodeTemplateSubstitutionTargetMapEntry(operation.getNodeName(), topology.getSubstitutionMapping().getCapabilities());
            removeNodeTemplateSubstitutionTargetMapEntry(operation.getNodeName(), topology.getSubstitutionMapping().getRequirements());
        }

        // group members removal
        TopologyUtils.updateGroupMembers(topology, template, operation.getNodeName(), null);
        // update the workflows
        workflowBuilderService.removeNode(topology, csar, operation.getNodeName());
        log.debug("Removed node template [ {} ] from the topology [ {} ] .", operation.getNodeName(), topology.getId());
    }

    /**
     * Remove a nodeTemplate outputs in a topology
     */
    private void removeOutputs(String nodeTemplateName, Topology topology) {
        if (topology.getOutputProperties() != null) {
            topology.getOutputProperties().remove(nodeTemplateName);
        }
        if (topology.getOutputAttributes() != null) {
            topology.getOutputAttributes().remove(nodeTemplateName);
        }
        if (topology.getOutputCapabilityProperties() != null) {
            topology.getOutputCapabilityProperties().remove(nodeTemplateName);
        }
    }

    private void removeNodeTemplateSubstitutionTargetMapEntry(String nodeTemplateName, Map<String, SubstitutionTarget> substitutionTargets) {
        if (substitutionTargets == null) {
            return;
        }
        Iterator<Map.Entry<String, SubstitutionTarget>> capabilities = substitutionTargets.entrySet().iterator();
        while (capabilities.hasNext()) {
            Map.Entry<String, SubstitutionTarget> e = capabilities.next();
            if (e.getValue().getNodeTemplateName().equals(nodeTemplateName)) {
                capabilities.remove();
            }
        }
    }

    /**
     * Removes all relationships connected to the removed node (either as source or target)
     *
     * @param removedNode The name of the removed node
     * @param topology The topology in which to remove the references.
     * @param typesTobeUnloaded List of types to remove from the topology (when relationships are removed)
     */
    private void removeRelationShipReferences(String removedNode, Csar csar, Topology topology, List<String> typesTobeUnloaded) {
        List<String> relationshipsToRemove = Lists.newArrayList();

        for (NodeTemplate nodeTemplate : safe(topology.getNodeTemplates()).values()) {
            if (removedNode.equals(nodeTemplate.getName())) {
                // remove all relationships
                for (Entry<String, RelationshipTemplate> relationshipTemplateEntry : safe(nodeTemplate.getRelationships()).entrySet()) {
                    typesTobeUnloaded.add(relationshipTemplateEntry.getValue().getType());
                    workflowBuilderService.removeRelationship(topology, csar, nodeTemplate.getName(),
                            relationshipTemplateEntry.getKey(), relationshipTemplateEntry.getValue());
                }
            } else {
                relationshipsToRemove.clear(); // This is for later removal
                for (RelationshipTemplate relationshipTemplate : safe(nodeTemplate.getRelationships()).values()) {
                    if (removedNode.equals(relationshipTemplate.getTarget())) {
                        relationshipsToRemove.add(relationshipTemplate.getName());
                        typesTobeUnloaded.add(relationshipTemplate.getType());
                        workflowBuilderService.removeRelationship(topology, csar, nodeTemplate.getName(),
                                relationshipTemplate.getName(), relationshipTemplate);
                    }
                }
                // remove the relationship from the node's relationship map.
                for (String relationshipToRemove : relationshipsToRemove) {
                    nodeTemplate.getRelationships().remove(relationshipToRemove);
                }
            }
        }
    }

    private void removeNodeFromPolicies(String removedNode, Topology topology) {
        for (PolicyTemplate policyTemplate : safe(topology.getPolicies()).values()) {
            policyTemplate.getTargets().remove(removedNode);
        }
    }

    @Override
    public void beforeCommit(DeleteNodeOperation operation) {
        for (Map.Entry<String, DeploymentArtifact> artifactEntry : safe(operation.getArtifacts()).entrySet()) {
            DeploymentArtifact artifact = artifactEntry.getValue();
            if (ArtifactRepositoryConstants.ALIEN_ARTIFACT_REPOSITORY.equals(artifact.getArtifactRepository())) {
                this.artifactRepository.deleteFile(artifact.getArtifactRef());
            }
        }
    }
}
@@ -4984,76 +4984,8 @@
 ) %7B%0A
-        List%3CString%3E relationshipsToRemove = Lists.newArrayList();%0A%0A
     
@@ -5083,649 +5083,123 @@
     
-if (removedNode.equals(nodeTemplate.getName())) %7B%0A                // remove all relationships%0A                for (Entry%3CString, RelationshipTemplate%3E relationshipTemplateEntry : safe(nodeTemplate.getRelationships()).entrySet()) %7B%0A                    typesTobeUnloaded.add(relationshipTemplateEntry.getV
+for (Iterator%3CRelationshipTemplate%3E i = safe(nodeTemplate.getRelationships()).v
 alue
+s
 ().
-getType());%0A                    workflowBuilderService.removeRelationship(topology, csar, nodeTemplate.getName(),%0A                            relationshipTemplateEntry.getKey(), relationshipTemplateEntry.getValue());%0A                %7D%0A            %7D else %7B%0A                relationshipsToRemove.clear(); // This is for later removal
+iterator(); i.hasNext() ;) %7B
 %0A   
@@ -5211,21 +5211,16 @@
         
-for (
 Relation
@@ -5257,64 +5257,21 @@
 ate 
-: safe(nodeTemplate.getRelationships()).values()) %7B%0A    
+= i.next();%0A%0A
     
@@ -5346,95 +5346,8 @@
 ) %7B%0A
-                        relationshipsToRemove.add(relationshipTemplate.getName());%0A    
     
@@ -5413,28 +5413,25 @@
 getType());%0A
-    
+%0A
             
@@ -5548,20 +5548,16 @@
         
-    
 relation
@@ -5607,29 +5607,8 @@
 e);%0A
-                    %7D
 %0A   
@@ -5607,39 +5607,32 @@
 e);%0A%0A           
-     %7D%0A
                 
@@ -5628,231 +5628,17 @@
     
-       // remove the relationship from the node's relationship map.%0A                for (String relationshipToRemove : relationshipsToRemove) %7B%0A                    nodeTemplate.getRelationships().remove(relationshipToRemove
+i.remove(
 );%0A 

Correct workflow cleanup with 2 relations to the same node