// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:nil -*-
// vim: ts=8 sw=2 sts=2 expandtab

#include "test/librbd/test_fixture.h"
#include "test/librbd/test_support.h"

#include "librbd/cache/pwl/LogMap.cc"

void register_test_write_log_map() {
}

namespace librbd {
namespace cache {
namespace pwl {

using namespace std;

struct TestLogEntry {
  uint64_t image_offset_bytes;
  uint64_t write_bytes;
  uint32_t referring_map_entries = 0;
  TestLogEntry(const uint64_t image_offset_bytes, const uint64_t write_bytes)
    : image_offset_bytes(image_offset_bytes), write_bytes(write_bytes) {
  }
  uint64_t get_offset_bytes() {
    return image_offset_bytes;
  }
  uint64_t get_write_bytes() {
    return write_bytes;
  }
  BlockExtent block_extent() {
    return BlockExtent(image_offset_bytes, image_offset_bytes + write_bytes);
  }
  uint32_t get_map_ref() {
    return referring_map_entries;
  }
  void inc_map_ref() {
    referring_map_entries++;
  }
  void dec_map_ref() {
    referring_map_entries--;
  }
  friend std::ostream &operator<<(std::ostream &os,
                                  const TestLogEntry &entry) {
    os << "referring_map_entries=" << entry.referring_map_entries << ", "
       << "image_offset_bytes=" << entry.image_offset_bytes << ", "
       << "write_bytes=" << entry.write_bytes;
    return os;
  };
};

typedef std::list<std::shared_ptr<TestLogEntry>> TestLogEntries;
typedef LogMapEntry<TestLogEntry> TestMapEntry;
typedef LogMapEntries<TestLogEntry> TestLogMapEntries;
typedef LogMap<TestLogEntry> TestLogMap;

class TestWriteLogMap : public TestFixture {
public:
  void SetUp() override {
    TestFixture::SetUp();
    m_cct = reinterpret_cast<CephContext*>(m_ioctx.cct());
  }

