/*
 * Copyright (C) 2019 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 "perfetto/ext/base/circular_queue.h"

#include <array>
#include <random>

#include "test/gtest_and_gmock.h"

namespace perfetto {
namespace base {
namespace {

using testing::ElementsAre;

TEST(CircularQueueTest, Int) {
  CircularQueue<int> queue(/*initial_capacity=*/1);
  ASSERT_EQ(queue.size(), 0u);
  queue.emplace_back(101);
  ASSERT_EQ(queue.size(), 1u);
  queue.emplace_back(102);
  queue.emplace_back(103);
  queue.emplace_back(104);
  ASSERT_EQ(queue.size(), 4u);

  auto it = queue.begin();
  for (int i = 101; i <= 104; i++) {
    ASSERT_EQ(*it, i);
    it++;
  }
  ASSERT_EQ(it, queue.end());

  queue.erase_front(1);
  ASSERT_EQ(queue.size(), 3u);
  ASSERT_EQ(*queue.begin(), 102);

  *(queue.begin() + 1) = 42;
  ASSERT_EQ(*(queue.end() - 2), 42);

  queue.erase_front(2);
  ASSERT_EQ(queue.size(), 1u);
  ASSERT_EQ(*queue.begin(), 104);

  queue.pop_front();
  ASSERT_EQ(queue.size(), 0u);
  ASSERT_EQ(queue.begin(), queue.end());

  const size_t kNumInts = 100000u;

  {
    std::minstd_rand0 rnd_engine(0);
    for (size_t i = 0; i < kNumInts; i++) {
      int n = static_cast<int>(rnd_engine());
      queue.emplace_back(n);
    }
  }
  ASSERT_EQ(queue.size(), kNumInts);
  ASSERT_EQ(static_cast<size_t>(queue.end() - queue.begin()), kNumInts);
  {
    std::minstd_rand0 rnd_engine(0);
    it = queue.begin();
    for (size_t i = 0; i < kNumInts; ++i, ++it) {
      ASSERT_LT(it, queue.end());
      ASSERT_EQ(*it, static_cast<int>(rnd_engine()));
    }
  }

  {
    std::minstd_rand0 del_rnd(42);
    std::minstd_rand0 rnd_engine(0);
    while (!queue.empty()) {
      ASSERT_EQ(*queue.begin(), static_cast<int>(rnd_engine()));
      size_t num_del = (del_rnd() % 8) + 1;
      queue.erase_front(num_del + 1);  // +1 because of the read 2 lines above.
      rnd_engine.discard(num_del);
    }
  }
}

TEST(CircularQueueTest, Sorting) {
  CircularQueue<uint64_t> queue;
  std::minstd_rand0 rnd_engine(0);
  for (int i = 0; i < 100000; i++) {
    queue.emplace_back(static_cast<uint64_t>(rnd_engine()));
    if ((i % 100) == 0)
      queue.erase_front(29);
  }
  ASSERT_FALSE(std::is_sorted(queue.begin(), queue.end()));
  std::sort(queue.begin(), queue.end());
  ASSERT_TRUE(std::is_sorted(queue.begin(), queue.end()));
}

TEST(CircularQueueTest, MoveOperators) {
  CircularQueue<int> queue;
  queue.emplace_back(1);
  queue.emplace_back(2);

  {
    CircularQueue<int> moved(std::move(queue));
    ASSERT_TRUE(queue.empty());
    ASSERT_EQ(moved.size(), 2u);

    moved.emplace_back(3);
    moved.emplace_back(4);
    ASSERT_EQ(moved.size(), 4u);
  }
  queue.emplace_back(10);
  queue.emplace_back(11);
  queue.emplace_back(12);
  ASSERT_EQ(queue.size(), 3u);
  ASSERT_EQ(queue.front(), 10);
  ASSERT_EQ(queue.back(), 12);

  {
    CircularQueue<int> moved;
    moved.emplace_back(42);
    moved = std::move(queue);
    ASSERT_TRUE(queue.empty());
    ASSERT_EQ(moved.size(), 3u);
    ASSERT_EQ(moved.size(), 3u);
    ASSERT_EQ(moved.front(), 10);
    ASSERT_EQ(moved.back(), 12);
  }
}

