/*
 * Copyright (C) 2016 Google 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.google.copybara.util;

import com.google.common.base.Stopwatch;
import com.google.devtools.build.lib.shell.BadExitStatusException;
import com.google.devtools.build.lib.shell.Command;
import com.google.devtools.build.lib.shell.CommandException;
import com.google.devtools.build.lib.shell.CommandResult;
import com.google.devtools.build.lib.shell.ShellUtils;
import com.google.devtools.build.lib.shell.SimpleKillableObserver;
import com.google.devtools.build.lib.shell.TerminationStatus;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * An utility class for executing commands and logging the output appropriately.
 */
public final class CommandUtil {

  private static final Logger logger = Logger.getLogger(CommandUtil.class.getName());
  private static final byte[] NO_INPUT = new byte[]{};

  private CommandUtil() {}

  /**
   * Executes a {@link Command} and writes to the console and the log depending on the exit code of
   * the command and the verbose flag.
   */
  public static CommandOutputWithStatus executeCommand(Command cmd, boolean verbose)
      throws CommandException {
    return executeCommand(cmd, NO_INPUT, verbose);
  }

  /**
   * Executes a {@link Command} with the given input and writes to the console and the log depending
   * on the exit code of the command and the verbose flag.
   */
  public static CommandOutputWithStatus executeCommand(
      Command cmd, byte[] input, boolean verbose) throws CommandException {
    Stopwatch stopwatch = Stopwatch.createStarted();
    String startMsg = "Executing ["
        + ShellUtils.prettyPrintArgv(Arrays.asList(cmd.getCommandLineElements())) + "]";
    logger.log(Level.INFO, startMsg);
    if (verbose) {
      System.err.println(startMsg);
    }
    ByteArrayOutputStream stdoutCollector = new ByteArrayOutputStream();
    ByteArrayOutputStream stderrCollector = new ByteArrayOutputStream();

    CommandResult cmdResult;

    TerminationStatus exitStatus = null;
    try {
      cmdResult = cmd.execute(input, new SimpleKillableObserver(),
          // If verbose we stream to the user console too
          verbose ? new DemultiplexOutputStream(System.err, stdoutCollector) : stdoutCollector,
          verbose ? new DemultiplexOutputStream(System.err, stderrCollector) : stderrCollector,
          true);
      exitStatus = cmdResult.getTerminationStatus();
      return new CommandOutputWithStatus(
          cmdResult.getTerminationStatus(),
          stdoutCollector.toByteArray(),
          stderrCollector.toByteArray());
    } catch (BadExitStatusException e) {
      exitStatus = e.getResult().getTerminationStatus();
      throw new BadExitStatusWithOutputException(e.getCommand(), e.getResult(), e.getMessage(),
          stdoutCollector.toByteArray(),
          stderrCollector.toByteArray());
    } finally {
      String finishMsg = "Command '" + cmd.getCommandLineElements()[0] + "' finished in "
          + stopwatch + ". " + (exitStatus != null ? exitStatus.toString() : "(No exit status)");

      boolean success = exitStatus != null && exitStatus.success();
      Level logLevel = success ? Level.INFO : Level.SEVERE;

      logOutput(logLevel, cmd, "STDOUT", stdoutCollector);
      logOutput(logLevel, cmd, "STDERR", stderrCollector);
      logger.log(logLevel, finishMsg);

      if (verbose) {
        System.err.println(finishMsg);
      }
    }
  }

  /**
   * Log to the appropiate log level the output of the command
   */
  private static void logOutput(Level level, Command cmd, final String outputType,
      ByteArrayOutputStream outputBytes) {

    String string = asString(outputBytes).trim();
    if (string.isEmpty()) {
      return;
    }
    for (String line : string.split(System.lineSeparator())) {
      logger.log(level, "'" + cmd.getCommandLineElements()[0] + "' " + outputType + ": " + line);
    }
  }

  private static String asString(ByteArrayOutputStream outputBytes) {
    return new String(outputBytes.toByteArray(), StandardCharsets.UTF_8);
  }

  /**
   * An {@link OutputStream} that can output to two {@code OutputStream}
   */
  private static class DemultiplexOutputStream extends OutputStream {

    private final OutputStream s1;
    private final OutputStream s2;

    private DemultiplexOutputStream(OutputStream s1, OutputStream s2) {
      this.s1 = s1;
      this.s2 = s2;
    }

    @Override
    public void write(int b) throws IOException {
      s1.write(b);
      s2.write(b);
    }

    @Override
    public void write(byte[] b, int off, int len) throws IOException {
      s1.write(b, off, len);
      s2.write(b, off, len);
    }

    @Override
    public void flush() throws IOException {
      s1.flush();
      s2.flush();
    }

    @Override
    public void close() throws IOException {
      IOException ex = null;
      try {
        s1.close();
      } catch (IOException e) {
        ex = e;
      }
      // We favor the second exception over the first one on close. But this is fine for now.
      s2.close();
      if (ex != null) {
        throw ex;
      }
    }
  }
}

@@ -3790,155 +3790,28 @@
     
-boolean success = exitStatus != null && exitStatus.success();%0A      Level logLevel = success ? Level.INFO : Level.SEVERE;%0A%0A      logOutput(logLevel
+logOutput(Level.INFO
 , cm
@@ -3857,24 +3857,26 @@
 gOutput(
-log
 Level
+.INFO
 , cmd, %22
@@ -3923,16 +3923,18 @@
 log(
-log
 Level
+.INFO
 , fi

Replace SEVERE by INFO for logging command failures