//
// Copyright (C) 2011 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 <string.h>
#include <unistd.h>

#include <memory>
#include <string>
#include <vector>

#include <gtest/gtest.h>

#include "update_engine/common/test_utils.h"
#include "update_engine/payload_consumer/bzip_extent_writer.h"
#include "update_engine/payload_consumer/extent_writer.h"
#include "update_engine/payload_consumer/xz_extent_writer.h"
#include "update_engine/payload_generator/bzip.h"
#include "update_engine/payload_generator/xz.h"

using chromeos_update_engine::test_utils::kRandomString;
using google::protobuf::RepeatedPtrField;
using std::string;
using std::vector;

namespace chromeos_update_engine {

namespace {

// ExtentWriter class that writes to memory, used to test the decompression
// step with the corresponding extent writer.
class MemoryExtentWriter : public ExtentWriter {
 public:
  // Creates the ExtentWriter that will write all the bytes to the passed |data|
  // blob.
  explicit MemoryExtentWriter(brillo::Blob* data) : data_(data) {
    data_->clear();
  }
  ~MemoryExtentWriter() override = default;

  bool Init(FileDescriptorPtr fd,
            const RepeatedPtrField<Extent>& extents,
            uint32_t block_size) override {
    return true;
  }
  bool Write(const void* bytes, size_t count) override {
    data_->reserve(data_->size() + count);
    data_->insert(data_->end(),
                  static_cast<const uint8_t*>(bytes),
                  static_cast<const uint8_t*>(bytes) + count);
    return true;
  }

 private:
  brillo::Blob* data_;
};

template <typename W>
bool DecompressWithWriter(const brillo::Blob& in, brillo::Blob* out) {
  std::unique_ptr<ExtentWriter> writer(
      new W(std::make_unique<MemoryExtentWriter>(out)));
  // Init() parameters are ignored by the testing MemoryExtentWriter.
  bool ok = writer->Init(nullptr, {}, 1);
  ok = writer->Write(in.data(), in.size()) && ok;
  return ok;
}

}  // namespace

template <typename T>
class ZipTest : public ::testing::Test {
 public:
  bool ZipCompress(const brillo::Blob& in, brillo::Blob* out) const = 0;
  bool ZipDecompress(const brillo::Blob& in, brillo::Blob* out) const = 0;
};

class BzipTest {};

template <>
class ZipTest<BzipTest> : public ::testing::Test {
 public:
  bool ZipCompress(const brillo::Blob& in, brillo::Blob* out) const {
    return BzipCompress(in, out);
  }
  bool ZipDecompress(const brillo::Blob& in, brillo::Blob* out) const {
    return DecompressWithWriter<BzipExtentWriter>(in, out);
  }
};

class XzTest {};

template <>
class ZipTest<XzTest> : public ::testing::Test {
 public:
  bool ZipCompress(const brillo::Blob& in, brillo::Blob* out) const {
    return XzCompress(in, out);
  }
  bool ZipDecompress(const brillo::Blob& in, brillo::Blob* out) const {
    return DecompressWithWriter<XzExtentWriter>(in, out);
  }
};

typedef ::testing::Types<BzipTest, XzTest> ZipTestTypes;

TYPED_TEST_CASE(ZipTest, ZipTestTypes);

TYPED_TEST(ZipTest, SimpleTest) {
  string in_str(
      "this should compress well xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
      "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
      "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
      "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
      "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
      "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
  brillo::Blob in(in_str.begin(), in_str.end());
  brillo::Blob out;
  EXPECT_TRUE(this->ZipCompress(in, &out));
  EXPECT_LT(out.size(), in.size());
  EXPECT_GT(out.size(), 0U);
  brillo::Blob decompressed;
  EXPECT_TRUE(this->ZipDecompress(out, &decompressed));
  EXPECT_EQ(in.size(), decompressed.size());
  EXPECT_EQ(0, memcmp(in.data(), decompressed.data(), in.size()));
}

TYPED_TEST(ZipTest, PoorCompressionTest) {
  brillo::Blob in(std::begin(kRandomString), std::end(kRandomString));
  brillo::Blob out;
  EXPECT_TRUE(this->ZipCompress(in, &out));
  EXPECT_GT(out.size(), in.size());
  brillo::Blob decompressed;
  EXPECT_TRUE(this->ZipDecompress(out, &decompressed));
  EXPECT_EQ(in.size(), decompressed.size());
  EXPECT_EQ(in, decompressed);
}

TYPED_TEST(ZipTest, MalformedZipTest) {
  brillo::Blob in(std::begin(kRandomString), std::end(kRandomString));
  brillo::Blob out;
  EXPECT_FALSE(this->ZipDecompress(in, &out));
}

TYPED_TEST(ZipTest, EmptyInputsTest) {
  brillo::Blob in;
  brillo::Blob out;
  EXPECT_TRUE(this->ZipDecompress(in, &out));
  EXPECT_EQ(0U, out.size());

  EXPECT_TRUE(this->ZipCompress(in, &out));
  EXPECT_EQ(0U, out.size());
}

TYPED_TEST(ZipTest, CompressELFTest) {
  string path = test_utils::GetBuildArtifactsPath("delta_generator");
  brillo::Blob in;
  utils::ReadFile(path, &in);
  brillo::Blob out;
  EXPECT_TRUE(this->ZipCompress(in, &out));
  EXPECT_LT(out.size(), in.size());
  EXPECT_GT(out.size(), 0U);
  brillo::Blob decompressed;
  EXPECT_TRUE(this->ZipDecompress(out, &decompressed));
  EXPECT_EQ(in.size(), decompressed.size());
  EXPECT_EQ(0, memcmp(in.data(), decompressed.data(), in.size()));
}

}  // namespace chromeos_update_engine