TEST(CircularQueueTest, Iterators) {
  for (size_t repeat = 1; repeat < 8; repeat++) {
    size_t capacity = 8 * (1 << repeat);
    CircularQueue<size_t> queue(capacity);
    for (size_t i = 0; i < capacity - 2; i++)
      queue.emplace_back(0u);
    queue.erase_front(queue.size());
    ASSERT_TRUE(queue.empty());
    ASSERT_EQ(queue.capacity(), capacity);

    // Now the queue is empty and the internal write iterator is abut to wrap.

    // Add a bit more than half-capacity and check that the queue didn't resize.
    for (size_t i = 0; i < capacity / 2 + 3; i++)
      queue.emplace_back(i);
    ASSERT_EQ(queue.capacity(), capacity);

    // Check that all iterators are consistent.
    auto begin = queue.begin();
    auto end = queue.end();
    auto mid = begin + std::distance(begin, end) / 2;
    ASSERT_TRUE(std::is_sorted(begin, end));
    ASSERT_TRUE(begin < end);
    ASSERT_TRUE(begin <= begin);
    ASSERT_TRUE(begin >= begin);
    ASSERT_FALSE(begin < begin);
    ASSERT_FALSE(begin > begin);
    ASSERT_TRUE(begin + 1 > begin);
    ASSERT_TRUE(begin + 1 >= begin);
    ASSERT_FALSE(begin >= begin + 1);
    ASSERT_TRUE(begin <= begin);
    ASSERT_TRUE(begin <= begin + 1);
    ASSERT_TRUE(end > mid);
    ASSERT_TRUE(mid > begin);
    ASSERT_TRUE(std::is_sorted(begin, mid));
    ASSERT_TRUE(std::is_sorted(mid, end));
  }
}

TEST(CircularQueueTest, ReverseIterators) {
  CircularQueue<int> queue;
  ASSERT_EQ(queue.rbegin(), queue.rend());
  queue.emplace_back(1);

  ASSERT_EQ(*queue.rbegin(), 1);
  ASSERT_EQ(++queue.rbegin(), queue.rend());

  queue.emplace_back(2);
  queue.emplace_back(3);

  auto it = queue.rbegin();
  ASSERT_EQ(*(it++), 3);
  ASSERT_EQ(*(it++), 2);
  ASSERT_EQ(*(it++), 1);
  ASSERT_EQ(it, queue.rend());
}

TEST(CircularQueueTest, InsertBefore) {
  CircularQueue<int> queue;
  queue.InsertBefore(queue.end(), 20);
  ASSERT_THAT(queue, ElementsAre(20));

  queue.InsertBefore(queue.begin(), 10);
  ASSERT_THAT(queue, ElementsAre(10, 20));

  queue.InsertBefore(queue.end(), 40);
  ASSERT_THAT(queue, ElementsAre(10, 20, 40));

  queue.InsertBefore(queue.Find(40), 30);
  ASSERT_THAT(queue, ElementsAre(10, 20, 30, 40));

  queue.InsertBefore(queue.begin(), 0);
  ASSERT_THAT(queue, ElementsAre(0, 10, 20, 30, 40));

  // Now test InsertAfter(reverse_iterator). There is a catch here,
  // Insert on a reverse iterator actually inserts *after* the iterator.
  // As bad as it sound, this is consistent with what other STL containers do.
  // It's tied to the fact that std::reverse_iterator creates maps r == i-1.
  // From STL docs:
  // > For a reverse iterator r constructed from an iterator i, the relationship
  // > &*r == &*(i - 1) is always true (as long as r is dereferenceable); thus a
  // > reverse iterator constructed from a one-past-the-end iterator
  // > dereferences to the last element in a sequence.
  // ```
  queue.InsertAfter(queue.rbegin(), 60);
  ASSERT_THAT(queue, ElementsAre(0, 10, 20, 30, 40, 60));

  for (auto it = queue.rbegin(); it != queue.rend(); ++it) {
    if (*it == 40) {
      queue.InsertAfter(it, 50);
      break;
    }
  }
  ASSERT_THAT(queue, ElementsAre(0, 10, 20, 30, 40, 50, 60));

  // I know you don't believe me, so here's the proof.
  std::vector<int> v{10, 20};
  v.emplace(v.rbegin().base(), 40);
  ASSERT_THAT(v, ElementsAre(10, 20, 40));
  for (auto it = v.rbegin(); it != v.rend(); ++it) {
    if (*it == 20) {
      v.insert(it.base(), 30);
      break;
    }
  }
  ASSERT_THAT(v, ElementsAre(10, 20, 30, 40));
}

