package ru.artyushov.jmhPlugin.configuration;

import com.intellij.execution.JavaExecutionUtil;
import com.intellij.execution.Location;
import com.intellij.execution.actions.ConfigurationContext;
import com.intellij.execution.configurations.ConfigurationFactory;
import com.intellij.execution.junit.JavaRunConfigurationProducerBase;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiMethod;
import com.intellij.util.PathUtil;
import org.jetbrains.annotations.NotNull;

import java.util.Objects;

import static ru.artyushov.jmhPlugin.configuration.ConfigurationUtils.findBenchmarkEntry;
import static ru.artyushov.jmhPlugin.configuration.ConfigurationUtils.toRunParams;
import static ru.artyushov.jmhPlugin.configuration.JmhConfiguration.Type.CLASS;
import static ru.artyushov.jmhPlugin.configuration.JmhConfiguration.Type.METHOD;

/**
 * Supports creating run configurations from context (by right-clicking a code element in the source editor or the project view).
 */
public class JmhConfigurationProducer extends JavaRunConfigurationProducerBase<JmhConfiguration> implements Cloneable {

    @NotNull
    @Override
    public ConfigurationFactory getConfigurationFactory() {
        return JmhConfigurationType.getInstance().getConfigurationFactories()[0];
    }

    /**
     * Sets up a configuration based on the specified context.
     *
     * @param configuration a clone of the template run configuration of the specified type
     * @param context       contains the information about a location in the source code.
     * @param sourceElement a reference to the source element for the run configuration (by default contains the element at caret,
     *                      can be updated by the producer to point to a higher-level element in the tree).
     * @return true if the context is applicable to this run configuration producer, false if the context is not applicable and the
     * configuration should be discarded.
     */
    @Override
    protected boolean setupConfigurationFromContext(@NotNull JmhConfiguration configuration, ConfigurationContext context, @NotNull Ref<PsiElement> sourceElement) {
        Location locationFromContext = context.getLocation();
        if (locationFromContext == null) {
            return false;
        }
        PsiElement benchmarkEntry = findBenchmarkEntry(locationFromContext.getPsiElement());

        final JmhConfiguration.Type runType;
        final PsiClass benchmarkClass;
        if (benchmarkEntry instanceof PsiClass) {
            runType = CLASS;
            benchmarkClass = (PsiClass) benchmarkEntry;
        } else if (benchmarkEntry instanceof PsiMethod) {
            runType = METHOD;
            benchmarkClass = benchmarkClassOfMethod(benchmarkEntry);
        } else {
            return false;
        }
        configuration.setBenchmarkClass(benchmarkClass.getQualifiedName());

        sourceElement.set(benchmarkEntry);
        setupConfigurationModule(context, configuration);
        final Module originalModule = configuration.getConfigurationModule().getModule();
        configuration.restoreOriginalModule(originalModule);
        String generatedParams = toRunParams(benchmarkEntry, true);
        configuration.setProgramParameters(createProgramParameters(generatedParams, configuration.getProgramParameters()));
        if (configuration.getWorkingDirectory() == null || configuration.getWorkingDirectory().isEmpty()) { // respect default working directory if set
            configuration.setWorkingDirectory(PathUtil.getLocalPath(context.getProject().getBaseDir()));
        }
        configuration.setName(getNameForConfiguration(benchmarkEntry));
        configuration.setType(runType);
        return true;
    }

    /**
     * Checks if the specified configuration was created from the specified context.
     *
     * @param configuration a configuration instance.
     * @param context       contains the information about a location in the source code.
     * @return true if this configuration was created from the specified context, false otherwise.
     */
    @Override
    public boolean isConfigurationFromContext(@NotNull JmhConfiguration configuration, @NotNull ConfigurationContext context) {
        Location locationFromContext = context.getLocation();
        if (locationFromContext == null) {
            return false;
        }
        PsiElement benchmarkEntry = findBenchmarkEntry(locationFromContext.getPsiElement());
        PsiClass benchmarkClass;
        if (benchmarkEntry instanceof PsiMethod) {
            benchmarkClass = benchmarkClassOfMethod(benchmarkEntry);
            // if the config is for a whole benchmark class then ignore the method and use its class as an entry
            if (configuration.getBenchmarkType() == CLASS) {
                benchmarkEntry = benchmarkClass;
            } else if (configuration.getBenchmarkType() != METHOD) {
                // unexpected BenchmarkType, must be METHOD
                return false;
            }
        } else if (benchmarkEntry instanceof PsiClass) {
            // if the config is for a specific method but we are on a class then the config can't be applied
            if (configuration.getBenchmarkType() == METHOD) {
                return false;
            } else if (configuration.getBenchmarkType() != CLASS) {
                // unexpected BenchmarkType, must be CLASS
                return false;
            }
            benchmarkClass = (PsiClass) benchmarkEntry;
        } else {
            return false;
        }
        //TODO: this check may be skipped because we'll then check ProgramParameters, but it still faster to filter out
        if (!Objects.equals(benchmarkClass.getQualifiedName(), configuration.getBenchmarkClass())) {
            return false;
        }
        String generatedParams = toRunParams(benchmarkEntry, true);
        if (configuration.getProgramParameters() == null || configuration.getProgramParameters().isEmpty()
                || !configuration.getProgramParameters().startsWith(generatedParams)) {
            return false;
        }
        Location location = JavaExecutionUtil.stepIntoSingleClass(locationFromContext);
        final Module originalModule = configuration.getConfigurationModule().getModule();
        if (location.getModule() == null || !location.getModule().equals(originalModule)) {
            return false;
        }
        setupConfigurationModule(context, configuration);
        configuration.restoreOriginalModule(originalModule);

        return true;
    }

    private String getNameForConfiguration(@NotNull PsiElement benchmarkEntry) {
        return toRunParams(benchmarkEntry, false);
    }

    private String createProgramParameters(String generatedParams, String defaultParams) {
        return defaultParams != null && !defaultParams.isEmpty() ? generatedParams + ' ' + defaultParams : generatedParams;
    }

    private PsiClass benchmarkClassOfMethod(PsiElement benchmarkEntry) {
        PsiMethod benchmarkMethod = (PsiMethod) benchmarkEntry;
        return benchmarkMethod.getContainingClass();
    }
}

@@ -516,43 +516,8 @@
 od;%0A
-import com.intellij.util.PathUtil;%0A
 impo
@@ -582,16 +582,81 @@
 jects;%0A%0A
+import static com.intellij.openapi.util.text.StringUtil.isEmpty;%0A
 import s
@@ -3478,55 +3478,16 @@
 if (
-configuration.getWorkingDirectory() == null %7C%7C 
+isEmpty(
 conf
@@ -3517,25 +3517,16 @@
 ectory()
-.isEmpty(
 )) %7B // 
@@ -3616,64 +3616,30 @@
 ory(
-PathUtil.getLocalPath(context.getProject().getBaseDir())
+%22$MODULE_WORKING_DIR$%22
 );%0A 
@@ -5929,32 +5929,40 @@
 ue);%0A        if 
+(isEmpty
 (configuration.g
@@ -5986,65 +5986,8 @@
 rs()
- == null %7C%7C configuration.getProgramParameters().isEmpty(
 )%0A  
@@ -6807,16 +6807,24 @@
  return 
+isEmpty(
 defaultP
@@ -6832,46 +6832,29 @@
 rams
- != null && !defaultParams.isEmpty() ?
+) ? generatedParams :
  gen
@@ -6887,34 +6887,16 @@
 ltParams
- : generatedParams
 ;%0A    %7D%0A

remove usage of deprecated getBaseDir()