// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.

package com.intellij.openapi.projectRoots.impl;

import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.roots.PersistentOrderRootType;
import com.intellij.openapi.roots.RootProvider;
import com.intellij.openapi.vfs.*;
import com.intellij.openapi.vfs.pointers.VirtualFilePointer;
import com.intellij.openapi.vfs.pointers.VirtualFilePointerContainer;
import com.intellij.openapi.vfs.pointers.VirtualFilePointerListener;
import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager;
import com.intellij.util.ArrayUtilRt;
import gnu.trove.THashMap;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;

import java.util.List;
import java.util.Map;

public class RootsAsVirtualFilePointers implements RootProvider {
  private static final Logger LOG = Logger.getInstance(RootsAsVirtualFilePointers.class);
  private final Map<OrderRootType, VirtualFilePointerContainer> myRoots = new THashMap<>();

  private final boolean myNoCopyJars;
  private final VirtualFilePointerListener myListener;
  @NotNull private final Disposable myParent;

  RootsAsVirtualFilePointers(boolean noCopyJars, VirtualFilePointerListener listener, @NotNull Disposable parent) {
    myNoCopyJars = noCopyJars;
    myListener = listener;
    myParent = parent;
  }

  @Override
  public VirtualFile @NotNull [] getFiles(@NotNull OrderRootType type) {
    VirtualFilePointerContainer container = myRoots.get(type);
    return container == null ? VirtualFile.EMPTY_ARRAY : container.getFiles();
  }

  @Override
  public String @NotNull [] getUrls(@NotNull OrderRootType type) {
    VirtualFilePointerContainer container = myRoots.get(type);
    return container == null ? ArrayUtilRt.EMPTY_STRING_ARRAY : container.getUrls();
  }

  public void addRoot(@NotNull VirtualFile virtualFile, @NotNull OrderRootType type) {
    getOrCreateContainer(type).add(virtualFile);
  }

  public void addRoot(@NotNull String url, @NotNull OrderRootType type) {
    getOrCreateContainer(type).add(url);
  }

  public void removeAllRoots(@NotNull OrderRootType type) {
    VirtualFilePointerContainer container = myRoots.get(type);
    if (container != null) {
      container.clear();
    }
  }

  public void removeRoot(@NotNull VirtualFile root, @NotNull OrderRootType type) {
    removeRoot(root.getUrl(), type);
  }

  public void removeRoot(@NotNull String url, @NotNull OrderRootType type) {
    VirtualFilePointerContainer container = myRoots.get(type);
    VirtualFilePointer pointer = container == null ? null : container.findByUrl(url);
    if (pointer != null) {
      container.remove(pointer);
    }
  }

  public void removeAllRoots() {
    for (VirtualFilePointerContainer myRoot : myRoots.values()) {
      myRoot.clear();
    }
  }

  public void readExternal(@NotNull Element element) {
    for (PersistentOrderRootType type : OrderRootType.getAllPersistentTypes()) {
      read(element, type);
    }

    ApplicationManager.getApplication().runReadAction(() -> myRoots.values().forEach(container -> {
      if (myNoCopyJars) {
        for (String root : container.getUrls()) {
          setNoCopyJars(root);
        }
      }
    }));
  }

  public void writeExternal(@NotNull Element element) {
    for (PersistentOrderRootType type : OrderRootType.getSortedRootTypes()) {
      write(element, type);
    }
  }

  void copyRootsFrom(@NotNull RootProvider rootContainer) {
    removeAllRoots();
    for (OrderRootType rootType : OrderRootType.getAllTypes()) {
      final String[] newRoots = rootContainer.getUrls(rootType);
      for (String newRoot : newRoots) {
        addRoot(newRoot, rootType);
      }
    }
  }

