// Copyright 2000-2018 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.externalSystem.service.execution;

import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.*;
import com.intellij.openapi.projectRoots.impl.JavaAwareProjectJdkTableImpl;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.EnvironmentUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Collection;
import java.util.List;
import java.util.stream.Stream;

import static com.intellij.openapi.util.Pair.pair;

public class ExternalSystemJdkUtil {
  public static final String USE_INTERNAL_JAVA = "#JAVA_INTERNAL";
  public static final String USE_PROJECT_JDK = "#USE_PROJECT_JDK";
  public static final String USE_JAVA_HOME = "#JAVA_HOME";

  @Nullable
  public static Sdk getJdk(@Nullable Project project, @Nullable String jdkName) throws ExternalSystemJdkException {
    if (jdkName == null) return null;

    if (USE_INTERNAL_JAVA.equals(jdkName)) {
      return JavaAwareProjectJdkTableImpl.getInstanceEx().getInternalJdk();
    }

    if (USE_PROJECT_JDK.equals(jdkName)) {
      if (project != null) {
        Sdk res = ProjectRootManager.getInstance(project).getProjectSdk();
        if (res != null) return res;

        Module[] modules = ModuleManager.getInstance(project).getModules();
        for (Module module : modules) {
          Sdk sdk = ModuleRootManager.getInstance(module).getSdk();
          if (sdk != null && sdk.getSdkType() instanceof JavaSdkType) return sdk;
        }
      }

      if (project == null || project.isDefault()) {
        Sdk recent = ProjectJdkTable.getInstance().findMostRecentSdkOfType(JavaSdk.getInstance());
        return recent != null ? recent : JavaAwareProjectJdkTableImpl.getInstanceEx().getInternalJdk();
      }

      throw new ProjectJdkNotFoundException();
    }

    if (USE_JAVA_HOME.equals(jdkName)) {
      String javaHome = EnvironmentUtil.getEnvironmentMap().get("JAVA_HOME");
      if (StringUtil.isEmptyOrSpaces(javaHome)) throw new UndefinedJavaHomeException();
      if (!isValidJdk(javaHome)) throw new InvalidJavaHomeException(javaHome);

      SimpleJavaSdkType sdkType = SimpleJavaSdkType.getInstance();
      String sdkName = sdkType.suggestSdkName(null, javaHome);
      return sdkType.createJdk(sdkName, javaHome);
    }

    Sdk projectJdk = ProjectJdkTable.getInstance().findJdk(jdkName);
    if (projectJdk != null) {
      String homePath = projectJdk.getHomePath();
      if (!isValidJdk(homePath)) throw new InvalidSdkException(homePath);
      return projectJdk;
    }

    return null;
  }

  @NotNull
  public static Pair<String, Sdk> getAvailableJdk(@Nullable Project project) throws ExternalSystemJdkException {
    SdkType javaSdkType = getJavaSdkType();

    if (project != null) {
      Stream<Sdk> projectSdks = Stream.concat(
        Stream.of(ProjectRootManager.getInstance(project).getProjectSdk()),
        Stream.of(ModuleManager.getInstance(project).getModules()).map(module -> ModuleRootManager.getInstance(module).getSdk()));
      Sdk projectSdk = projectSdks
        .filter(sdk -> sdk != null && sdk.getSdkType() == javaSdkType && isValidJdk(sdk.getHomePath()))
        .findFirst().orElse(null);
      if (projectSdk != null) {
        return pair(USE_PROJECT_JDK, projectSdk);
      }
    }

    List<Sdk> allJdks = ProjectJdkTable.getInstance().getSdksOfType(javaSdkType);
    Sdk mostRecentSdk = allJdks.stream().filter(sdk -> isValidJdk(sdk.getHomePath())).max(javaSdkType.versionComparator()).orElse(null);
    if (mostRecentSdk != null) {
      return pair(mostRecentSdk.getName(), mostRecentSdk);
    }
    if (!ApplicationManager.getApplication().isUnitTestMode()) {
      String javaHome = EnvironmentUtil.getEnvironmentMap().get("JAVA_HOME");
      if (isValidJdk(javaHome)) {
        SimpleJavaSdkType simpleJavaSdkType = SimpleJavaSdkType.getInstance();
        String sdkName = simpleJavaSdkType.suggestSdkName(null, javaHome);
        return pair(USE_JAVA_HOME, simpleJavaSdkType.createJdk(sdkName, javaHome));
      }
    }

    return pair(USE_INTERNAL_JAVA, JavaAwareProjectJdkTableImpl.getInstanceEx().getInternalJdk());
  }

  @NotNull
  public static Collection<String> suggestJdkHomePaths() {
    return getJavaSdkType().suggestHomePaths();
  }

  @NotNull
  public static SdkType getJavaSdkType() {
    // JavaSdk.getInstance() can be null for non-java IDE
    JavaSdk javaSdk = JavaSdk.getInstance();
    return javaSdk == null ? SimpleJavaSdkType.getInstance() : javaSdk;
  }

  /** @deprecated trivial (to be removed in IDEA 2019) */
  @Deprecated
  public static boolean checkForJdk(@NotNull Project project, @Nullable String jdkName) {
    try {
      final Sdk sdk = getJdk(project, jdkName);
      return sdk != null && sdk.getHomePath() != null && JdkUtil.checkForJdk(sdk.getHomePath());
    }
    catch (ExternalSystemJdkException ignore) { }
    return false;
  }

  public static boolean isValidJdk(@Nullable String homePath) {
    return !StringUtil.isEmptyOrSpaces(homePath) && (JdkUtil.checkForJdk(homePath) || JdkUtil.checkForJre(homePath));
  }

  @NotNull
  public static Sdk addJdk(String homePath) {
    // JavaSdk.getInstance() can be null for non-java IDE
    JavaSdk javaSdk = JavaSdk.getInstance();
    if (javaSdk == null) {
      SimpleJavaSdkType simpleJavaSdkType = SimpleJavaSdkType.getInstance();
      return simpleJavaSdkType.createJdk(simpleJavaSdkType.suggestSdkName(null, homePath), homePath);
    }
    else {
      return ApplicationManager.getApplication().runWriteAction(
        (Computable<Sdk>)() -> {
          Sdk jdk = javaSdk.createJdk(javaSdk.suggestSdkName(null, homePath), homePath, false);
          ProjectJdkTable.getInstance().addJdk(jdk);
          return jdk;
        }
      );
    }
  }
}
@@ -516,16 +516,84 @@
 leImpl;%0A
+import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil;%0A
 import c
@@ -695,53 +695,8 @@
 er;%0A
-import com.intellij.openapi.util.Computable;%0A
 impo
@@ -5676,24 +5676,37 @@
 homePath) %7B%0A
+    Sdk jdk;%0A
     // JavaS
@@ -5906,22 +5906,21 @@
 ;%0A      
-return
+jdk =
  simpleJ
@@ -6028,114 +6028,8 @@
     
-return ApplicationManager.getApplication().runWriteAction(%0A        (Computable%3CSdk%3E)() -%3E %7B%0A          Sdk 
 jdk 
@@ -6114,48 +6114,39 @@
     
-  
+%7D%0A
     
-ProjectJdkTable.getInstance()
+SdkConfigurationUtil
 .add
-J
+S
 dk(j
@@ -6150,22 +6150,16 @@
 k(jdk);%0A
-      
     retu
@@ -6170,33 +6170,8 @@
 dk;%0A
-        %7D%0A      );%0A    %7D%0A
   %7D%0A

add JDK to ProjectJdkTable for for non-java IDE (IDEA-CR-36256)