package org.jetbrains.plugins.scaladev;

import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.AbstractProjectComponent;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.*;
import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.roots.ui.configuration.LibraryJavaSourceRootDetector;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.openapi.vfs.VirtualFileVisitor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.scala.project.notification.source.AttachSourcesUtil;

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

public class IdeaSourcesAttach extends AbstractProjectComponent {
    protected IdeaSourcesAttach(Project project) {
        super(project);
    }

    private Logger LOG = Logger.getInstance(this.getClass());

    public final static String NAME = "IdeaSourcesAttach";

    @NotNull
    @Override
    public String getComponentName() {
        return NAME;
    }

    @Override
    public void projectOpened() {
        try {
            attachIdeaSources();
        } catch (Exception e) {
            LOG.error(e);
        }
    }

    void attachIdeaSources() {
        if (!ApplicationManager.getApplication().isInternal()) return;
        final Set<LibraryOrderEntry> libs = getLibsWithoutSourceRoots();
        LOG.info("Got " + libs.size() + " total IDEA libraries with missing source roots");
        if (libs.isEmpty()) return;
        LibraryOrderEntry pivot = null;
        for (LibraryOrderEntry lib : libs) {
            Library library = lib.getLibrary();
            if (library != null && library.getFiles(OrderRootType.CLASSES).length > 0) {
                pivot = lib;
                break;
            }
        }
        if (pivot == null) {
            LOG.error("No libraries with valid class roots found");
            return;
        }

        final VirtualFile zip = findSourcesZip(findCurrentSDKDir(pivot));
        if (zip == null) return;
        LOG.info("Found related sources archive: " + zip.getCanonicalPath());
        new Task.Backgroundable(myProject, "Attaching Idea Sources", true) {
            @Override
            public void run(@NotNull ProgressIndicator indicator) {
                setTitle("Scanning for Sources Archive");
                final Collection<VirtualFile> roots = new LibraryJavaSourceRootDetector().detectRoots(zip, indicator);
                setTitle("Attaching Source Roots");
                for (LibraryOrderEntry lib : libs) {
                    final Library library = lib.getLibrary();
                    if (library != null && library.getUrls(OrderRootType.SOURCES).length == 0) {
                        ApplicationManager.getApplication().invokeLater(new Runnable() {
                            @Override
                            public void run() {
                                AttachSourcesUtil.appendSources(library, (VirtualFile[]) roots.toArray());
                            }
                        });
                    }
                }
                LOG.info("Finished attaching IDEA sources");
            }
        }.queue();
    }

    Set<LibraryOrderEntry> getLibsWithoutSourceRoots() {
        return needsAttaching(getIntellijJars());
    }

    private HashSet<LibraryOrderEntry> getIntellijJars() {
        VirtualFile[] all = PackageIndex.getInstance(myProject).getDirectoriesByPackageName("com.intellij", false);
        ArrayList<VirtualFile> jars = new ArrayList<VirtualFile>();
        for (VirtualFile f : all) {
            if (f.getUrl().contains(".jar!")) jars.add(f);
        }
        ArrayList<LibraryOrderEntry> libs = new ArrayList<LibraryOrderEntry>();
        for (VirtualFile jar : jars) {
            for (OrderEntry orderEntry : ProjectFileIndex.SERVICE.getInstance(myProject).getOrderEntriesForFile(jar)) {
                if (orderEntry instanceof LibraryOrderEntry) libs.add((LibraryOrderEntry) orderEntry);
            }
        }
        return new HashSet<LibraryOrderEntry>(libs);
    }

    private Set<LibraryOrderEntry> needsAttaching(Set<LibraryOrderEntry> libs) {
        Set<LibraryOrderEntry> res = new HashSet<LibraryOrderEntry>();
        for (LibraryOrderEntry lib : libs) {
            Library library = lib.getLibrary();
            if (library != null && library.getUrls(OrderRootType.SOURCES).length == 0)
                res.add(lib);
        }
        return res;
    }

    private VirtualFile findSourcesZip(VirtualFile root) {
        final VirtualFile[] res = {null};
        try {
            VfsUtilCore.visitChildrenRecursively(root,
                    new VirtualFileVisitor() {
                        @NotNull
                        @Override
                        public Result visitFileEx(@NotNull VirtualFile file) {
                            if (file.getName().matches("^sources\\.(zip|jar)$")) {
                                res[0] = VirtualFileManager.getInstance().findFileByUrl("jar://" + file.getCanonicalPath() + "!/");
                                throw new RuntimeException();
                            }
                            return VirtualFileVisitor.CONTINUE;
                        }
                    });
        } catch (Exception ignored) {

        }
        return res[0];
    }

    private VirtualFile findCurrentSDKDir(LibraryOrderEntry anyLib) {
        String path = anyLib.getFiles(OrderRootType.CLASSES)[0].getCanonicalPath();
        if (path == null) return null;
        VirtualFile parent = VirtualFileManager.getInstance().findFileByUrl("file://" + path.substring(0, path.length() - 2));
        while (parent != null) {
            if (parent.getName().matches("^\\d+\\.\\d+\\.\\d+$") || parent.equals(myProject.getBaseDir()))
                return parent;
            parent = parent.getParent();
        }
        return null;
    }
}

@@ -945,16 +945,48 @@
 til.Set;
+%0Aimport java.util.regex.Pattern;
 %0A%0Apublic
@@ -1045,16 +1045,102 @@
 onent %7B%0A
+    private static final Pattern PATTERN = Pattern.compile(%22%5Esources%5C%5C.(zip%7Cjar)$%22);%0A%0A
     prot
@@ -1225,16 +1225,22 @@
 private 
+final 
 Logger L
@@ -1297,20 +1297,20 @@
 lic 
-final 
 static
+ final
  Str
@@ -3376,17 +3376,34 @@
 ibrary, 
-(
+roots.toArray(new 
 VirtualF
@@ -3410,25 +3410,21 @@
 ile%5B
-%5D) 
 roots.
-toArray(
+size()%5D
 ));%0A
@@ -4560,16 +4560,22 @@
 taching(
+final 
 Set%3CLibr
@@ -4601,24 +4601,30 @@
 ) %7B%0A        
+final 
 Set%3CLibraryO
@@ -4774,32 +4774,33 @@
 %0A            if 
+(
 (library != null
@@ -4791,36 +4791,38 @@
 (library != null
+)
  && 
+(
 library.getUrls(
@@ -4856,16 +4856,17 @@
 th == 0)
+)
 %0A       
@@ -5319,16 +5319,32 @@
      if 
+(PATTERN.matcher
 (file.ge
@@ -5342,32 +5342,33 @@
 r(file.getName()
+)
 .matches(%22%5Esourc
@@ -5360,39 +5360,16 @@
 matches(
-%22%5Esources%5C%5C.(zip%7Cjar)$%22
 )) %7B%0A   

fix CCE in sources attacher - never mindlessly apply quickfixes without reviewing