/*
 * Copyright (c) 2014 - 2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.ferstl.depgraph.dependency;

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Set;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
import com.github.ferstl.depgraph.graph.GraphBuilder;

import static java.lang.Math.max;
import static java.util.EnumSet.allOf;


/**
 * A node visitor that creates edges between the visited nodes using a {@link GraphBuilder}. This class implements the
 * {@code DependencyNodeVisitor} interfaces for dependency trees and dependency graphs and adapts the different node
 * instances using {@link DependencyNode}.
 */
class GraphBuildingVisitor implements org.apache.maven.shared.dependency.graph.traversal.DependencyNodeVisitor, org.apache.maven.shared.dependency.tree.traversal.DependencyNodeVisitor {

  private final GraphBuilder<DependencyNode> graphBuilder;
  private final Deque<DependencyNode> nodeStack;
  private final ArtifactFilter globalFilter;
  private final ArtifactFilter targetFilter;
  private final Set<NodeResolution> includedResolutions;

  /**
   * Max depth of the graph. Nodes deeper than this depth will be cut off from the graph.
   */
  private int cutOffDepth = 0;

  GraphBuildingVisitor(GraphBuilder<DependencyNode> graphBuilder, ArtifactFilter globalFilter, ArtifactFilter targetFilter, Set<NodeResolution> includedResolutions) {
    this.graphBuilder = graphBuilder;
    this.nodeStack = new ArrayDeque<>();
    this.globalFilter = globalFilter;
    this.targetFilter = targetFilter;
    this.includedResolutions = includedResolutions;
  }

  GraphBuildingVisitor(GraphBuilder<DependencyNode> graphBuilder, ArtifactFilter targetFilter) {
    this(graphBuilder, DoNothingArtifactFilter.INSTANCE, targetFilter, allOf(NodeResolution.class));
  }

  @Override
  public boolean visit(org.apache.maven.shared.dependency.graph.DependencyNode node) {
    return internalVisit2(new DependencyNode(node));
  }

  @Override
  public boolean endVisit(org.apache.maven.shared.dependency.graph.DependencyNode node) {
    return internalEndVisit2(new DependencyNode(node));
  }

  @Override
  public boolean visit(org.apache.maven.shared.dependency.tree.DependencyNode node) {
    return internalVisit2(new DependencyNode(node));
  }

  @Override
  public boolean endVisit(org.apache.maven.shared.dependency.tree.DependencyNode node) {
    return internalEndVisit2(new DependencyNode(node));
  }

  private boolean internalVisit(DependencyNode node) {
    DependencyNode currentParent = this.nodeStack.peek();

    if (this.globalFilter.include(node.getArtifact()) && leadsToTargetDependency(node)) {
      if (currentParent != null && this.includedResolutions.contains(node.getResolution())) {
        mergeWithExisting(node);

        this.graphBuilder.addEdge(currentParent, node);
      }

      this.nodeStack.push(node);

      return true;
    }

    return false;
  }

  private boolean internalVisit2(DependencyNode node) {
    if (isIncluded(node)) {
      this.nodeStack.push(node);

      if (this.targetFilter.include(node.getArtifact())) {
        this.cutOffDepth = max(this.cutOffDepth, this.nodeStack.size());
      }

      return true;
    }

    return false;
  }

  private boolean internalEndVisit2(DependencyNode node) {
    if (isIncluded(node)) {
      this.nodeStack.pop();
      DependencyNode currentParent = this.nodeStack.peek();


      if (currentParent == null) {
        // We are at the root element of the graph.
        this.cutOffDepth = 0;
      } else if (this.nodeStack.size() < this.cutOffDepth) {
        this.cutOffDepth = this.nodeStack.size();
        mergeWithExisting(node);
        this.graphBuilder.addEdge(currentParent, node);
      }

      return true;
    }

    return false;
  }

  private boolean isIncluded(DependencyNode node) {
    return this.globalFilter.include(node.getArtifact()) && this.includedResolutions.contains(node.getResolution());
  }

  private void mergeWithExisting(DependencyNode node) {
    DependencyNode effectiveNode = this.graphBuilder.getEffectiveNode(node);
    node.merge(effectiveNode);
  }

  private boolean leadsToTargetDependency(DependencyNode node) {
    if (this.targetFilter.include(node.getArtifact())) {
      return true;
    }

    for (DependencyNode c : node.getChildren()) {
      if (leadsToTargetDependency(c)) {
        return true;
      }
    }

    return false;
  }

  private boolean internalEndVisit(DependencyNode node) {
    if (this.globalFilter.include(node.getArtifact()) && leadsToTargetDependency(node)) {
      this.nodeStack.pop();
    }

    return true;
  }

  private enum DoNothingArtifactFilter implements ArtifactFilter {
    INSTANCE;

    @Override
    public boolean include(Artifact artifact) {
      return true;
    }

  }
}

@@ -3600,32 +3600,33 @@
 node) %7B%0A    if (
+!
 isIncluded(node)
@@ -3623,32 +3623,57 @@
 uded(node)) %7B%0A  
+    return false;%0A    %7D%0A%0A
     this.nodeSta
@@ -3684,26 +3684,24 @@
 ush(node);%0A%0A
-  
     if (this
@@ -3741,26 +3741,24 @@
 ifact())) %7B%0A
-  
       this.c
@@ -3816,31 +3816,27 @@
 ize());%0A    
-  
 %7D%0A%0A
-  
     return t
@@ -3844,38 +3844,14 @@
 ue;%0A
-    %7D%0A%0A    return false;%0A
   %7D%0A%0A
+%0A
   pr
@@ -3913,16 +3913,17 @@
     if (
+!
 isInclud
@@ -3928,32 +3928,57 @@
 uded(node)) %7B%0A  
+    return false;%0A    %7D%0A%0A
     this.nodeSta
@@ -3983,26 +3983,25 @@
 tack.pop();%0A
-  
+%0A
     Dependen
@@ -4050,143 +4050,15 @@
 ();%0A
-%0A%0A  
     if 
-(currentParent == null) %7B%0A        // We are at the root element of the graph.%0A        this.cutOffDepth = 0;%0A      %7D else if 
 (thi
@@ -4094,26 +4094,24 @@
 OffDepth) %7B%0A
-  
       this.c
@@ -4145,16 +4145,52 @@
 .size();
+%0A%0A      if (currentParent != null) %7B
 %0A       
@@ -4278,36 +4278,16 @@
 %0A      %7D
-%0A%0A      return true;
 %0A    %7D%0A%0A
@@ -4289,36 +4289,35 @@
   %7D%0A%0A    return 
-fals
+tru
 e;%0A  %7D%0A%0A  privat

Simplify visitor methods