/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * 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.
 */

#include "test/gtest_and_gmock.h"

#include "src/trace_processor/util/gzip_utils.h"

#include <zconf.h>
#include <zlib.h>
#include <cstddef>
#include <cstdint>
#include <fstream>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include "perfetto/base/logging.h"

using std::string;

namespace perfetto::trace_processor::util {

static std::string TrivialGzipCompress(const std::string& input) {
  constexpr auto buffer_len = 10000;
  std::unique_ptr<char[]> output_ptr(new char[buffer_len]);
  char* output = output_ptr.get();
  z_stream defstream;
  defstream.zalloc = Z_NULL;
  defstream.zfree = Z_NULL;
  defstream.opaque = Z_NULL;
  defstream.avail_in = uint32_t(input.size());
  defstream.next_in =
      const_cast<Bytef*>(reinterpret_cast<const Bytef*>(input.data()));
  defstream.avail_out = buffer_len;
  defstream.next_out = reinterpret_cast<Bytef*>(output);
  deflateInit(&defstream, Z_BEST_COMPRESSION);  // GZip decompress
  deflate(&defstream, Z_FINISH);
  deflateEnd(&defstream);
  PERFETTO_CHECK(defstream.avail_out > 0);
  return {output, buffer_len - defstream.avail_out};
}

// Trivially decompress using ZlibOnlineDecompress.
// It's called 'trivial' because we are feeding the entire input in one shot.
static std::string TrivialDecompress(const std::string& input) {
  string output;
  GzipDecompressor decompressor;
  decompressor.FeedAndExtract(
      reinterpret_cast<const uint8_t*>(input.data()), uint32_t(input.size()),
      [&](const uint8_t* data, size_t len) {
        output.append(reinterpret_cast<const char*>(data), len);
      });
  return output;
}

// Decompress a large GZip file using a in-memory buffer of 4KB, and write the
// decompressed output in another file.
static void DecompressGzipFileInFileOut(const std::string& input_file,
                                        const std::string& output_file) {
  std::ofstream output(output_file.c_str(), std::ios::out | std::ios::binary);
  std::ifstream input(input_file.c_str(), std::ios::binary);
  GzipDecompressor decompressor;
  constexpr uint32_t buffer_sizeof = 4096;
  char buffer[buffer_sizeof];
  while (!input.eof()) {
    input.read(buffer, buffer_sizeof);
    decompressor.FeedAndExtract(
        reinterpret_cast<const uint8_t*>(buffer), size_t(input.gcount()),
        [&](const uint8_t* data, size_t len) {
          output.write(reinterpret_cast<const char*>(data),
                       std::streamsize(len));
        });
  }
  EXPECT_FALSE(input.bad());
}

TEST(GzipDecompressor, Basic) {
  string input = "Abc..Def..Ghi";
  string compressed = TrivialGzipCompress(input);
  EXPECT_EQ(21u, compressed.size());
  string decompressed = TrivialDecompress(compressed);
  EXPECT_EQ(input, decompressed);
}

TEST(GzipDecompressor, Streaming) {
  string input = "Abc..Def..Ghi";
  string compressed = TrivialGzipCompress(input);
  string decompressed;
  auto consumer = [&](const uint8_t* data, size_t len) {
    decompressed.append(reinterpret_cast<const char*>(data), len);
  };
  GzipDecompressor decompressor;
  const auto* compressed_u8 =
      reinterpret_cast<const uint8_t*>(compressed.data());
  ASSERT_GT(compressed.size(), 17u);
  decompressor.FeedAndExtract(compressed_u8, 7, consumer);
  decompressor.FeedAndExtract(compressed_u8 + 7, 10, consumer);
  decompressor.FeedAndExtract(compressed_u8 + 17, compressed.size() - 17,
                              consumer);

  EXPECT_EQ(input, decompressed);
}

static std::string ReadFile(const std::string& file_name) {
  std::ifstream fd(file_name, std::ios::binary);
  std::stringstream buffer;
  buffer << fd.rdbuf();
  fd.close();
  return buffer.str();
}

static void WriteFile(const std::string& file_name,
                      const std::string& content) {
  std::ofstream fd(file_name, std::ios::out | std::ios::binary);
  fd.write(content.data(), std::streamsize(content.size()));
  fd.close();
}

TEST(GzipDecompressor, DISABLED_FileInFileOut) {
  auto big_string = []() {
    std::string output;
    for (int i = 0; i < 1000; i++) {
      output += "Abc..Def..Ghi.";  // len = 14
    }
    return output;
  }();
  constexpr auto gz_file = "/tmp/abc.gz";
  constexpr auto txt_file = "/tmp/abc.txt";
  EXPECT_EQ(size_t(1000 * 14), big_string.size());
  WriteFile(gz_file, TrivialGzipCompress(big_string));
  DecompressGzipFileInFileOut(gz_file, txt_file);
  EXPECT_TRUE(ReadFile(txt_file) == big_string);
}

}  // namespace perfetto::trace_processor::util
