//  Copyright (c) 2011-present, Facebook, Inc.  All rights reserved.
//  This source code is licensed under both the GPLv2 (found in the
//  COPYING file in the root directory) and Apache 2.0 License
//  (found in the LICENSE.Apache file in the root directory).
//
#ifndef ROCKSDB_LITE

#include <vector>
#include "util/testharness.h"
#include "util/testutil.h"
#include "utilities/col_buf_decoder.h"
#include "utilities/col_buf_encoder.h"

namespace rocksdb {

class ColumnAwareEncodingTest : public testing::Test {
 public:
  ColumnAwareEncodingTest() {}

  ~ColumnAwareEncodingTest() {}
};

class ColumnAwareEncodingTestWithSize
    : public ColumnAwareEncodingTest,
      public testing::WithParamInterface<size_t> {
 public:
  ColumnAwareEncodingTestWithSize() {}

  ~ColumnAwareEncodingTestWithSize() {}

  static std::vector<size_t> GetValues() { return {4, 8}; }
};

INSTANTIATE_TEST_CASE_P(
    ColumnAwareEncodingTestWithSize, ColumnAwareEncodingTestWithSize,
    ::testing::ValuesIn(ColumnAwareEncodingTestWithSize::GetValues()));

TEST_P(ColumnAwareEncodingTestWithSize, NoCompressionEncodeDecode) {
  size_t col_size = GetParam();
  std::unique_ptr<ColBufEncoder> col_buf_encoder(
      new FixedLengthColBufEncoder(col_size, kColNoCompression, false, true));
  std::string str_buf;
  uint64_t base_val = 0x0102030405060708;
  uint64_t val = 0;
  memcpy(&val, &base_val, col_size);
  const int row_count = 4;
  for (int i = 0; i < row_count; ++i) {
    str_buf.append(reinterpret_cast<char*>(&val), col_size);
  }
  const char* str_buf_ptr = str_buf.c_str();
  for (int i = 0; i < row_count; ++i) {
    col_buf_encoder->Append(str_buf_ptr);
  }
  col_buf_encoder->Finish();
  const std::string& encoded_data = col_buf_encoder->GetData();
  // Check correctness of encoded string length
  ASSERT_EQ(row_count * col_size, encoded_data.size());

  const char* encoded_data_ptr = encoded_data.c_str();
  uint64_t expected_encoded_val;
  if (col_size == 8) {
    expected_encoded_val = port::kLittleEndian ? 0x0807060504030201 : 0x0102030405060708;
  } else if (col_size == 4) {
    expected_encoded_val = port::kLittleEndian ? 0x08070605 : 0x0102030400000000;
  }
  uint64_t encoded_val = 0;
  for (int i = 0; i < row_count; ++i) {
    memcpy(&encoded_val, encoded_data_ptr, col_size);
    // Check correctness of encoded value
    ASSERT_EQ(expected_encoded_val, encoded_val);
    encoded_data_ptr += col_size;
  }

  std::unique_ptr<ColBufDecoder> col_buf_decoder(
      new FixedLengthColBufDecoder(col_size, kColNoCompression, false, true));
  encoded_data_ptr = encoded_data.c_str();
  encoded_data_ptr += col_buf_decoder->Init(encoded_data_ptr);
  char* decoded_data = new char[100];
  char* decoded_data_base = decoded_data;
  for (int i = 0; i < row_count; ++i) {
    encoded_data_ptr +=
        col_buf_decoder->Decode(encoded_data_ptr, &decoded_data);
  }

  // Check correctness of decoded string length
  ASSERT_EQ(row_count * col_size, decoded_data - decoded_data_base);
  decoded_data = decoded_data_base;
  for (int i = 0; i < row_count; ++i) {
    uint64_t decoded_val;
    decoded_val = 0;
    memcpy(&decoded_val, decoded_data, col_size);
    // Check correctness of decoded value
    ASSERT_EQ(val, decoded_val);
    decoded_data += col_size;
  }
  delete[] decoded_data_base;
}

TEST_P(ColumnAwareEncodingTestWithSize, RleEncodeDecode) {
  size_t col_size = GetParam();
  std::unique_ptr<ColBufEncoder> col_buf_encoder(
      new FixedLengthColBufEncoder(col_size, kColRle, false, true));
  std::string str_buf;
  uint64_t base_val = 0x0102030405060708;
  uint64_t val = 0;
  memcpy(&val, &base_val, col_size);
  const int row_count = 4;
  for (int i = 0; i < row_count; ++i) {
    str_buf.append(reinterpret_cast<char*>(&val), col_size);
  }
  const char* str_buf_ptr = str_buf.c_str();
  for (int i = 0; i < row_count; ++i) {
    str_buf_ptr += col_buf_encoder->Append(str_buf_ptr);
  }
  col_buf_encoder->Finish();
  const std::string& encoded_data = col_buf_encoder->GetData();
  // Check correctness of encoded string length
  ASSERT_EQ(col_size + 1, encoded_data.size());

  const char* encoded_data_ptr = encoded_data.c_str();
  uint64_t encoded_val = 0;
  memcpy(&encoded_val, encoded_data_ptr, col_size);
  uint64_t expected_encoded_val;
  if (col_size == 8) {
    expected_encoded_val = port::kLittleEndian ? 0x0807060504030201 : 0x0102030405060708;
  } else if (col_size == 4) {
    expected_encoded_val = port::kLittleEndian ? 0x08070605 : 0x0102030400000000;
  }
  // Check correctness of encoded value
  ASSERT_EQ(expected_encoded_val, encoded_val);

  std::unique_ptr<ColBufDecoder> col_buf_decoder(
      new FixedLengthColBufDecoder(col_size, kColRle, false, true));
  char* decoded_data = new char[100];
  char* decoded_data_base = decoded_data;
  encoded_data_ptr += col_buf_decoder->Init(encoded_data_ptr);
  for (int i = 0; i < row_count; ++i) {
    encoded_data_ptr +=
        col_buf_decoder->Decode(encoded_data_ptr, &decoded_data);
  }
  // Check correctness of decoded string length
  ASSERT_EQ(decoded_data - decoded_data_base, row_count * col_size);
  decoded_data = decoded_data_base;
  for (int i = 0; i < row_count; ++i) {
    uint64_t decoded_val;
    decoded_val = 0;
    memcpy(&decoded_val, decoded_data, col_size);
    // Check correctness of decoded value
    ASSERT_EQ(val, decoded_val);
    decoded_data += col_size;
  }
  delete[] decoded_data_base;
}

TEST_P(ColumnAwareEncodingTestWithSize, DeltaEncodeDecode) {
  size_t col_size = GetParam();
  int row_count = 4;
  std::unique_ptr<ColBufEncoder> col_buf_encoder(
      new FixedLengthColBufEncoder(col_size, kColDeltaVarint, false, true));
  std::string str_buf;
  uint64_t base_val1 = port::kLittleEndian ? 0x0102030405060708 : 0x0807060504030201;
  uint64_t base_val2 = port::kLittleEndian ? 0x0202030405060708 : 0x0807060504030202;
  uint64_t val1 = 0, val2 = 0;
  memcpy(&val1, &base_val1, col_size);
  memcpy(&val2, &base_val2, col_size);
  const char* str_buf_ptr;
  for (int i = 0; i < row_count / 2; ++i) {
    str_buf = std::string(reinterpret_cast<char*>(&val1), col_size);
    str_buf_ptr = str_buf.c_str();
    col_buf_encoder->Append(str_buf_ptr);

    str_buf = std::string(reinterpret_cast<char*>(&val2), col_size);
    str_buf_ptr = str_buf.c_str();
    col_buf_encoder->Append(str_buf_ptr);
  }
  col_buf_encoder->Finish();
  const std::string& encoded_data = col_buf_encoder->GetData();
  // Check encoded string length
  int varint_len = 0;
  if (col_size == 8) {
    varint_len = 9;
  } else if (col_size == 4) {
    varint_len = port::kLittleEndian ? 5 : 9;
  }
  // Check encoded string length: first value is original one (val - 0), the
  // coming three are encoded as 1, -1, 1, so they should take 1 byte in varint.
  ASSERT_EQ(varint_len + 3 * 1, encoded_data.size());

  std::unique_ptr<ColBufDecoder> col_buf_decoder(
      new FixedLengthColBufDecoder(col_size, kColDeltaVarint, false, true));
  char* decoded_data = new char[100];
  char* decoded_data_base = decoded_data;
  const char* encoded_data_ptr = encoded_data.c_str();
  encoded_data_ptr += col_buf_decoder->Init(encoded_data_ptr);
  for (int i = 0; i < row_count; ++i) {
    encoded_data_ptr +=
        col_buf_decoder->Decode(encoded_data_ptr, &decoded_data);
  }

  // Check correctness of decoded string length
  ASSERT_EQ(row_count * col_size, decoded_data - decoded_data_base);
  decoded_data = decoded_data_base;

  // Check correctness of decoded data
  for (int i = 0; i < row_count / 2; ++i) {
    uint64_t decoded_val = 0;
    memcpy(&decoded_val, decoded_data, col_size);
    ASSERT_EQ(val1, decoded_val);
    decoded_data += col_size;
    memcpy(&decoded_val, decoded_data, col_size);
    ASSERT_EQ(val2, decoded_val);
    decoded_data += col_size;
  }
  delete[] decoded_data_base;
}

TEST_F(ColumnAwareEncodingTest, ChunkBufEncodeDecode) {
  std::unique_ptr<ColBufEncoder> col_buf_encoder(
      new VariableChunkColBufEncoder(kColDict));
  std::string buf("12345678\377\1\0\0\0\0\0\0\0\376", 18);
  col_buf_encoder->Append(buf.c_str());
  col_buf_encoder->Finish();
  const std::string& encoded_data = col_buf_encoder->GetData();
  const char* str_ptr = encoded_data.c_str();

  std::unique_ptr<ColBufDecoder> col_buf_decoder(
      new VariableChunkColBufDecoder(kColDict));
  str_ptr += col_buf_decoder->Init(str_ptr);
  char* decoded_data = new char[100];
  char* decoded_data_base = decoded_data;
  col_buf_decoder->Decode(str_ptr, &decoded_data);
  for (size_t i = 0; i < buf.size(); ++i) {
    ASSERT_EQ(buf[i], decoded_data_base[i]);
  }
  delete[] decoded_data_base;
}

}  // namespace rocksdb

int main(int argc, char** argv) {
  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}

#else

#include <cstdio>

int main() {
  fprintf(stderr,
          "SKIPPED as column aware encoding experiment is not enabled in "
          "ROCKSDB_LITE\n");
}
#endif  // ROCKSDB_LITE