  CephContext *m_cct;
};

TEST_F(TestWriteLogMap, Simple) {
  TestLogEntries es;
  TestLogMapEntries lme;
  TestLogMap  map(m_cct);

  /* LogEntry takes offset, length, in bytes */
  auto e1 = make_shared<TestLogEntry>(4, 8);
  TestLogEntry *e1_ptr = e1.get();
  ASSERT_EQ(4, e1_ptr->get_offset_bytes());
  ASSERT_EQ(8, e1_ptr->get_write_bytes());
  map.add_log_entry(e1);

  /* BlockExtent takes first, last, in blocks */
  TestLogMapEntries found0 = map.find_map_entries(BlockExtent(0, 100));
  int numfound = found0.size();
  /* Written range includes the single write above */
  ASSERT_EQ(1, numfound);
  ASSERT_EQ(e1, found0.front().log_entry);

  /* Nothing before that */
  found0 = map.find_map_entries(BlockExtent(0, 3));
  numfound = found0.size();
  ASSERT_EQ(0, numfound);

  /* Nothing after that */
  found0 = map.find_map_entries(BlockExtent(12, 99));
  numfound = found0.size();
  ASSERT_EQ(0, numfound);

  /* 4-11 will be e1 */
  for (int i=4; i<12; i++) {
    TestLogMapEntries found0 = map.find_map_entries(BlockExtent(i, i + 1));
    int numfound = found0.size();
    ASSERT_EQ(1, numfound);
    ASSERT_EQ(e1, found0.front().log_entry);
  }

  map.remove_log_entry(e1);
  /* Nothing should be found */
  for (int i=4; i<12; i++) {
    TestLogMapEntries found0 = map.find_map_entries(BlockExtent(i, i + 1));
    int numfound = found0.size();
    ASSERT_EQ(0, numfound);
  }
}

TEST_F(TestWriteLogMap, OverlapFront) {
  TestLogMap map(m_cct);

  auto e0 = make_shared<TestLogEntry>(4, 8);
  map.add_log_entry(e0);
  /* replaces block 4-7 of e0 */
  auto e1 = make_shared<TestLogEntry>(0, 8);
  map.add_log_entry(e1);

  /* Written range includes the two writes above */
  TestLogMapEntries found0 = map.find_map_entries(BlockExtent(0, 100));
  int numfound = found0.size();
  ASSERT_EQ(2, numfound);
  ASSERT_EQ(e1, found0.front().log_entry);
  ASSERT_EQ(0, found0.front().block_extent.block_start);
  ASSERT_EQ(8, found0.front().block_extent.block_end);
  found0.pop_front();
  ASSERT_EQ(e0, found0.front().log_entry);
  ASSERT_EQ(8, found0.front().block_extent.block_start);
  ASSERT_EQ(12, found0.front().block_extent.block_end);

  /* 0-7 will be e1 */
  for (int i=0; i<8; i++) {
    TestLogMapEntries found0 = map.find_map_entries(BlockExtent(i, i + 1));
    int numfound = found0.size();
    ASSERT_EQ(1, numfound);
    ASSERT_EQ(e1, found0.front().log_entry);
  }

  /* 8-11 will be e0 */
  for (int i=8; i<12; i++) {
    TestLogMapEntries found0 = map.find_map_entries(BlockExtent(i, i + 1));
    int numfound = found0.size();
    ASSERT_EQ(1, numfound);
    ASSERT_EQ(e0, found0.front().log_entry);
  }
}

TEST_F(TestWriteLogMap, OverlapBack) {
  TestLogMap map(m_cct);

  auto e0 = make_shared<TestLogEntry>(0, 8);
  map.add_log_entry(e0);
  /* replaces block 4-7 of e0 */
  auto e1 = make_shared<TestLogEntry>(4, 8);
  map.add_log_entry(e1);

  /* Written range includes the two writes above */
  TestLogMapEntries found0 = map.find_map_entries(BlockExtent(0, 100));
  int numfound = found0.size();
  ASSERT_EQ(2, numfound);
  ASSERT_EQ(e0, found0.front().log_entry);
  ASSERT_EQ(0, found0.front().block_extent.block_start);
  ASSERT_EQ(4, found0.front().block_extent.block_end);
  found0.pop_front();
  ASSERT_EQ(e1, found0.front().log_entry);
  ASSERT_EQ(4, found0.front().block_extent.block_start);
  ASSERT_EQ(12, found0.front().block_extent.block_end);

  /* 0-3 will be e0 */
  for (int i=0; i<4; i++) {
    TestLogMapEntries found0 = map.find_map_entries(BlockExtent(i, i + 1));
    int numfound = found0.size();
    ASSERT_EQ(1, numfound);
    ASSERT_EQ(e0, found0.front().log_entry);
  }

  /* 4-11 will be e1 */
  for (int i=4; i<12; i++) {
    TestLogMapEntries found0 = map.find_map_entries(BlockExtent(i, i + 1));
    int numfound = found0.size();
    ASSERT_EQ(1, numfound);
    ASSERT_EQ(e1, found0.front().log_entry);
  }

  map.remove_log_entry(e0);

  /* 0-3 will find nothing */
  for (int i=0; i<4; i++) {
    TestLogMapEntries found0 = map.find_map_entries(BlockExtent(i, i + 1));
    int numfound = found0.size();
    ASSERT_EQ(0, numfound);
  }

  /* 4-11 will still be e1 */
  for (int i=4; i<12; i++) {
    TestLogMapEntries found0 = map.find_map_entries(BlockExtent(i, i + 1));
    int numfound = found0.size();
    ASSERT_EQ(1, numfound);
    ASSERT_EQ(e1, found0.front().log_entry);
  }

}

TEST_F(TestWriteLogMap, OverlapMiddle) {
  TestLogMap map(m_cct);

  auto e0 = make_shared<TestLogEntry>(0, 1);
  map.add_log_entry(e0);

  TestLogMapEntries found0 = map.find_map_entries(BlockExtent(0, 1));
  int numfound = found0.size();
  ASSERT_EQ(1, numfound);
  ASSERT_EQ(e0, found0.front().log_entry);
  TestLogEntries entries = map.find_log_entries(BlockExtent(0, 1));
  int entriesfound = entries.size();
  ASSERT_EQ(1, entriesfound);
  ASSERT_EQ(e0, entries.front());

  auto e1 = make_shared<TestLogEntry>(1, 1);
  map.add_log_entry(e1);

  found0 = map.find_map_entries(BlockExtent(1, 2));
  numfound = found0.size();
  ASSERT_EQ(1, numfound);
  ASSERT_EQ(e1, found0.front().log_entry);
  entries = map.find_log_entries(BlockExtent(1, 2));
  entriesfound = entries.size();
  ASSERT_EQ(1, entriesfound);
  ASSERT_EQ(e1, entries.front());

  auto e2 = make_shared<TestLogEntry>(2, 1);
  map.add_log_entry(e2);

  found0 = map.find_map_entries(BlockExtent(2, 3));
  numfound = found0.size();
  ASSERT_EQ(1, numfound);
  ASSERT_EQ(e2, found0.front().log_entry);
  entries = map.find_log_entries(BlockExtent(2, 3));
  entriesfound = entries.size();
  ASSERT_EQ(1, entriesfound);
  ASSERT_EQ(e2, entries.front());

  /* replaces e1 */
  auto e3 = make_shared<TestLogEntry>(1, 1);
  map.add_log_entry(e3);

  found0 = map.find_map_entries(BlockExtent(1, 2));
  numfound = found0.size();
  ASSERT_EQ(1, numfound);
  ASSERT_EQ(e3, found0.front().log_entry);
  entries = map.find_log_entries(BlockExtent(1, 2));
  entriesfound = entries.size();
  ASSERT_EQ(1, entriesfound);
  ASSERT_EQ(e3, entries.front());

  found0 = map.find_map_entries(BlockExtent(0, 100));
  numfound = found0.size();
  ASSERT_EQ(3, numfound);
  ASSERT_EQ(e0, found0.front().log_entry);
  found0.pop_front();
  ASSERT_EQ(e3, found0.front().log_entry);
  found0.pop_front();
  ASSERT_EQ(e2, found0.front().log_entry);
  entries = map.find_log_entries(BlockExtent(0, 100));
  entriesfound = entries.size();
  ASSERT_EQ(3, entriesfound);
  ASSERT_EQ(e0, entries.front());
  entries.pop_front();
  ASSERT_EQ(e3, entries.front());
  entries.pop_front();
  ASSERT_EQ(e2, entries.front());

  entries.clear();
  entries.emplace_back(e0);
  entries.emplace_back(e1);
  map.remove_log_entries(entries);

  found0 = map.find_map_entries(BlockExtent(0, 100));
  numfound = found0.size();
  ASSERT_EQ(2, numfound);
  ASSERT_EQ(e3, found0.front().log_entry);
  found0.pop_front();
  ASSERT_EQ(e2, found0.front().log_entry);
}

TEST_F(TestWriteLogMap, OverlapSplit) {
  TestLogMap map(m_cct);

  auto e0 = make_shared<TestLogEntry>(0, 8);
  map.add_log_entry(e0);

  /* Splits e0 at 1 */
  auto e1 = make_shared<TestLogEntry>(1, 1);
  map.add_log_entry(e1);

  /* Splits e0 again at 4 */
  auto e2 = make_shared<TestLogEntry>(4, 2);
  map.add_log_entry(e2);

  /* Replaces one block of e2, and one of e0 */
  auto e3 = make_shared<TestLogEntry>(5, 2);
  map.add_log_entry(e3);

  /* Expecting: 0:e0, 1:e1, 2..3:e0, 4:e2, 5..6:e3, 7:e0 */
  TestLogMapEntries found0 = map.find_map_entries(BlockExtent(0, 100));
  int numfound = found0.size();
  ASSERT_EQ(6, numfound);
  ASSERT_EQ(e0, found0.front().log_entry);
  ASSERT_EQ(0, found0.front().block_extent.block_start);
  ASSERT_EQ(1, found0.front().block_extent.block_end);
  found0.pop_front();
  ASSERT_EQ(e1, found0.front().log_entry);
  ASSERT_EQ(1, found0.front().block_extent.block_start);
  ASSERT_EQ(2, found0.front().block_extent.block_end);
  found0.pop_front();
  ASSERT_EQ(e0, found0.front().log_entry);
  ASSERT_EQ(2, found0.front().block_extent.block_start);
  ASSERT_EQ(4, found0.front().block_extent.block_end);
  found0.pop_front();
  ASSERT_EQ(e2, found0.front().log_entry);
  ASSERT_EQ(4, found0.front().block_extent.block_start);
  ASSERT_EQ(5, found0.front().block_extent.block_end);
  found0.pop_front();
  ASSERT_EQ(e3, found0.front().log_entry);
  ASSERT_EQ(5, found0.front().block_extent.block_start);
  ASSERT_EQ(7, found0.front().block_extent.block_end);
  found0.pop_front();
  ASSERT_EQ(e0, found0.front().log_entry);
  ASSERT_EQ(7, found0.front().block_extent.block_start);
  ASSERT_EQ(8, found0.front().block_extent.block_end);
}

} // namespace pwl
} // namespace cache
} // namespace librbd
