/*
 * Copyright 2000-2012 JetBrains s.r.o.
 *
 * 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 git4idea.roots;

import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.vfs.VirtualFile;
import git4idea.GitUtil;
import git4idea.PlatformFacade;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

/**
 * <p>
 *   Scans the file system looking for Git roots, which contain the Project or its parts,
 *   and returns the information enclosed to the {@link GitRootDetectInfo}.
 *   The main part of the information are Git roots which will be proposed to the user to be added as VCS roots.
 * </p>
 * <p>
 *   Linked sources are not scanned. User should add Git roots for them explicitly.
 * </p>
 *
 * @author Kirill Likhodedov
 */
public class GitRootDetector {

  private static final int MAXIMUM_SCAN_DEPTH = 2;

  @NotNull private final Project myProject;
  @NotNull private final PlatformFacade myPlatformFacade;

  public GitRootDetector(@NotNull Project project, @NotNull PlatformFacade platformFacade) {
    myProject = project;
    myPlatformFacade = platformFacade;
  }

  public GitRootDetectInfo detect() {
    VirtualFile projectDir = myProject.getBaseDir();

    if (projectDir == null) {
      return new GitRootDetectInfo(Collections.<VirtualFile>emptyList(), false, false);
    }

    final Set<VirtualFile> roots = scanForRootsInsideDir(projectDir);
    roots.addAll(scanForRootsInContentRoots());

    if (roots.contains(projectDir)) {
      return new GitRootDetectInfo(roots, true, false);
    }

    VirtualFile rootAbove = scanForSingleRootAboveDir(projectDir);
    if (rootAbove != null) {
      roots.add(rootAbove);
      return new GitRootDetectInfo(roots, true, true);
    }
    return new GitRootDetectInfo(roots, false, false);
  }

  private Set<VirtualFile> scanForRootsInContentRoots() {
    Set<VirtualFile> gitRoots = new HashSet<VirtualFile>();
    VirtualFile[] roots = myPlatformFacade.getProjectRootManager(myProject).getContentRoots();
    for (VirtualFile contentRoot : roots) {
      Set<VirtualFile> rootsInsideRoot = scanForRootsInsideDir(contentRoot);
      if (!rootsInsideRoot.contains(contentRoot)) {
        VirtualFile rootAbove = scanForSingleRootAboveDir(contentRoot);
        if (rootAbove != null) {
          rootsInsideRoot.add(rootAbove);
        }
      }
      gitRoots.addAll(rootsInsideRoot);
    }
    return gitRoots;
  }

  @NotNull
  private Set<VirtualFile> scanForRootsInsideDir(@NotNull final VirtualFile dir, final int depth) {
    final Set<VirtualFile> roots = new HashSet<VirtualFile>();
    if (depth > MAXIMUM_SCAN_DEPTH) {
      // performance optimization via limitation: don't scan deep though the whole VFS, 2 levels under a content root is enough
      return roots;
    }

    myPlatformFacade.runReadAction(new Computable<Object>() {
      @Nullable @Override public Object compute() {
        if (myProject.isDisposed() || !dir.isDirectory()) {
          return null;
        }
        if (hasGitDir(dir)) {
          roots.add(dir);
        }
        for (VirtualFile child : dir.getChildren()) {
          roots.addAll(scanForRootsInsideDir(child, depth + 1));
        }
        return null;
      }
    });

    return roots;
  }

  @NotNull
  private Set<VirtualFile> scanForRootsInsideDir(@NotNull VirtualFile projectDir) {
    return scanForRootsInsideDir(projectDir, 0);
  }

  @Nullable
  private VirtualFile scanForSingleRootAboveDir(@NotNull final VirtualFile projectDir) {
    return myPlatformFacade.runReadAction(new Computable<VirtualFile>() {
      @Nullable @Override public VirtualFile compute() {
        if (myProject.isDisposed()) {
          return null;
        }

        VirtualFile par = projectDir.getParent();
        while (par != null) {
          if (hasGitDir(par)) {
            return par;
          }
          par = par.getParent();
        }
        return null;
      }
    });
  }

  private static boolean hasGitDir(@NotNull VirtualFile dir) {
    VirtualFile gitDir = dir.findChild(GitUtil.DOT_GIT);
    return gitDir != null && gitDir.exists();
  }

}

@@ -668,53 +668,8 @@
 ct;%0A
-import com.intellij.openapi.util.Computable;%0A
 impo
@@ -3389,126 +3389,8 @@
  %7D%0A%0A
-    myPlatformFacade.runReadAction(new Computable%3CObject%3E() %7B%0A      @Nullable @Override public Object compute() %7B%0A    
     
@@ -3451,39 +3451,28 @@
     
-    
 return 
-null;%0A        %7D%0A    
+roots;%0A    %7D%0A
     
@@ -3491,36 +3491,32 @@
 r(dir)) %7B%0A      
-    
 roots.add(dir);%0A
@@ -3515,34 +3515,26 @@
 d(dir);%0A    
-   
- 
 %7D%0A
-    
     for (Vir
@@ -3573,28 +3573,24 @@
 ()) %7B%0A      
-  
-  
 roots.addAll
@@ -3636,56 +3636,14 @@
 ));%0A
-    
     %7D%0A
-        return null;%0A      %7D%0A    %7D);%0A%0A
     
@@ -3738,32 +3738,25 @@
 VirtualFile 
-projectD
+d
 ir) %7B%0A    re
@@ -3782,24 +3782,17 @@
 sideDir(
-projectD
+d
 ir, 0);%0A
@@ -3887,157 +3887,15 @@
 ile 
-projectDir) %7B%0A    return myPlatformFacade.runReadAction(new Computable%3CVirtualFile%3E() %7B%0A      @Nullable @Override public VirtualFile compute() %7B%0A    
+dir) %7B%0A
     
@@ -3926,28 +3926,24 @@
 ()) %7B%0A      
-    
 return null;
@@ -3947,27 +3947,19 @@
 ll;%0A    
-   
- 
 %7D%0A%0A
-    
     Virt
@@ -3972,24 +3972,17 @@
 e par = 
-projectD
+d
 ir.getPa
@@ -3985,28 +3985,24 @@
 etParent();%0A
-    
     while (p
@@ -4017,28 +4017,24 @@
 ll) %7B%0A      
- 
-   
 if (hasGitDi
@@ -4039,28 +4039,24 @@
 Dir(par)) %7B%0A
-    
         retu
@@ -4073,24 +4073,16 @@
     
-    %7D%0A  
+%7D%0A
       
-  
 par 
@@ -4100,20 +4100,16 @@
 rent();%0A
-    
     %7D%0A  
@@ -4106,28 +4106,24 @@
 ;%0A    %7D%0A    
- 
-   
 return null;
@@ -4127,24 +4127,8 @@
 ll;%0A
-      %7D%0A    %7D);%0A
   %7D%0A

remove read action access, because it may lead to live lock during project startup. Probably one of causes of IDEA-87656.