TEST(CircularQueueTest, InsertBeforeReverse) {
  CircularQueue<int> queue;

  std::array<int, 5> new_entries{4, 1, 5, 2, 3};
  for (int n : new_entries) {
    auto it = queue.rbegin();
    while (it != queue.rend() && n < *it) {
      ++it;
    }
    queue.InsertAfter(it, n);
  }
  ASSERT_THAT(queue, ElementsAre(1, 2, 3, 4, 5));
}

// Test that InsertBefore works correctly when it triggers a capacity growth.
// This verifies that the iterator's pos_ (which is an abstract index) remains
// valid after the internal storage is reallocated during Grow().
TEST(CircularQueueTest, InsertBeforeWithGrow) {
  // Use a small initial capacity so we can easily trigger growth.
  CircularQueue<int> queue(/*initial_capacity=*/4);

  // Fill the queue to capacity.
  queue.emplace_back(10);
  queue.emplace_back(20);
  queue.emplace_back(30);
  queue.emplace_back(40);
  ASSERT_EQ(queue.size(), 4u);
  ASSERT_EQ(queue.capacity(), 4u);

  // Get the position where we want to insert (at element 30).
  // Note: We can't use the iterator directly after InsertBefore because
  // increment_generation() invalidates it. We use pos_ which is accessed
  // directly by InsertBefore.
  auto it = queue.begin() + 2;
  ASSERT_EQ(*it, 30);

  // This InsertBefore should trigger Grow() since we're at capacity.
  // After Grow(), the iterator's pos_ (an abstract index) should still be
  // valid because CircularQueue uses monotonic indices, not pointers.
  queue.InsertBefore(it, 25);

  // Verify the queue grew.
  ASSERT_GT(queue.capacity(), 4u);
  ASSERT_EQ(queue.size(), 5u);

  // Verify all elements are in the correct order.
  ASSERT_THAT(queue, ElementsAre(10, 20, 25, 30, 40));
}

// Test InsertBefore with growth when the queue has wrapped around internally.
TEST(CircularQueueTest, InsertBeforeWithGrowAfterWrap) {
  CircularQueue<int> queue(/*initial_capacity=*/4);

  // Fill and then pop to move the internal begin_ forward.
  queue.emplace_back(1);
  queue.emplace_back(2);
  queue.pop_front();
  queue.pop_front();

  // Now internal begin_ is at position 2. Add elements to wrap around.
  queue.emplace_back(10);
  queue.emplace_back(20);
  queue.emplace_back(30);
  queue.emplace_back(40);
  ASSERT_EQ(queue.size(), 4u);
  ASSERT_EQ(queue.capacity(), 4u);

  // Insert in the middle, triggering growth while wrapped.
  auto it = queue.begin() + 2;
  ASSERT_EQ(*it, 30);
  queue.InsertBefore(it, 25);

  ASSERT_GT(queue.capacity(), 4u);
  ASSERT_EQ(queue.size(), 5u);
  ASSERT_THAT(queue, ElementsAre(10, 20, 25, 30, 40));
}

