/*
 * Copyright 2006-2011 Dave Griffith, Bas Leijdekkers
 *
 * 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.siyeh.ig.packaging;

import com.intellij.analysis.AnalysisScope;
import com.intellij.codeInspection.CommonProblemDescriptor;
import com.intellij.codeInspection.GlobalInspectionContext;
import com.intellij.codeInspection.InspectionManager;
import com.intellij.codeInspection.reference.RefClass;
import com.intellij.codeInspection.reference.RefEntity;
import com.intellij.codeInspection.reference.RefJavaUtil;
import com.intellij.codeInspection.reference.RefPackage;
import com.intellij.psi.PsiClass;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.BaseGlobalInspection;
import com.siyeh.ig.dependency.DependencyUtils;
import com.siyeh.ig.psiutils.ClassUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class DisjointPackageInspection extends BaseGlobalInspection {

  @NotNull
  @Override
  public String getDisplayName() {
    return InspectionGadgetsBundle.message("disjoint.package.display.name");
  }

  @Override
  @Nullable
  public CommonProblemDescriptor[] checkElement(
    @NotNull RefEntity refEntity, @NotNull AnalysisScope analysisScope,
    @NotNull InspectionManager inspectionManager,
    @NotNull GlobalInspectionContext globalInspectionContext) {
    if (!(refEntity instanceof RefPackage)) {
      return null;
    }
    final RefPackage refPackage = (RefPackage)refEntity;
    final List<RefEntity> children = refPackage.getChildren();
    final Set<RefClass> childClasses = new HashSet<>();
    for (RefEntity child : children) {
      if (!(child instanceof RefClass)) {
        continue;
      }
      final PsiClass psiClass = ((RefClass)child).getUastElement().getJavaPsi();
      if (ClassUtils.isInnerClass(psiClass)) {
        continue;
      }
      childClasses.add((RefClass)child);
    }
    if (childClasses.isEmpty()) {
      return null;
    }
    final Set<Set<RefClass>> components =
      createComponents(refPackage, childClasses);
    if (components.size() == 1) {
      return null;
    }
    final String errorString = InspectionGadgetsBundle
      .message("disjoint.package.problem.descriptor",
               refPackage.getQualifiedName(),
               Integer.valueOf(components.size()));

    return new CommonProblemDescriptor[]{
      inspectionManager.createProblemDescriptor(errorString)};
  }

  private static Set<Set<RefClass>> createComponents(
    RefPackage aPackage, Set<RefClass> classes) {
    final Set<RefClass> allClasses = new HashSet<>(classes);
    final Set<Set<RefClass>> out = new HashSet<>();
    while (!allClasses.isEmpty()) {
      final RefClass seed = allClasses.iterator().next();
      allClasses.remove(seed);
      final Set<RefClass> currentComponent = new HashSet<>();
      currentComponent.add(seed);
      final List<RefClass> pendingClasses = new ArrayList<>();
      pendingClasses.add(seed);
      while (!pendingClasses.isEmpty()) {
        final RefClass classToProcess = pendingClasses.remove(0);
        final Set<RefClass> relatedClasses =
          getRelatedClasses(aPackage, classToProcess);
        for (RefClass relatedClass : relatedClasses) {
          if (!currentComponent.contains(relatedClass) &&
              !pendingClasses.contains(relatedClass)) {
            currentComponent.add(relatedClass);
            pendingClasses.add(relatedClass);
            allClasses.remove(relatedClass);
          }
        }
      }
      out.add(currentComponent);
    }
    return out;
  }

  private static Set<RefClass> getRelatedClasses(RefPackage aPackage,
                                                 RefClass classToProcess) {
    final Set<RefClass> out = new HashSet<>();
    final Set<RefClass> dependencies =
      DependencyUtils.calculateDependenciesForClass(classToProcess);
    for (RefClass dependency : dependencies) {
      if (packageContainsClass(aPackage, dependency)) {
        out.add(dependency);
      }
    }

    final Set<RefClass> dependents =
      DependencyUtils.calculateDependentsForClass(classToProcess);
    for (RefClass dependent : dependents) {
      if (packageContainsClass(aPackage, dependent)) {
        out.add(dependent);
      }
    }
    return out;
  }

  private static boolean packageContainsClass(RefPackage aPackage,
                                              RefClass aClass) {
    return aPackage.equals(RefJavaUtil.getPackage(aClass));
  }
}

@@ -1476,16 +1476,52 @@
 til.Set;
+%0Aimport java.util.stream.Collectors;
 %0A%0Apublic
@@ -2126,32 +2126,34 @@
 nal 
-Lis
+Se
 t%3CRef
-Entity
+Class
 %3E child
-ren
+Classes
  = r
@@ -2157,24 +2157,31 @@
 = refPackage
+%0A      
 .getChildren
@@ -2186,117 +2186,48 @@
 en()
-;
 %0A    
-final Set%3CRefClass%3E childClasses = new HashSet%3C%3E
+  .stream
 ()
-;
 %0A    
-for (RefEntity 
+  .filter(
 child 
-: children) %7B%0A      if (!(
+-%3E 
 chil
@@ -2252,238 +2252,81 @@
 ass)
-) %7B
 %0A      
-  continue;%0A      %7D%0A      final PsiClass psiClass = ((RefClass)child).getUastElement().getJavaPsi();%0A      if (ClassUtils.isInnerClass(psiClass)) %7B%0A        continue;%0A      %7D%0A      childClasses.add((RefClass)child);%0A    %7D
+.map(child -%3E (RefClass)child)%0A      .collect(Collectors.toSet());
 %0A   

remove redundant psi resolve