package jetbrains.mps.execution.api.configurations;

/*Generated by MPS */

import jetbrains.mps.annotations.GeneratedClass;
import com.intellij.execution.junit.RuntimeConfigurationProducer;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.Nullable;
import com.intellij.execution.actions.ConfigurationContext;
import com.intellij.execution.configurations.ConfigurationFactory;
import org.jetbrains.annotations.NotNull;
import jetbrains.mps.project.MPSProject;
import com.intellij.openapi.project.Project;
import jetbrains.mps.ide.project.ProjectHelper;
import com.intellij.execution.RunnerAndConfigurationSettings;
import com.intellij.execution.Location;
import java.util.List;
import java.util.Objects;
import com.intellij.execution.configurations.RunConfiguration;
import jetbrains.mps.plugins.runconfigs.MPSLocation;
import jetbrains.mps.plugins.runconfigs.MPSPsiElement;
import jetbrains.mps.smodel.ModelAccessHelper;
import jetbrains.mps.util.Computable;
import com.intellij.execution.impl.RunnerAndConfigurationSettingsImpl;
import com.intellij.execution.impl.RunManagerImpl;
import jetbrains.mps.util.EqualUtil;

/**
 * Currently extends the deprecated RuntimeConfigurationProducer
 * To be migrated in 3.5 to the {@link com.intellij.execution.actions.RunConfigurationProducer }
 * 
 * @param <T> denotes the MpsPsiElement's item which is the 'key' of the subclassing producer
 */
@GeneratedClass(node = "r:49e72ff8-8ace-42fd-8f9f-5961eed9792e(jetbrains.mps.execution.api.configurations)/8536479618435048503", model = "r:49e72ff8-8ace-42fd-8f9f-5961eed9792e(jetbrains.mps.execution.api.configurations)")
public abstract class BaseMpsProducer<T> extends RuntimeConfigurationProducer {
  private PsiElement mySourceElement;
  @Nullable
  private ConfigurationContext myContext;

  public BaseMpsProducer(ConfigurationFactory configurationFactory) {
    super(configurationFactory);
  }

  public void setSourceElement(PsiElement sourceElement) {
    mySourceElement = sourceElement;
  }

  @Override
  public PsiElement getSourceElement() {
    return mySourceElement;
  }

  @NotNull
  protected final MPSProject getMpsProject() {
    if (myContext == null) {
      throw new IllegalStateException("Context is not set");
    }
    Project project = myContext.getProject();
    return ProjectHelper.fromIdeaProject(project);
  }

  @Nullable
  protected ConfigurationContext getContext() {
    return myContext;
  }

  /**
   * Here we are making resolve by type and name (!), however it is not legal in some scenarios (consider main1 and main2, user creates configuration
   * named main2, which runs the main method from the main1 class. If now user switches to the main2 and presses ctrl-shift-f10 he will be still
   * running the main method from the main1 class)
   * The proper way is to implement this method in each producer obligatory.
   * However runconfigurations generator must be fully updated with new platform
   * RunConfigurationProducer (instead of RuntimeConfigurationProducer).
   */
  @Override
  @Nullable
  protected RunnerAndConfigurationSettings findExistingByElement(Location location, @NotNull List<? extends RunnerAndConfigurationSettings> existingConfigurations, ConfigurationContext context) {
    RunnerAndConfigurationSettings given = getConfiguration();
    for (RunnerAndConfigurationSettings existing : existingConfigurations) {
      if (Objects.equals(existing.getType(), given.getType())) {
        if (isConfigurationFromContext(existing.getConfiguration(), context)) {
          return existing;
        }
      }
    }
    return null;
  }

  /**
   * Supposed to return whether the given location is created from the given context
   * By default compares by names
   * 
   * TODO keep it after migration to the {@link com.intellij.execution.actions.RunConfigurationProducer }
   */
  protected boolean isConfigurationFromContext(@NotNull RunConfiguration configuration, @NotNull ConfigurationContext context) {
    RunnerAndConfigurationSettings given = getConfiguration();
    return Objects.equals(configuration.getName(), given.getName());
  }

  @Nullable
  @Override
  protected RunnerAndConfigurationSettings createConfigurationByElement(Location location, ConfigurationContext context) {
    myContext = context;
    if (!(location instanceof MPSLocation)) {
      return null;
    }
    MPSLocation mpsLocation = (MPSLocation) location;
    final MPSPsiElement psiElement = mpsLocation.getPsiElement();
    MPSProject mpsProject = mpsLocation.getMPSProject();
    if (psiElement.isTransientElement()) {
      // Generally, no run configurations for transient elements might be too much - one may desire
      // e.g. to run a main class for a transient model. Perhaps, each producer shall decide itself (in isApplicable) whether
      // to ignore tranient models, however, at the moment I decided to prevent any code execution for transient models for
      // the sake of change simplicity.
      return null;
    }
    RunConfiguration config = new ModelAccessHelper(mpsProject.getRepository()).runReadAction(new Computable<RunConfiguration>() {
      @Override
      public RunConfiguration compute() {
        Object mpsItem = psiElement.getMPSItem();
        if (mpsItem == null) {
          return null;
        }
        if (!(isApplicable(mpsItem))) {
          return null;
        }
        return doCreateConfiguration((T) mpsItem);
      }
    });
    if (config == null) {
      return null;
    }
    return new RunnerAndConfigurationSettingsImpl(RunManagerImpl.getInstanceImpl(location.getProject()), config, false);
  }

  @Nullable
  protected abstract RunConfiguration doCreateConfiguration(T node);

  protected abstract boolean isApplicable(Object element);

  @Override
  public int compareTo(Object o) {
    return RuntimeConfigurationProducer.PREFERED;
  }

  @Override
  public int hashCode() {
    return ((myContext == null ? 0 : myContext.hashCode())) + 10 * ((mySourceElement == null ? 0 : mySourceElement.hashCode())) + 20 * getClass().getName().hashCode();
  }

  @Override
  public boolean equals(Object obj) {
    if (obj == null || !(obj instanceof BaseMpsProducer)) {
      return false;
    }
    BaseMpsProducer configCreator = (BaseMpsProducer) obj;
    return EqualUtil.equals(configCreator.myContext, myContext) && EqualUtil.equals(configCreator.mySourceElement, mySourceElement) && EqualUtil.equals(configCreator.getClass().getName(), getClass().getName());
  }
}