TEST(CircularQueueTest, ObjectLifetime) {
  class Checker {
   public:
    struct Stats {
      int num_ctors = 0;
      int num_dtors = 0;
      int num_alive = 0;
    };
    Checker(Stats* stats, int n) : stats_(stats), n_(n) {
      EXPECT_GE(n, 0);
      stats_->num_ctors++;
      stats_->num_alive++;
      ptr_ = reinterpret_cast<uintptr_t>(this);
    }

    ~Checker() {
      EXPECT_EQ(ptr_, reinterpret_cast<uintptr_t>(this));
      if (n_ >= 0)
        stats_->num_alive--;
      n_ = -1;
      stats_->num_dtors++;
    }

    Checker(Checker&& other) noexcept {
      n_ = other.n_;
      other.n_ = -1;
      stats_ = other.stats_;
      ptr_ = reinterpret_cast<uintptr_t>(this);
    }

    Checker& operator=(Checker&& other) {
      new (this) Checker(std::move(other));
      return *this;
    }

    Checker(const Checker&) = delete;
    Checker& operator=(const Checker&) = delete;

    Stats* stats_ = nullptr;
    uintptr_t ptr_ = 0;
    int n_ = -1;
  };

  // Check that dtors are invoked on old elements when growing capacity.
  Checker::Stats stats;
  {
    CircularQueue<Checker> queue(/*initial_capacity=*/2);
    for (int i = 0; i < 2; i++)
      queue.emplace_back(&stats, i);
    ASSERT_EQ(stats.num_ctors, 2);
    ASSERT_EQ(stats.num_dtors, 0);

    // This further insertion will grow the queue, causing two std::move()s
    // for the previous elements and one emplace.
    queue.emplace_back(&stats, 2);
    ASSERT_EQ(stats.num_ctors, 3);

    // The two old elements that have std::move()d should be destroyed upon
    // expansion.
    ASSERT_EQ(stats.num_dtors, 2);
  }
  ASSERT_EQ(stats.num_dtors, 2 + 3);

  stats = Checker::Stats();
  {
    CircularQueue<Checker> queue(/*initial_capacity=*/1);
    for (int i = 0; i < 5; i++)
      queue.emplace_back(&stats, i);
    ASSERT_EQ(stats.num_ctors, 5);
    Checker c5(&stats, 5);
    queue.emplace_back(std::move(c5));
    ASSERT_EQ(stats.num_alive, 5 + 1);

    queue.erase_front(2);
    ASSERT_EQ(stats.num_alive, 5 + 1 - 2);

    for (int i = 0; i < 4; i++)
      queue.emplace_back(&stats, 10 + i);
    ASSERT_EQ(stats.num_alive, 5 + 1 - 2 + 4);
  }
  ASSERT_EQ(stats.num_ctors, 5 + 1 + 4);
  ASSERT_EQ(stats.num_alive, 0);

  stats = Checker::Stats();
  {
    CircularQueue<Checker> q1(1);
    CircularQueue<Checker> q2(64);
    for (int i = 0; i < 100; i++) {
      q1.emplace_back(&stats, 1000 + i * 2);
      q2.emplace_back(&stats, 1001 + i * 2);
    }

    ASSERT_EQ(stats.num_alive, 200);

    for (int i = 0; i < 100; i += 2) {
      auto it1 = q1.begin() + i;
      auto it2 = q2.begin() + i;
      std::swap(*it1, *it2);
    }
    auto comparer = [](const Checker& lhs, const Checker& rhs) {
      return lhs.n_ < rhs.n_;
    };
    ASSERT_TRUE(std::is_sorted(q1.begin(), q1.end(), comparer));
    ASSERT_TRUE(std::is_sorted(q2.begin(), q2.end(), comparer));
    ASSERT_EQ(stats.num_alive, 200);
  }
}

}  // namespace
}  // namespace base
}  // namespace perfetto
