/*
 * Copyright 2012-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.step.fs;

import com.facebook.buck.io.filesystem.ProjectFilesystem;
import com.facebook.buck.step.ExecutionContext;
import com.facebook.buck.step.Step;
import com.facebook.buck.step.StepExecutionResult;
import com.facebook.buck.step.StepExecutionResults;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.io.ByteStreams;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.tukaani.xz.LZMA2Options;
import org.tukaani.xz.XZ;
import org.tukaani.xz.XZOutputStream;

/**
 * A {@link Step} to compress a file with XZ / LZMA2.
 *
 * @see <a href="http://tukaani.org/xz/">XZ</a>
 * @see <a href="http://tukaani.org/xz/java.html">XZ for Java</a>
 * @see <a href="http://tukaani.org/xz/embedded.html">XZ Embedded</a>
 */
public class XzStep implements Step {
  public static final int DEFAULT_COMPRESSION_LEVEL = 4;

  private final ProjectFilesystem filesystem;
  private final Path sourceFile;
  private final Path destinationFile;
  private final int compressionLevel;
  private final boolean keep;
  private final int check;

  /**
   * Create an {@link XzStep} to compress a file using XZ.
   *
   * @param sourceFile file to compress
   * @param destinationFile where to store compressed data
   * @param compressionLevel a value between 0-9, it impacts memory requirements for decompression
   * @param keep by default, {@code xz} deletes the source file when compressing. This argument
   *     forces {@code xz} to keep it.
   * @param check integrity check to use. Must be one of {@link XZ#CHECK_CRC32}, {@link
   *     XZ#CHECK_CRC64}, {@link XZ#CHECK_SHA256}, {@link XZ#CHECK_NONE} (Note: XZ Embedded can only
   *     verify CRC32).
   */
  @VisibleForTesting
  XzStep(
      ProjectFilesystem filesystem,
      Path sourceFile,
      Path destinationFile,
      int compressionLevel,
      boolean keep,
      int check) {
    this.filesystem = filesystem;
    this.sourceFile = sourceFile;
    this.destinationFile = destinationFile;
    Preconditions.checkArgument(
        compressionLevel >= LZMA2Options.PRESET_MIN && compressionLevel <= LZMA2Options.PRESET_MAX,
        "compressionLevel out of bounds.");
    this.compressionLevel = compressionLevel;
    this.keep = keep;
    this.check = check;
  }

  /**
   * Creates an XzStep to compress a file with XZ compression level 4.
   *
   * <p>The destination file will be {@code sourceFile} with the added {@code .xz} extension.
   *
   * <p>Decompression will require 5MiB of RAM.
   *
   * @param sourceFile file to compress
   */
  public XzStep(ProjectFilesystem filesystem, Path sourceFile) {
    this(filesystem, sourceFile, DEFAULT_COMPRESSION_LEVEL);
  }

  /**
   * Creates an XzStep to compress a file with XZ compression level 4.
   *
   * <p>Decompression will require 5MiB of RAM.
   *
   * @param sourceFile file to compress
   * @param outputPath desired output path
   */
  public XzStep(ProjectFilesystem filesystem, Path sourceFile, Path outputPath) {
    this(filesystem, sourceFile, outputPath, DEFAULT_COMPRESSION_LEVEL);
  }

  /**
   * Creates an XzStep to compress a file with the given XZ compression level and output path.
   *
   * <p>Decompression will require up to 64MiB of RAM.
   *
   * @param sourceFile file to compress
   * @param outputPath the desired output path.
   * @param compressionLevel level of compression (from 0-9)
   */
  public XzStep(
      ProjectFilesystem filesystem, Path sourceFile, Path outputPath, int compressionLevel) {
    this(filesystem, sourceFile, outputPath, compressionLevel, /* keep */ false, XZ.CHECK_CRC32);
  }

  /**
   * Creates an XzStep to compress a file with XZ at a user supplied compression level .
   *
   * <p>The destination file will be {@code sourceFile} with the added {@code .xz} extension.
   *
   * <p>Decompression will require up to 64MiB of RAM.
   *
   * @param sourceFile file to compress
   * @param compressionLevel value from 0 to 9. Higher values result in better compression, but also
   *     need more time to compress and will need more RAM to decompress.
   */
  public XzStep(ProjectFilesystem filesystem, Path sourceFile, int compressionLevel) {
    this(
        filesystem,
        sourceFile,
        Paths.get(sourceFile + ".xz"),
        compressionLevel,
        /* keep */ false,
        XZ.CHECK_CRC32);
  }

  @Override
  public StepExecutionResult execute(ExecutionContext context)
      throws IOException, InterruptedException {
    boolean deleteSource = false;
    try (InputStream in = filesystem.newFileInputStream(sourceFile);
        OutputStream out = filesystem.newFileOutputStream(destinationFile);
        XZOutputStream xzOut = new XZOutputStream(out, new LZMA2Options(compressionLevel), check)) {
      XzMemorySemaphore.acquireMemory(compressionLevel);
      ByteStreams.copy(in, xzOut);
      xzOut.finish();
      deleteSource = !keep;
    } finally {
      XzMemorySemaphore.releaseMemory(compressionLevel);
    }
    if (deleteSource) {
      filesystem.deleteFileAtPath(sourceFile);
    }
    return StepExecutionResults.SUCCESS;
  }

  public Path getDestinationFile() {
    return destinationFile;
  }

  @Override
  public String getDescription(ExecutionContext context) {
    return Joiner.on(" ")
        .skipNulls()
        .join(
            "xz",
            "-z",
            "-" + compressionLevel,
            (keep ? "--keep" : null),
            "--check=crc32",
            sourceFile);
  }

  @Override
  public String getShortName() {
    return "xz";
  }
}

@@ -3119,33 +3119,72 @@
 mpression level 
-4
+%7B@value%0A   * #DEFAULT_COMPRESSION_LEVEL%7D
 .%0A   *%0A   * %3Cp%3ET
@@ -3433,24 +3433,24 @@
 urceFile) %7B%0A
+
     this(fil
@@ -3507,392 +3507,8 @@
  %7D%0A%0A
-  /**%0A   * Creates an XzStep to compress a file with XZ compression level 4.%0A   *%0A   * %3Cp%3EDecompression will require 5MiB of RAM.%0A   *%0A   * @param sourceFile file to compress%0A   * @param outputPath desired output path%0A   */%0A  public XzStep(ProjectFilesystem filesystem, Path sourceFile, Path outputPath) %7B%0A    this(filesystem, sourceFile, outputPath, DEFAULT_COMPRESSION_LEVEL);%0A  %7D%0A%0A
   /*

Remove unused constructor.