#include "ring_buffer.h"

#include <gtest/gtest.h>

#include <algorithm>
#include <cassert>
#include <vector>

namespace {

template <typename T>
class TypedRingBuffer;

template <typename T>
class TypedRingBuffer<T *> {
 public:
  explicit TypedRingBuffer(int size) : rb_(rb_new(size)) {}
  ~TypedRingBuffer() { rb_kill(rb_); }
  TypedRingBuffer(TypedRingBuffer const &) = delete;

  bool full() const { return rb_full(rb_); }
  bool empty() const { return rb_empty(rb_); }
  T *write(T *p) { return static_cast<T *>(rb_write(rb_, p)); }
  bool read(T **p) {
    void *vp;
    bool res = rb_read(rb_, &vp);
    *p = static_cast<T *>(vp);
    return res;
  }

  uint16_t size() const { return rb_size(rb_); }
  uint16_t data(T **dest) const {
    std::vector<void *> vdest(size());
    uint16_t res = rb_data(rb_, vdest.data());
    for (uint16_t i = 0; i < size(); i++) {
      dest[i] = static_cast<T *>(vdest.at(i));
    }
    return res;
  }

  bool contains(T *p) const {
    std::vector<T *> elts(size());
    data(elts.data());
    return std::find(elts.begin(), elts.end(), p) != elts.end();
  }

  bool ok() const { return rb_ != nullptr; }

 private:
  RingBuffer *rb_;
};

TEST(RingBuffer, EmptyBufferReportsEmpty) {
  TypedRingBuffer<int *> rb(10);
  ASSERT_TRUE(rb.ok());
  EXPECT_TRUE(rb.empty());
}

TEST(RingBuffer, EmptyBufferReportsNotFull) {
  TypedRingBuffer<int *> rb(10);
  ASSERT_TRUE(rb.ok());
  EXPECT_FALSE(rb.full());
}

TEST(RingBuffer, ZeroSizedRingBufferIsBothEmptyAndFull) {
  TypedRingBuffer<int *> rb(0);
  ASSERT_TRUE(rb.ok());
  EXPECT_TRUE(rb.empty());
  EXPECT_TRUE(rb.full());
}

TEST(RingBuffer, WritingMakesBufferNotEmpty) {
  TypedRingBuffer<int *> rb(2);
  ASSERT_TRUE(rb.ok());
  int value0 = 123;
  rb.write(&value0);
  EXPECT_FALSE(rb.empty());
}

TEST(RingBuffer, WritingOneElementMakesBufferNotFull) {
  TypedRingBuffer<int *> rb(2);
  ASSERT_TRUE(rb.ok());
  int value0 = 123;
  rb.write(&value0);
  EXPECT_FALSE(rb.full());
}

TEST(RingBuffer, WritingAllElementsMakesBufferFull) {
  TypedRingBuffer<int *> rb(2);
  ASSERT_TRUE(rb.ok());
  int value0 = 123;
  int value1 = 231;
  rb.write(&value0);
  rb.write(&value1);
  EXPECT_TRUE(rb.full());
}

TEST(RingBuffer, ReadingElementFromFullBufferMakesItNotFull) {
  TypedRingBuffer<int *> rb(2);
  ASSERT_TRUE(rb.ok());
  int value0 = 123;
  int value1 = 231;
  rb.write(&value0);
  rb.write(&value1);
  EXPECT_TRUE(rb.full());
  int *retrieved;
  // Reading deletes the element.
  EXPECT_TRUE(rb.read(&retrieved));
  EXPECT_FALSE(rb.full());
}

TEST(RingBuffer, ZeroSizeBufferCanBeWrittenToOnce) {
  TypedRingBuffer<int *> rb(0);
  ASSERT_TRUE(rb.ok());
  int value0 = 123;
  // Strange behaviour: we can write one element to a 0-size buffer.
  EXPECT_EQ(nullptr, rb.write(&value0));
  EXPECT_EQ(&value0, rb.write(&value0));
  int *retrieved = nullptr;
  // But then we can't read it.
  EXPECT_FALSE(rb.read(&retrieved));
  EXPECT_EQ(nullptr, retrieved);
}

TEST(RingBuffer, ReadingFromEmptyBufferFails) {
  TypedRingBuffer<int *> rb(2);
  ASSERT_TRUE(rb.ok());
  int *retrieved;
  EXPECT_FALSE(rb.read(&retrieved));
}

TEST(RingBuffer, WritingToBufferWhenFullOverwritesBeginning) {
  TypedRingBuffer<int *> rb(2);
  ASSERT_TRUE(rb.ok());
  int value0 = 123;
  int value1 = 231;
  int value2 = 312;
  int value3 = 432;
  EXPECT_EQ(nullptr, rb.write(&value0));
  EXPECT_EQ(nullptr, rb.write(&value1));
  EXPECT_TRUE(rb.contains(&value0));
  EXPECT_TRUE(rb.contains(&value1));

  // Adding another element evicts the first element.
  EXPECT_EQ(&value0, rb.write(&value2));
  EXPECT_FALSE(rb.contains(&value0));
  EXPECT_TRUE(rb.contains(&value2));

  // Adding another evicts the second.
  EXPECT_EQ(&value1, rb.write(&value3));
  EXPECT_FALSE(rb.contains(&value1));
  EXPECT_TRUE(rb.contains(&value3));
}

TEST(RingBuffer, SizeIsNumberOfElementsInBuffer) {
  TypedRingBuffer<int *> rb(10);
  ASSERT_TRUE(rb.ok());
  int value0 = 123;
  EXPECT_EQ(rb.size(), 0);
  rb.write(&value0);
  EXPECT_EQ(rb.size(), 1);
  rb.write(&value0);
  EXPECT_EQ(rb.size(), 2);
  rb.write(&value0);
  EXPECT_EQ(rb.size(), 3);
  rb.write(&value0);
  EXPECT_EQ(rb.size(), 4);

  int *retrieved;
  rb.read(&retrieved);
  EXPECT_EQ(rb.size(), 3);
  rb.read(&retrieved);
  EXPECT_EQ(rb.size(), 2);
  rb.read(&retrieved);
  EXPECT_EQ(rb.size(), 1);
  rb.read(&retrieved);
  EXPECT_EQ(rb.size(), 0);
}

TEST(RingBuffer, SizeIsLimitedByMaxSize) {
  TypedRingBuffer<int *> rb(4);
  ASSERT_TRUE(rb.ok());
  int value0 = 123;
  rb.write(&value0);
  rb.write(&value0);
  rb.write(&value0);
  rb.write(&value0);
  EXPECT_EQ(rb.size(), 4);

  // Add one more.
  rb.write(&value0);
  // Still size is 4.
  EXPECT_EQ(rb.size(), 4);
}

}  // namespace
