// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/services/file_util/single_file_tar_file_extractor.h"

#include <utility>
#include <vector>

#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "chrome/services/file_util/public/mojom/constants.mojom.h"
#include "chrome/services/file_util/single_file_tar_reader.h"
#include "mojo/public/cpp/bindings/remote.h"

namespace {

constexpr int kTarBufferSize = 8192;

// TarExtractorInner extracts a .TAR file and writes the extracted data to the
// destination file.
class TarExtractorInner {
 public:
  TarExtractorInner(
      mojo::PendingRemote<chrome::mojom::SingleFileExtractorListener>
          pending_listener,
      base::File src_file,
      base::File dst_file)
      : listener_(std::move(pending_listener)),
        src_file_(std::move(src_file)),
        dst_file_(std::move(dst_file)) {}

  chrome::file_util::mojom::ExtractionResult Extract() {
    std::vector<uint8_t> tar_buffer;
    while (true) {
      tar_buffer = ReadTarFile();
      if (tar_buffer.empty()) {
        return chrome::file_util::mojom::ExtractionResult::kGenericError;
      }

      base::span<const uint8_t> output_file_content;
      if (!tar_reader_.ExtractChunk(base::span(tar_buffer),
                                    output_file_content)) {
        return chrome::file_util::mojom::ExtractionResult::kInvalidSrcFile;
      }

      // TODO(sahok): Utilize this logic and Deduplicate the same logic in
      // single_file_tar_xz_file_extractor.cc
      if (!dst_file_.WriteAtCurrentPosAndCheck(output_file_content))
        return chrome::file_util::mojom::ExtractionResult::kDstFileError;

      if (tar_reader_.IsComplete())
        return chrome::file_util::mojom::ExtractionResult::kSuccess;

      listener_->OnProgress(tar_reader_.tar_content_size().value(),
                            tar_reader_.bytes_processed());
    }
  }

  void CloseFiles() {
    src_file_.Close();
    dst_file_.Close();
  }

 private:
  // ReadTarFile reads .tar file and returns the contents. ReadTarFile reads
  // kTarBufferSize bytes at most, and might return an empty vector on error.
  // Returned vector is resized to actual bytes read.
  std::vector<uint8_t> ReadTarFile() {
    std::vector<uint8_t> tar_buffer(kTarBufferSize);
    const std::optional<size_t> bytes_read =
        src_file_.ReadAtCurrentPos(tar_buffer);

    if (bytes_read.value_or(0) == 0) {
      // After reading the last chunk of file content, it is expected that
      // tar_reader_.IsComplete() in the Extract function returns true and the
      // .tar.xz file extraction ends.
      return {};
    }
    tar_buffer.resize(*bytes_read);
    return tar_buffer;
  }

  const mojo::Remote<chrome::mojom::SingleFileExtractorListener> listener_;

  base::File src_file_;
  base::File dst_file_;

  SingleFileTarReader tar_reader_;
};
}  // namespace

SingleFileTarFileExtractor::SingleFileTarFileExtractor() = default;
SingleFileTarFileExtractor::~SingleFileTarFileExtractor() = default;

void SingleFileTarFileExtractor::Extract(
    base::File src_file,
    base::File dst_file,
    mojo::PendingRemote<chrome::mojom::SingleFileExtractorListener>
        pending_listener,
    SingleFileExtractor::ExtractCallback callback) {
  if (!src_file.IsValid() || !dst_file.IsValid()) {
    std::move(callback).Run(
        chrome::file_util::mojom::ExtractionResult::kGenericError);
    return;
  }
  TarExtractorInner extractor(std::move(pending_listener), std::move(src_file),
                              std::move(dst_file));
  chrome::file_util::mojom::ExtractionResult result = extractor.Extract();

  // Explicitly close the files before calling `callback` to ensure all
  // extracted data is flushed to the file and the file is closed.
  extractor.CloseFiles();
  std::move(callback).Run(result);
}
