// Copyright 2000-2020 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.execution.target;

import com.intellij.execution.CommandLineUtil;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.target.value.TargetValue;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.execution.ParametersListUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.concurrency.Promise;

import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeoutException;

/**
 * Command line that can be executed on any {@link TargetEnvironment}.
 * <p>
 * Exe-path, working-directory and other properties are initialized in a lazy way,
 * that allows to create a command line before creating a target where it should be run.
 *
 * @see TargetedCommandLineBuilder
 */
public final class TargetedCommandLine {
  @NotNull private final TargetValue<String> myExePath;
  @NotNull private final TargetValue<String> myWorkingDirectory;
  @NotNull private final TargetValue<String> myInputFilePath;
  @NotNull private final Charset myCharset;
  @NotNull private final List<TargetValue<String>> myParameters;
  @NotNull private final Map<String, TargetValue<String>> myEnvironment;

  public TargetedCommandLine(@NotNull TargetValue<String> exePath,
                             @NotNull TargetValue<String> workingDirectory,
                             @NotNull TargetValue<String> inputFilePath,
                             @NotNull Charset charset,
                             @NotNull List<TargetValue<String>> parameters,
                             @NotNull Map<String, TargetValue<String>> environment) {
    myExePath = exePath;
    myWorkingDirectory = workingDirectory;
    myInputFilePath = inputFilePath;
    myCharset = charset;
    myParameters = parameters;
    myEnvironment = environment;
  }

  /**
   * {@link GeneralCommandLine#getPreparedCommandLine()}
   */
  public String getCommandPresentation(@NotNull TargetEnvironment target) throws ExecutionException {
    String exePath = resolvePromise(myExePath.getTargetValue(), "exe path");
    if (exePath == null) {
      throw new ExecutionException("Resolved value for exe path is null");
    }
    List<String> parameters = new ArrayList<>();
    for (TargetValue<String> parameter : myParameters) {
      parameters.add(resolvePromise(parameter.getTargetValue(), "parameter"));
    }
    return StringUtil.join(CommandLineUtil.toCommandLine(ParametersListUtil.escape(exePath), parameters,
                                                         target.getRemotePlatform().getPlatform()), " ");
  }

  public List<String> collectCommandsSynchronously() throws ExecutionException {
    String command = resolvePromise(myExePath.getTargetValue(), "exe path");
    if (command == null) {
      throw new ExecutionException("Resolved value for exe path is null");
    }

    List<String> commandLine = new ArrayList<>(myParameters.size() + 1);

    commandLine.add(command);

    for (TargetValue<String> parameter : myParameters) {
      commandLine.add(resolvePromise(parameter.getTargetValue(), "parameter"));
    }

    return commandLine;
  }

  @Nullable
  public String getWorkingDirectory() throws ExecutionException {
    return resolvePromise(myWorkingDirectory.getTargetValue(), "working directory");
  }

  @Nullable
  public String getInputFilePath() throws ExecutionException {
    return resolvePromise(myInputFilePath.getTargetValue(), "input file path");
  }

  @NotNull
  public Map<String, String> getEnvironmentVariables() throws ExecutionException {
    Map<String, String> result = new LinkedHashMap<>();
    for (Map.Entry<String, TargetValue<String>> e : myEnvironment.entrySet()) {
      result.put(e.getKey(), resolvePromise(e.getValue().getTargetValue(), "environment variable " + e.getKey()));
    }
    return result;
  }

  @NotNull
  public Charset getCharset() {
    return myCharset;
  }

  @Nullable
  private static String resolvePromise(@NotNull Promise<String> promise, @NotNull String debugName)
    throws ExecutionException {
    try {
      return promise.blockingGet(0);
    }
    catch (java.util.concurrent.ExecutionException | TimeoutException e) {
      throw new ExecutionException("Couldn't resolve promise for " + debugName, e);
    }
  }
}

@@ -628,122 +628,82 @@
 se;%0A
-%0A
 import 
-java.nio.charset.Charset;%0Aimport java.util.ArrayList;%0Aimport java.util.LinkedHashMap;%0Aimport java.util.Lis
+org.jetbrains.concurrency.Promises;%0A%0Aimport java.nio.charset.Charse
 t;%0Ai
@@ -718,19 +718,17 @@
 va.util.
-Map
+*
 ;%0Aimport
@@ -2955,295 +2955,588 @@
     
-S
 tr
-ing command = resolvePromise(myExePath.getTargetValue(), %22exe path%22);%0A    if (command == null) %7B%0A      throw new ExecutionException(%22Resolved value for exe path is null%22);%0A    %7D%0A%0A    List%3C
+y %7B%0A      return collectCommands().blockingGet(0);%0A    %7D%0A    catch (java.util.concurrent.ExecutionException %7C TimeoutException e) %7B%0A      throw new ExecutionException(%22Couldn't collect commands%22, e);%0A    %7D%0A  %7D%0A%0A  public @NotNull Promise%3C@NotNull List%3C@NotNull 
 String%3E
+%3E
  co
-mmandLine = new ArrayList%3C%3E(myParameters.size() + 1);%0A%0A    commandLine.add(command
+llectCommands() %7B%0A    List%3CPromise%3CString%3E%3E promises = new ArrayList%3C%3E(myParameters.size() + 1);%0A    promises.add(myExePath.getTargetValue().then(command -%3E %7B%0A      if (command == null) %7B%0A        throw new IllegalStateException(%22Resolved value for exe path is null%22);%0A      %7D%0A      return command;%0A    %7D)
 );%0A
-%0A
     
@@ -3598,38 +3598,20 @@
     
-commandLine.add(resolvePromise
+promises.add
 (par
@@ -3629,38 +3629,24 @@
 argetValue()
-, %22parameter%22)
 );%0A    %7D%0A%0A  
@@ -3642,17 +3642,16 @@
 ;%0A    %7D%0A
-%0A
     retu
@@ -3657,19 +3657,41 @@
 urn 
-commandLine
+Promises.collectResults(promises)
 ;%0A  

Introduce asynchronous TargetedCommandLine.collectCommands.