// Copyright 2014 Pants project contributors (see CONTRIBUTORS.md).
// Licensed under the Apache License, Version 2.0 (see LICENSE).

package com.twitter.intellij.pants.service.task;

import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.process.ProcessAdapter;
import com.intellij.execution.process.ProcessEvent;
import com.intellij.execution.process.ProcessOutputTypes;
import com.intellij.execution.process.UnixProcessManager;
import com.intellij.openapi.externalSystem.model.ExternalSystemException;
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId;
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListener;
import com.intellij.openapi.externalSystem.task.AbstractExternalSystemTaskManager;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
import com.intellij.util.containers.ContainerUtil;
import com.twitter.intellij.pants.PantsBundle;
import com.twitter.intellij.pants.model.PantsTargetAddress;
import com.twitter.intellij.pants.settings.PantsExecutionSettings;
import com.twitter.intellij.pants.util.PantsConstants;
import com.twitter.intellij.pants.util.PantsUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

public class PantsTaskManager extends AbstractExternalSystemTaskManager<PantsExecutionSettings> {
  public static final Map<String, String> goal2JvmOptionsFlag = ContainerUtil.newHashMap(
    Pair.create("test", "--jvm-test-junit-options"),
    Pair.create("run", "--jvm-run-jvm-options")
  );
  private final Map<ExternalSystemTaskId, Process> myCancellationMap = ContainerUtil.newConcurrentMap();


  @Override
  public void executeTasks(
    @NotNull final ExternalSystemTaskId id,
    @NotNull List<String> taskNames,
    @NotNull String projectPath,
    @Nullable PantsExecutionSettings settings,
    @NotNull List<String> vmOptions,
    @NotNull List<String> scriptParameters,
    @Nullable String debuggerSetup,
    @NotNull final ExternalSystemTaskNotificationListener listener
  ) throws ExternalSystemException {
    if (settings == null) {
      return;
    }
    projectPath = PantsTargetAddress.extractPath(projectPath);
    final GeneralCommandLine commandLine = PantsUtil.defaultCommandLine(projectPath);

    final String relativeProjectPath = PantsUtil.getRelativeProjectPath(commandLine.getWorkDirectory(), new File(projectPath));

    commandLine.addParameters(taskNames);
    if (!settings.getTargetNames().isEmpty()) {
      for (String targetName : settings.getTargetNames()) {
        commandLine.addParameter(relativeProjectPath + ":" + targetName);
      }
    } else {
      commandLine.addParameter(relativeProjectPath + File.separator + "::");
    }
    commandLine.addParameters(scriptParameters);
    for (String goal : taskNames) {
      final String jvmOptionsFlag = goal2JvmOptionsFlag.get(goal);
      if (jvmOptionsFlag == null) {
        continue;
      }
      for (String vmOption : vmOptions) {
        commandLine.addParameter(jvmOptionsFlag + "=" + vmOption);
      }
    }

    commandLine.addParameters("--no-color");
    if (debuggerSetup != null) {
      if (taskNames.size() > 1) {
        throw new ExternalSystemException(PantsBundle.message("pants.error.multiple.tasks.for.debugging"));
      }
      final String goal = taskNames.iterator().next();
      final String jvmOptionsFlag = goal2JvmOptionsFlag.get(goal);
      if (jvmOptionsFlag == null) {
        throw new ExternalSystemException(PantsBundle.message("pants.error.cannot.debug.task", goal));
      }
      commandLine.addParameter(jvmOptionsFlag + "=" + debuggerSetup);
    }

    listener.onTaskOutput(id, commandLine.getCommandLineString(PantsConstants.PANTS), true);
    try {
      final Process process = commandLine.createProcess();
      myCancellationMap.put(id, process);
      PantsUtil.getOutput(
        process,
        new ProcessAdapter() {
          @Override
          public void startNotified(ProcessEvent event) {
            super.startNotified(event);
            listener.onStart(id);
          }

          @Override
          public void onTextAvailable(ProcessEvent event, Key outputType) {
            super.onTextAvailable(event, outputType);
            listener.onTaskOutput(id, event.getText(), outputType == ProcessOutputTypes.STDOUT);
          }
        }
      );
    }
    catch (ExecutionException e) {
      throw new ExternalSystemException(e);
    }
    finally {
      myCancellationMap.remove(id);
    }
  }

  @Override
  public boolean cancelTask(@NotNull ExternalSystemTaskId id, @NotNull ExternalSystemTaskNotificationListener listener)
    throws ExternalSystemException {
    final Process process = myCancellationMap.get(id);
    if (process != null && process.isAlive()) {
      try {
        UnixProcessManager.sendSignalToProcessTree(process, UnixProcessManager.SIGTERM);
        boolean exitWithinTimeout = process.waitFor(5, TimeUnit.SECONDS);
        if (!exitWithinTimeout) {
          listener.onTaskOutput(id, "SIGTERM timed out. Force kill.\n", true);
          process.destroy();
        }
        return true;
      }
      catch (Exception e) {
        throw new ExternalSystemException(e);
      }
    }
    return true;
  }
}

@@ -460,66 +460,8 @@
 es;%0A
-import com.intellij.execution.process.UnixProcessManager;%0A
 impo
@@ -1314,20 +1314,27 @@
 va.util.
-List
+Collections
 ;%0Aimport
@@ -1344,19 +1344,20 @@
 va.util.
-Map
+List
 ;%0Aimport
@@ -1371,27 +1371,11 @@
 til.
-concurrent.TimeUnit
+Map
 ;%0A%0Ap
@@ -1773,17 +1773,16 @@
 Map();%0A%0A
-%0A
   @Overr
@@ -4887,29 +4887,8 @@
 null
- && process.isAlive()
 ) %7B%0A
@@ -4903,286 +4903,8 @@
 y %7B%0A
-        UnixProcessManager.sendSignalToProcessTree(process, UnixProcessManager.SIGTERM);%0A        boolean exitWithinTimeout = process.waitFor(5, TimeUnit.SECONDS);%0A        if (!exitWithinTimeout) %7B%0A          listener.onTaskOutput(id, %22SIGTERM timed out. Force kill.%5Cn%22, true);%0A  
     
@@ -4926,26 +4926,16 @@
 troy();%0A
-        %7D%0A
         

Revert "send SIGTERM to pants for clean exit instead of force kill upon run cancellation"