/*
 * Copyright 2013-present Facebook, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License. You may obtain
 * a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

package com.facebook.buck.cli;

import com.facebook.buck.command.Build;
import com.facebook.buck.event.ConsoleEvent;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.parser.NoSuchBuildTargetException;
import com.facebook.buck.rules.BinaryBuildRule;
import com.facebook.buck.rules.BuildRule;
import com.facebook.buck.rules.SourcePathResolver;
import com.facebook.buck.rules.Tool;
import com.facebook.buck.util.ForwardingProcessListener;
import com.facebook.buck.util.HumanReadableException;
import com.facebook.buck.util.ListeningProcessExecutor;
import com.facebook.buck.util.ProcessExecutorParams;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;

import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;

import java.io.IOException;
import java.nio.channels.Channels;
import java.util.List;

public class RunCommand extends AbstractCommand {

  /**
   * Expected usage:
   * <pre>
   *   buck run //java/com/facebook/tools/munge:munge --mungearg /tmp/input
   * </pre>
   */
  @Argument
  private List<String> noDashArguments = Lists.newArrayList();

  @Option(name = "--", handler = ConsumeAllOptionsHandler.class)
  private List<String> withDashArguments = Lists.newArrayList();

  private Supplier<ImmutableList<String>> arguments = Suppliers.memoize(
    new Supplier<ImmutableList<String>>() {
      @Override
      public ImmutableList<String> get() {
        ImmutableList.Builder<String> builder = new ImmutableList.Builder<>();
        builder.addAll(noDashArguments);
        builder.addAll(withDashArguments);
        return builder.build();
      }
    });

  public ImmutableList<String> getArguments() {
    return arguments.get();
  }

  /** @return the arguments (if any) to be passed to the target command. */
  public ImmutableList<String> getTargetArguments() {
    return arguments.get().subList(1, arguments.get().size());
  }

  public boolean hasTargetSpecified() {
    return arguments.get().size() > 0;
  }

  /** @return the normalized target name for command to run. */
  public String getTarget(BuckConfig buckConfig) {
      return getCommandLineBuildTargetNormalizer(buckConfig).normalize(arguments.get().get(0));
  }

  @VisibleForTesting
  void setArguments(ImmutableList<String> arguments) {
    this.arguments = Suppliers.ofInstance(arguments);
  }

  @Override
  public String getShortDescription() {
    return "runs a target as a command";
  }

  @Override
  public int runWithoutHelp(CommandRunnerParams params) throws IOException, InterruptedException {
    if (!hasTargetSpecified()) {
      params.getBuckEventBus().post(ConsoleEvent.severe(
          "No target given to run"));
      params.getBuckEventBus().post(ConsoleEvent.severe(
          "buck run <target> <arg1> <arg2>..."));
      return 1;
    }

    // Make sure the target is built.
    BuildCommand buildCommand = new BuildCommand(
        ImmutableList.of(getTarget(params.getBuckConfig())));
    int exitCode = buildCommand.runWithoutHelp(params);
    if (exitCode != 0) {
      return exitCode;
    }

    String targetName = getTarget(params.getBuckConfig());
    BuildTarget target = Iterables.getOnlyElement(
        getBuildTargets(
            params.getCell().getCellRoots(),
            ImmutableSet.of(targetName)));

    Build build = buildCommand.getBuild();
    BuildRule targetRule;
    try {
      targetRule = build.getRuleResolver().requireRule(target);
    } catch (NoSuchBuildTargetException e) {
      throw new HumanReadableException(e.getHumanReadableErrorMessage());
    }
    BinaryBuildRule binaryBuildRule = null;
    if (targetRule instanceof BinaryBuildRule) {
      binaryBuildRule = (BinaryBuildRule) targetRule;
    }
    if (binaryBuildRule == null) {
      params.getBuckEventBus().post(ConsoleEvent.severe(
          "target " + targetName + " is not a binary rule (only binary rules can be `run`)"));
      return 1;
    }

    // Ideally, we would take fullCommand, disconnect from NailGun, and run the command in the
    // user's shell. Currently, if you use `buck run` with buckd and ctrl-C to kill the command
    // being run, occasionally I get the following error when I try to run `buck run` again:
    //
    //   Daemon is busy, please wait or run "buck kill" to terminate it.
    //
    // Clearly something bad has happened here. If you are using `buck run` to start up a server
    // or some other process that is meant to "run forever," then it's pretty common to do:
    // `buck run`, test server, hit ctrl-C, edit server code, repeat. This should not wedge buckd.
    SourcePathResolver resolver = new SourcePathResolver(build.getRuleResolver());
    Tool executable = binaryBuildRule.getExecutableCommand();
    ListeningProcessExecutor processExecutor = new ListeningProcessExecutor();
    ProcessExecutorParams processExecutorParams =
        ProcessExecutorParams.builder()
            .addAllCommand(executable.getCommandPrefix(resolver))
            .addAllCommand(getTargetArguments())
            .setEnvironment(
                ImmutableMap.<String, String>builder()
                    .putAll(params.getEnvironment())
                    .putAll(executable.getEnvironment(resolver))
                    .build())
            .setDirectory(params.getCell().getFilesystem().getRootPath())
            .build();
    ForwardingProcessListener processListener =
        new ForwardingProcessListener(
            Channels.newChannel(params.getConsole().getStdOut()),
            Channels.newChannel(params.getConsole().getStdErr()));
    ListeningProcessExecutor.LaunchedProcess process =
        processExecutor.launchProcess(processExecutorParams, processListener);
    try {
      return processExecutor.waitForProcess(process);
    } finally {
      processExecutor.destroyProcess(process, /* force */ false);
      processExecutor.waitForProcess(process);
    }
  }

  @Override
  public boolean isReadOnly() {
    return false;
  }

}

@@ -1748,16 +1748,22 @@
 %0Apublic 
+final 
 class Ru
@@ -1797,16 +1797,16 @@
 mmand %7B%0A
-
 %0A  /**%0A 
@@ -2147,16 +2147,22 @@
 private 
+final 
 Supplier
@@ -2533,22 +2533,36 @@
  %7D);%0A%0A  
-public
+@VisibleForTesting%0A 
  Immutab
@@ -2705,21 +2705,22 @@
 . */%0A  p
-ublic
+rivate
  Immutab
@@ -2824,29 +2824,30 @@
 ));%0A  %7D%0A%0A  p
-ublic
+rivate
  boolean has
@@ -2973,29 +2973,30 @@
  run. */%0A  p
-ublic
+rivate
  String getT
@@ -3131,143 +3131,8 @@
  %7D%0A%0A
-  @VisibleForTesting%0A  void setArguments(ImmutableList%3CString%3E arguments) %7B%0A    this.arguments = Suppliers.ofInstance(arguments);%0A  %7D%0A%0A
   @O

clean up visibility and remove unused code in RunCommand