#include "gtest/gtest.h"
#include "packer.h"

#include <random>

namespace nc {
namespace {

class PackerFixture : public ::testing::Test {
 protected:
  PackedUintSeq seq_;
  std::vector<uint64_t> vec_;
};

class RLEFixture : public ::testing::Test {
 protected:
  RLEField<uint64_t> seq_;
  std::vector<uint64_t> vec_;
};

TEST_F(PackerFixture, Empty) {
  ASSERT_EQ(0ul, seq_.SizeBytes());

  seq_.Restore(&vec_);
  ASSERT_EQ(0ul, vec_.size());
}

TEST(Packer, AppendByteSizes) {
  uint64_t lower_boundary = 0;
  for (size_t byte_count = 1; byte_count <= 8; byte_count++) {
    PackedUintSeq seq_;

    // Upon packing each integer is compressed to the minimum number of bytes
    // required to hold it plus 3 bits taken from the first byte.
    uint64_t upper_boundary = (1UL << ((byte_count * 8) - 3)) - 1;

    seq_.Append(lower_boundary);
    ASSERT_EQ(byte_count, seq_.SizeBytes());

    seq_.Append(upper_boundary);
    ASSERT_EQ(2 * byte_count, seq_.SizeBytes());

    lower_boundary = upper_boundary + 1;
  }
}

TEST(Packer, AppendTooLarge) {
  PackedUintSeq seq;

  seq.Append(0);
  ASSERT_DEATH(seq.Append(std::numeric_limits<uint64_t>::max()), "too large");
}

TEST(Packer, AppendNonIncrementing) {
  PackedUintSeq seq;

  seq.Append(1000);
  ASSERT_DEATH(seq.Append(1), "increment");
}

TEST_F(PackerFixture, Append1KSame) {
  for (size_t i = 0; i < 1000; ++i) {
    seq_.Append(1050);
  }

  seq_.Restore(&vec_);
  ASSERT_EQ(1000ul, vec_.size());
  for (size_t i = 0; i < 1000; ++i) {
    ASSERT_EQ(1050ul, vec_.at(0));
  }
}

TEST_F(PackerFixture, Append10M) {
  std::default_random_engine e(1);
  std::vector<uint64_t> model;

  uint64_t prev = 0;
  for (uint64_t i = 0; i < 10000000L; ++i) {
    uint64_t val = prev + e();
    prev = val;

    seq_.Append(val);
    model.push_back(val);
  }

  seq_.Restore(&vec_);
  ASSERT_EQ(model, vec_);
}

TEST_F(PackerFixture, Append1MIter) {
  std::default_random_engine e(2);
  std::vector<uint64_t> model;

  uint64_t prev = 0;
  for (uint64_t i = 0; i < 1000000L; ++i) {
    uint64_t val = prev + e();
    prev = val;

    seq_.Append(val);
    model.push_back(val);
  }

  PackedUintSeqIterator it(seq_);
  uint64_t value;
  while (it.Next(&value)) {
    vec_.push_back(value);
  }

  ASSERT_EQ(model, vec_);
}

TEST_F(RLEFixture, Empty) {
  seq_.Restore(&vec_);

  ASSERT_EQ(0ul, seq_.SizeBytes());
  ASSERT_TRUE(vec_.empty());
}

TEST_F(RLEFixture, One) {
  seq_.Append(1);

  seq_.Restore(&vec_);

  ASSERT_EQ(1ul, vec_.size());
  ASSERT_EQ(1ul, vec_.at(0));
}

TEST_F(RLEFixture, Append1M) {
  std::vector<uint64_t> model;
  for (size_t i = 0; i < 1000000; i++) {
    uint64_t val = 100 + 5 * i;

    seq_.Append(val);
    model.push_back(val);
  }

  // Regardless of the implementation if RLE is implemented correctly the 10M
  // values should fit in 50 bytes.
  ASSERT_GT(50ul, seq_.SizeBytes());

  seq_.Restore(&vec_);
  ASSERT_EQ(model, vec_);
}

TEST_F(RLEFixture, Append10M) {
  std::default_random_engine e(1);

  std::vector<uint64_t> model;
  for (size_t i = 0; i < 10000000; i++) {
    uint64_t val = e();

    seq_.Append(val);
    model.push_back(val);
  }

  seq_.Restore(&vec_);
  ASSERT_EQ(model, vec_);
}

TEST_F(RLEFixture, Append1MIter) {
  std::default_random_engine e(2);

  std::vector<uint64_t> model;
  for (size_t i = 0; i < 1000000; i++) {
    uint64_t val = e();

    seq_.Append(val);
    model.push_back(val);
  }

  RLEFieldIterator<uint64_t> it(seq_);
  uint64_t value;
  while (it.Next(&value)) {
    vec_.push_back(value);
  }

  ASSERT_EQ(model, vec_);
}

TEST_F(RLEFixture, RandomAccess) {
  std::default_random_engine e(2);

  std::vector<uint64_t> model;
  std::uniform_int_distribution<size_t> sequence_base(1, 1000000);
  std::uniform_int_distribution<size_t> stride_len(1, 10000);
  std::uniform_int_distribution<size_t> increment(1, 100);
  for (size_t i = 0; i < 1000; i++) {
    size_t base = sequence_base(e);
    size_t len = stride_len(e);
    size_t inc = increment(e);
    for (size_t i = 0; i < len; ++i) {
      seq_.Append(base + i * inc);
      model.emplace_back(base + i * inc);
    }
  }

  std::vector<uint64_t> all_values = seq_.Restore();
  ASSERT_EQ(model, all_values);
  for (size_t i = 0; i < 1000000; ++i) {
    std::uniform_int_distribution<size_t> rnd_index(0, all_values.size() - 1);
    size_t index = rnd_index(e);
    ASSERT_EQ(all_values[index], seq_.at(index));
  }
}

}  // namespace
}  // namespace nc