  private static void setNoCopyJars(@NotNull String url) {
    if (StandardFileSystems.JAR_PROTOCOL.equals(VirtualFileManager.extractProtocol(url))) {
      String path = VirtualFileManager.extractPath(url);
      final VirtualFileSystem fileSystem = StandardFileSystems.jar();
      if (fileSystem instanceof JarCopyingFileSystem) {
        ((JarCopyingFileSystem)fileSystem).setNoCopyJarForPath(path);
      }
    }
  }

  /**
   <roots>
   <sourcePath>
     <root type="composite">
       <root type="simple" url="jar://I:/Java/jdk1.8/src.zip!/" />
       <root type="simple" url="jar://I:/Java/jdk1.8/javafx-src.zip!/" />
     </root>
   </sourcePath>
   </roots>
   */
  private void read(@NotNull Element roots, @NotNull PersistentOrderRootType type)  {
    String sdkRootName = type.getSdkRootName();
    Element child = sdkRootName == null ? null : roots.getChild(sdkRootName);
    if (child == null) {
      return;
    }

    List<Element> composites = child.getChildren();
    if (composites.size() != 1) {
      LOG.error(composites);
    }
    Element composite = composites.get(0);
    if (!composite.getChildren("root").isEmpty()) {
      VirtualFilePointerContainer container = getOrCreateContainer(type);
      container.readExternal(composite, "root", false);
    }
  }

  /**
   <roots>
   <sourcePath>
     <root type="composite">
       <root type="simple" url="jar://I:/Java/jdk1.8/src.zip!/" />
       <root type="simple" url="jar://I:/Java/jdk1.8/javafx-src.zip!/" />
     </root>
   </sourcePath>
   </roots>
   */
  private void write(@NotNull Element roots, @NotNull PersistentOrderRootType type) {
    String sdkRootName = type.getSdkRootName();
    if (sdkRootName == null) {
      return;
    }
    Element e = new Element(sdkRootName);
    roots.addContent(e);
    Element composite = new Element("root");
    composite.setAttribute("type", "composite");
    e.addContent(composite);
    VirtualFilePointerContainer container = myRoots.get(type);
    if (container != null) {
      container.writeExternal(composite, "root", false);
    }
    for (Element root : composite.getChildren()) {
      root.setAttribute("type", "simple");
    }
  }

  @Override
  public void addRootSetChangedListener(@NotNull RootSetChangedListener listener) {
    throw new RuntimeException();
  }

  @Override
  public void addRootSetChangedListener(@NotNull RootSetChangedListener listener, @NotNull Disposable parentDisposable) {
    throw new RuntimeException();
  }

  @Override
  public void removeRootSetChangedListener(@NotNull RootSetChangedListener listener) {
    throw new RuntimeException();
  }

  @NotNull
  private VirtualFilePointerContainer getOrCreateContainer(@NotNull OrderRootType rootType) {
    VirtualFilePointerContainer roots = myRoots.get(rootType);
    if (roots == null) {
      roots = VirtualFilePointerManager.getInstance().createContainer(myParent, myListener);
      myRoots.put(rootType, roots);
    }
    return roots;
  }
}

@@ -228,68 +228,8 @@
 le;%0A
-import com.intellij.openapi.application.ApplicationManager;%0A
 impo
@@ -772,35 +772,8 @@
 Rt;%0A
-import gnu.trove.THashMap;%0A
 impo
@@ -880,16 +880,63 @@
 til.Map;
+%0Aimport java.util.concurrent.ConcurrentHashMap;
 %0A%0Apublic
@@ -1163,17 +1163,26 @@
 s = new 
-T
+Concurrent
 HashMap%3C
@@ -3174,63 +3174,33 @@
     
-ApplicationManager.getApplication().runReadAction(() -%3E
+if (myNoCopyJars) %7B%0A     
  myR
@@ -3240,34 +3240,8 @@
 %3E %7B%0A
-      if (myNoCopyJars) %7B%0A
     
@@ -3338,17 +3338,16 @@
    %7D
+);
 %0A    %7D
-));
 %0A  %7D

remove read action to avoid deadlock in IDEA-222480 IDEA hangs on startup while initializing ProjectJdkTable