// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.plugins.groovy.debugger;

import com.intellij.execution.Executor;
import com.intellij.execution.JavaExecutionUtil;
import com.intellij.execution.configurations.JavaParameters;
import com.intellij.execution.configurations.ModuleBasedConfiguration;
import com.intellij.execution.configurations.RunConfiguration;
import com.intellij.execution.configurations.RunProfile;
import com.intellij.execution.executors.DefaultDebugExecutor;
import com.intellij.execution.runners.JavaProgramPatcher;
import com.intellij.ide.plugins.PluginManagerCore;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.LanguageLevelUtil;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.JavaSdk;
import com.intellij.openapi.projectRoots.JavaSdkVersion;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.LanguageLevelProjectExtension;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.search.FileTypeIndex;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.PsiModificationTracker;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.model.java.JdkVersionDetector;
import org.jetbrains.plugins.groovy.GroovyFileType;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.regex.Pattern;

import static com.intellij.ide.plugins.PluginManagerCoreKt.getPluginDistDirByClass;

final class GroovyHotSwapper extends JavaProgramPatcher {
  private static final Logger LOG = Logger.getInstance(GroovyHotSwapper.class);
  private static final String GROOVY_HOTSWAP_AGENT_PATH = "groovy.hotswap.agent.path";
  private static final Pattern SPRING_LOADED_PATTERN = Pattern.compile("-javaagent:.+springloaded-[^/\\\\]+\\.jar");

  private static boolean containsGroovyClasses(Project project) {
    return CachedValuesManager.getManager(project).getCachedValue(project, () ->
      CachedValueProvider.Result.create(
        FileTypeIndex.containsFileOfType(GroovyFileType.GROOVY_FILE_TYPE, GlobalSearchScope.projectScope(project)),
        PsiModificationTracker.MODIFICATION_COUNT));
  }

  private static boolean hasSpringLoadedReloader(JavaParameters javaParameters) {
    for (String param : javaParameters.getVMParametersList().getParameters()) {
      if (SPRING_LOADED_PATTERN.matcher(param).matches()) {
        return true;
      }
    }

    return false;
  }

  @Override
  public void patchJavaParameters(Executor executor, RunProfile configuration, JavaParameters javaParameters) {
    ApplicationManager.getApplication().assertReadAccessAllowed();
    if (!executor.getId().equals(DefaultDebugExecutor.EXECUTOR_ID)) {
      return;
    }

    if (!GroovyDebuggerSettings.getInstance().ENABLE_GROOVY_HOTSWAP) {
      return;
    }

    if (hasSpringLoadedReloader(javaParameters)) {
      return;
    }

    if (!(configuration instanceof RunConfiguration)) {
      return;
    }

    final Project project = ((RunConfiguration)configuration).getProject();
    if (project == null) {
      return;
    }

    if (!LanguageLevelProjectExtension.getInstance(project).getLanguageLevel().isAtLeast(LanguageLevel.JDK_1_5)) {
      return;
    }

    if (configuration instanceof ModuleBasedConfiguration) {
      final Module module = ((ModuleBasedConfiguration<?, ?>)configuration).getConfigurationModule().getModule();
      if (module != null) {
        final LanguageLevel level = LanguageLevelUtil.getEffectiveLanguageLevel(module);
        if (!level.isAtLeast(LanguageLevel.JDK_1_5)) {
          return;
        }
      }
    }

    Sdk jdk = javaParameters.getJdk();
    if (jdk != null) {
      JavaSdkVersion version = JavaSdk.getInstance().getVersion(jdk);
      if (version != null && !version.isAtLeast(JavaSdkVersion.JDK_1_8) && jdk.getHomePath() != null) {
        JdkVersionDetector.JdkVersionInfo info = JdkVersionDetector.getInstance().detectJdkVersionInfo(jdk.getHomePath());
        if (info != null && info.variant == JdkVersionDetector.Variant.IBM) {
          LOG.info("Due to old IBM JDK peculiarities (IDEA-59070) we don't add Groovy agent when running applications under it");
          return;
        }
      }
    }

    if (!project.isDefault() && containsGroovyClasses(project)) {
      String agentPath = prepareAgentPath();
      if (agentPath != null) {
        javaParameters.getVMParametersList().add("-javaagent:" + agentPath);
      }
    }
  }

  @Nullable
  private static String prepareAgentPath() {
    String agentJarPath = getAgentJarPath();
    if (agentJarPath == null) {
      // error already reported from getAgentJarPath
      return null;
    }

    if (!Files.isRegularFile(Path.of(agentJarPath))) {
      LOG.error("Groovy hotswap agent is not found at: " + agentJarPath);
      return null;
    }

    String agentPath = JavaExecutionUtil.handleSpacesInAgentPath(agentJarPath, "groovyHotSwap", GROOVY_HOTSWAP_AGENT_PATH);
    if (agentPath == null) {
      LOG.error("Unable to handle spaces in path for groovy hotswap agent: " + agentJarPath);
      return null;
    }

    if (!Files.isRegularFile(Path.of(agentPath))) {
      LOG.error("Groovy hotswap agent is not found (after handling spaces) at: " + agentPath +
                ". original jar path (existing): " + agentJarPath);
      return null;
    }

    return agentPath;
  }

  @Nullable
  private static String getAgentJarPath() {
    if (PluginManagerCore.isRunningFromSources()) { //development mode
      return Path.of(PathManager.getCommunityHomePath(), "plugins", "groovy", "hotswap", "gragent.jar").toString();
    }
    else {
      Path groovyPluginDist = getPluginDistDirByClass(GroovyHotSwapper.class);
      if (groovyPluginDist == null) {
        LOG.error("Failed to find groovy plugin dist directory");
        return null;
      }

      return groovyPluginDist.resolve("lib").resolve("agent").resolve("gragent.jar").toString();
    }
  }
}
