// Copyright 2020 The Pigweed Authors
//
// 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
//
//     https://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 "pw_containers/inline_queue.h"

#include <algorithm>
#include <array>
#include <cstddef>

#include "pw_compilation_testing/negative_compilation.h"
#include "pw_containers/algorithm.h"
#include "pw_containers_private/test_helpers.h"
#include "pw_unit_test/framework.h"

namespace pw::containers {
namespace {

using namespace std::literals::string_view_literals;
using test::CopyOnly;
using test::Counter;
using test::MoveOnly;

TEST(InlineQueue, Construct_Sized) {
  InlineQueue<int, 3> queue;
  EXPECT_TRUE(queue.empty());
  EXPECT_EQ(queue.size(), 0u);
  EXPECT_EQ(queue.max_size(), 3u);
}

TEST(InlineQueue, Construct_GenericSized) {
  InlineQueue<int, 3> sized_queue;
  InlineQueue<int>& queue(sized_queue);
  EXPECT_TRUE(queue.empty());
  EXPECT_EQ(queue.size(), 0u);
  EXPECT_EQ(queue.max_size(), 3u);
}

TEST(InlineQueue, Construct_CopySameCapacity) {
  InlineQueue<CopyOnly, 4> queue(4, CopyOnly(123));
  InlineQueue<CopyOnly, 4> copied(queue);

  EXPECT_EQ(4u, queue.size());
  EXPECT_EQ(123, queue[3].value);

  EXPECT_EQ(4u, copied.size());
  EXPECT_EQ(123, copied[3].value);
}

TEST(InlineQueue, Construct_CopyLargerCapacity) {
  InlineQueue<CopyOnly, 4> queue(4, CopyOnly(123));
  InlineQueue<CopyOnly, 5> copied(queue);

  EXPECT_EQ(4u, queue.size());
  EXPECT_EQ(123, queue[3].value);

  EXPECT_EQ(4u, copied.size());
  EXPECT_EQ(123, copied[3].value);
}

TEST(InlineQueue, Construct_CopySmallerCapacity) {
  InlineQueue<CopyOnly, 4> queue(3, CopyOnly(123));
  InlineQueue<CopyOnly, 3> copied(queue);

  EXPECT_EQ(3u, queue.size());
  EXPECT_EQ(123, queue[2].value);

  EXPECT_EQ(3u, copied.size());
  EXPECT_EQ(123, copied[2].value);
}

TEST(InlineQueue, Construct_MoveSameCapacity) {
  InlineQueue<MoveOnly, 4> queue;
  queue.emplace(MoveOnly(1));
  queue.emplace(MoveOnly(2));
  queue.emplace(MoveOnly(3));
  queue.emplace(MoveOnly(4));
  InlineQueue<MoveOnly, 4> moved(std::move(queue));

  // NOLINTNEXTLINE(bugprone-use-after-move)
  EXPECT_EQ(0u, queue.size());

  ASSERT_EQ(4u, moved.size());
  EXPECT_EQ(4, moved[3].value);
}

TEST(InlineQueue, Construct_MoveLargerCapacity) {
  InlineQueue<MoveOnly, 4> queue;
  queue.emplace(MoveOnly(1));
  queue.emplace(MoveOnly(2));
  queue.emplace(MoveOnly(3));
  queue.emplace(MoveOnly(4));
  InlineQueue<MoveOnly, 5> moved(std::move(queue));

  // NOLINTNEXTLINE(bugprone-use-after-move)
  EXPECT_EQ(0u, queue.size());

  ASSERT_EQ(4u, moved.size());
  EXPECT_EQ(4, moved[3].value);
}

TEST(InlineQueue, Construct_MoveSmallerCapacity) {
  InlineQueue<MoveOnly, 4> queue;
  queue.emplace(MoveOnly(1));
  queue.emplace(MoveOnly(2));
  queue.emplace(MoveOnly(3));
  InlineQueue<MoveOnly, 3> moved(std::move(queue));

  // NOLINTNEXTLINE(bugprone-use-after-move)
  EXPECT_EQ(0u, queue.size());

  ASSERT_EQ(3u, moved.size());
  EXPECT_EQ(3, moved[2].value);
}

TEST(InlineQueue, Destruct_ZeroLength) {
  Counter::Reset();
  {
    InlineQueue<Counter, 0> queue;
    EXPECT_EQ(queue.size(), 0u);
  }
  EXPECT_EQ(Counter::created, 0);
  EXPECT_EQ(Counter::destroyed, 0);
}

TEST(InlineQueue, Destruct_Empty) {
  Counter::Reset();

  {
    InlineQueue<Counter, 3> queue;
    EXPECT_EQ(queue.size(), 0u);
  }
  EXPECT_EQ(Counter::created, 0);
  EXPECT_EQ(Counter::destroyed, 0);
}

TEST(InlineQueue, Destruct_MultipleEntries) {
  Counter value;
  Counter::Reset();

  {
    InlineQueue<Counter, 128> queue(100, value);
  }

  EXPECT_EQ(Counter::created, 100);
  EXPECT_EQ(Counter::destroyed, 100);
}

TEST(InlineQueue, Assign_InitializerList) {
  InlineQueue<int, 4> queue = {1, 3, 5, 7};

  EXPECT_EQ(4u, queue.size());

  EXPECT_EQ(1, queue[0]);
  EXPECT_EQ(3, queue[1]);
  EXPECT_EQ(5, queue[2]);
  EXPECT_EQ(7, queue[3]);
}

TEST(InlineQueue, Assign_CopySameCapacity) {
  InlineQueue<CopyOnly, 4> queue(4, CopyOnly(123));
  InlineQueue<CopyOnly, 4> copied = queue;

  EXPECT_EQ(4u, queue.size());
  EXPECT_EQ(123, queue[3].value);

  EXPECT_EQ(4u, copied.size());
  EXPECT_EQ(123, copied[3].value);
}

TEST(InlineQueue, Assign_CopyLargerCapacity) {
  InlineQueue<CopyOnly, 4> queue(4, CopyOnly(123));
  InlineQueue<CopyOnly, 5> copied = queue;

  EXPECT_EQ(4u, queue.size());
  EXPECT_EQ(123, queue[3].value);

  EXPECT_EQ(4u, copied.size());
  EXPECT_EQ(123, copied[3].value);
}

TEST(InlineQueue, Assign_CopySmallerCapacity) {
  InlineQueue<CopyOnly, 4> queue(3, CopyOnly(123));
  InlineQueue<CopyOnly, 3> copied = queue;

  EXPECT_EQ(3u, queue.size());
  EXPECT_EQ(123, queue[2].value);

  EXPECT_EQ(3u, copied.size());
  EXPECT_EQ(123, copied[2].value);
}

TEST(InlineQueue, Access_Iterator) {
  InlineQueue<Counter, 2> queue(2);
  for (Counter& item : queue) {
    EXPECT_EQ(item.value, 0);
  }
  for (const Counter& item : queue) {
    EXPECT_EQ(item.value, 0);
  }
}

TEST(InlineQueue, Access_ConstIterator) {
  const InlineQueue<Counter, 2> queue(2);
  for (const Counter& item : queue) {
    EXPECT_EQ(item.value, 0);
  }
}

TEST(InlineQueue, Access_ZeroLength) {
  InlineQueue<Counter, 0> queue;

  EXPECT_EQ(0u, queue.size());
  EXPECT_EQ(0u, queue.max_size());
  EXPECT_TRUE(queue.empty());
  EXPECT_TRUE(queue.full());

  for (Counter& item : queue) {
    (void)item;
    FAIL();
  }
}

TEST(InlineQueue, Access_ContiguousData) {
  // Content = {}, Storage = [x, x]
  InlineQueue<int, 2> queue;

  {
    auto [first, second] = queue.contiguous_data();
    EXPECT_EQ(first.size(), 0u);
    EXPECT_EQ(second.size(), 0u);
  }

  // Content = {1}, Storage = [1, x]
  queue.push(1);
  {
    auto [first, second] = queue.contiguous_data();
    EXPECT_TRUE(Equal(first, std::array<int, 1>{1}));
    EXPECT_TRUE(Equal(second, std::array<int, 0>{}));
  }

  // Content = {1, 2}, Storage = [1, 2]
  queue.push(2);
  EXPECT_TRUE(queue.full());
  {
    auto [first, second] = queue.contiguous_data();
    EXPECT_TRUE(Equal(first, std::array<int, 2>{1, 2}));
    EXPECT_TRUE(Equal(second, std::array<int, 0>{}));
  }

  // Content = {2}, Storage = [x, 2]
  queue.pop();
  {
    auto [first, second] = queue.contiguous_data();
    EXPECT_TRUE(Equal(first, std::array<int, 1>{2}));
    EXPECT_TRUE(Equal(second, std::array<int, 0>{}));
  }

  // Content = {2, 1}, Storage = [1, 2]
  queue.push(1);
  {
    auto [first, second] = queue.contiguous_data();
    EXPECT_TRUE(Equal(first, std::array<int, 1>{2}));
    EXPECT_TRUE(Equal(second, std::array<int, 1>{1}));
  }

  // Content = {1}, Storage = [1, x]
  queue.pop();
  {
    auto [first, second] = queue.contiguous_data();
    EXPECT_TRUE(Equal(first, std::array<int, 1>{1}));
    EXPECT_TRUE(Equal(second, std::array<int, 0>{}));
  }

  // Content = {1, 2}, Storage = [1, 2]
  queue.push(2);
  {
    auto [first, second] = queue.contiguous_data();
    EXPECT_TRUE(Equal(first, std::array<int, 2>{1, 2}));
    EXPECT_TRUE(Equal(second, std::array<int, 0>{}));
  }
}

TEST(InlineQueue, Access_ConstContiguousData) {
  // Content = {1, 2}, Storage = [1, 2]
  const InlineQueue<int, 2> queue = {1, 2};

  {
    auto [first, second] = queue.contiguous_data();
    EXPECT_EQ(first.size(), 2u);
    EXPECT_EQ(second.size(), 0u);
  }
}

TEST(InlineQueue, Modify_Clear) {
  Counter::Reset();

  InlineQueue<Counter, 100> queue;
  queue.emplace();
  queue.emplace();
  queue.emplace();

  queue.clear();

  EXPECT_EQ(3, Counter::created);
  EXPECT_EQ(3, Counter::destroyed);
}

TEST(InlineQueue, Modify_Push_Copy) {
  Counter value(99);
  Counter::Reset();

  {
    InlineQueue<Counter, 10> queue;
    queue.push(value);

    EXPECT_EQ(queue.size(), 1u);
    EXPECT_EQ(queue.front().value, 99);
  }

  EXPECT_EQ(Counter::created, 1);
  EXPECT_EQ(Counter::destroyed, 1);
}

TEST(InlineQueue, Modify_Push_Move) {
  Counter::Reset();

  {
    Counter value(99);
    InlineQueue<Counter, 10> queue;
    queue.push(std::move(value));

    EXPECT_EQ(queue.size(), 1u);
    EXPECT_EQ(queue.front().value, 99);
    // NOLINTNEXTLINE(bugprone-use-after-move)
    EXPECT_EQ(value.value, 0);
  }

  EXPECT_EQ(Counter::created, 1);
  EXPECT_EQ(Counter::destroyed, 2);
  EXPECT_EQ(Counter::moved, 1);
}

TEST(InlineQueue, Modify_Emplace) {
  Counter::Reset();

  {
    InlineQueue<Counter, 10> queue;
    queue.emplace(314);

    EXPECT_EQ(queue.size(), 1u);
    EXPECT_EQ(queue.front().value, 314);
  }

  EXPECT_EQ(Counter::created, 1);
  EXPECT_EQ(Counter::destroyed, 1);
}

TEST(InlineQueue, Modify_Overwrite) {
  Counter::Reset();
  {
    InlineQueue<Counter, 2> queue(2);
    queue.push_overwrite(1);
    queue.emplace_overwrite(2);

    EXPECT_EQ(queue.size(), 2u);
    EXPECT_EQ(queue.front().value, 1);
    EXPECT_EQ(queue.back().value, 2);
  }
}

TEST(InlineQueue, Modify_Wrap) {
  Counter::Reset();

  {
    InlineQueue<Counter, 3> queue;
    queue.emplace(1);
    queue.emplace(2);
    queue.emplace(3);

    ASSERT_EQ(queue.size(), 3u);
    EXPECT_EQ(queue[0].value, 1);
    EXPECT_EQ(queue[1].value, 2);
    EXPECT_EQ(queue[2].value, 3);

    queue.pop();
    queue.emplace(4);

    ASSERT_EQ(queue.size(), 3u);
    EXPECT_EQ(queue[0].value, 2);
    EXPECT_EQ(queue[1].value, 3);
    EXPECT_EQ(queue[2].value, 4);
  }

  EXPECT_EQ(Counter::created, 4);
  EXPECT_EQ(Counter::destroyed, 4);
}

TEST(InlineQueue, Modify_Pop) {
  Counter::Reset();

  InlineQueue<Counter, 3> queue;
  queue.emplace(0);
  queue.pop();
  queue.emplace(0);
  queue.pop();
  queue.emplace(1);  // This wraps to the other end.
  queue.emplace(2);  // This is the first entry in storage.
  queue.emplace(3);
  // Content = {1, 2, 3}, Storage = [2, 3, 1]

  ASSERT_EQ(queue.size(), 3u);
  EXPECT_EQ(queue[0].value, 1);
  EXPECT_EQ(queue[1].value, 2);
  EXPECT_EQ(queue[2].value, 3);

  // This wraps around
  queue.pop();
  // Content = {2, 3}, Storage = [2, 3, x]

  EXPECT_EQ(queue.size(), 2u);
  EXPECT_EQ(queue[0].value, 2);
  EXPECT_EQ(queue[1].value, 3);

  queue.pop();
  // Content = {3}, Storage = [x, 3, x]
  ASSERT_EQ(queue.size(), 1u);
  EXPECT_EQ(queue[0].value, 3);

  EXPECT_EQ(Counter::created, 5);
  EXPECT_EQ(Counter::destroyed, 4);
}

TEST(InlineQueue, Generic) {
  InlineQueue<int, 10> queue({1, 2, 3, 4, 5});
  InlineQueue<int>& generic_queue(queue);

  EXPECT_EQ(generic_queue.size(), queue.size());
  EXPECT_EQ(generic_queue.max_size(), queue.max_size());

  uint16_t i = 0;
  for (int value : queue) {
    EXPECT_EQ(value, generic_queue[i]);
    i += 1;
  }

  i = 0;
  for (int value : generic_queue) {
    EXPECT_EQ(queue[i], value);
    i += 1;
  }
}

TEST(InlineQueue, ConstexprMaxSize) {
  InlineQueue<int, 10> queue;
  constexpr size_t kMaxSize = queue.max_size();
  EXPECT_EQ(queue.max_size(), kMaxSize);

  // Ensure the generic sized container does not have a constexpr max_size().
  [[maybe_unused]] InlineQueue<int>& generic_queue(queue);
#if PW_NC_TEST(InlineQueue_GenericMaxSize_NotConstexpr)
  PW_NC_EXPECT_CLANG(
      "kGenericMaxSize.* must be initialized by a constant expression");
  PW_NC_EXPECT_GCC("call to non-'constexpr' function .*InlineQueue.*max_size");
  [[maybe_unused]] constexpr size_t kGenericMaxSize = generic_queue.max_size();
#endif  // PW_NC_TEST
}

TEST(InlineQueue, StdMaxElement) {
  // Content = {1, 2, 3, 4}, Storage = [1, 2, 3, 4]
  InlineQueue<int, 4> queue = {1, 2, 3, 4};

  auto max_element_it = std::max_element(queue.begin(), queue.end());
  ASSERT_NE(max_element_it, queue.end());
  EXPECT_EQ(*max_element_it, 4);

  // Content = {2, 3, 4, 5}, Storage = [5, 2, 3, 4]
  queue.push_overwrite(5);

  max_element_it = std::max_element(queue.begin(), queue.end());
  ASSERT_NE(max_element_it, queue.end());
  EXPECT_EQ(*max_element_it, 5);

  // Content = {3, 4, 5}, Storage = [5, x, 3, 4]
  queue.pop();

  max_element_it = std::max_element(queue.begin(), queue.end());
  ASSERT_NE(max_element_it, queue.end());
  EXPECT_EQ(*max_element_it, 5);

  // Content = {}, Storage = [x, x, x, x]
  queue.clear();

  max_element_it = std::max_element(queue.begin(), queue.end());
  ASSERT_EQ(max_element_it, queue.end());
}

TEST(InlineQueue, StdMaxElementConst) {
  // Content = {1, 2, 3, 4}, Storage = [1, 2, 3, 4]
  InlineQueue<int, 4> queue = {1, 2, 3, 4};

  auto max_element_it = std::max_element(queue.cbegin(), queue.cend());
  ASSERT_NE(max_element_it, queue.cend());
  EXPECT_EQ(*max_element_it, 4);

  // Content = {2, 3, 4, 5}, Storage = [5, 2, 3, 4]
  queue.push_overwrite(5);

  max_element_it = std::max_element(queue.cbegin(), queue.cend());
  ASSERT_NE(max_element_it, queue.cend());
  EXPECT_EQ(*max_element_it, 5);

  // Content = {3, 4, 5}, Storage = [5, x, 3, 4]
  queue.pop();

  max_element_it = std::max_element(queue.cbegin(), queue.cend());
  ASSERT_NE(max_element_it, queue.cend());
  EXPECT_EQ(*max_element_it, 5);

  // Content = {}, Storage = [x, x, x, x]
  queue.clear();

  max_element_it = std::max_element(queue.cbegin(), queue.cend());
  ASSERT_EQ(max_element_it, queue.cend());
}

TEST(InlineQueue, OperatorPlus) {
  // Content = {0, 0, 1, 2}, Storage = [0, 0, 1, 2]
  InlineQueue<int, 4> queue = {0, 0, 1, 2};
  // Content = {0, 1, 2, 3}, Storage = [3, 0, 1, 2]
  queue.push_overwrite(3);
  // Content = {1, 2, 3, 4}, Storage = [3, 4, 1, 2]
  queue.push_overwrite(4);

  for (int i = 0; i < 4; i++) {
    ASSERT_EQ(*(queue.begin() + i), static_cast<int>(i + 1));
    ASSERT_EQ(*(i + queue.begin()), static_cast<int>(i + 1));
  }

  ASSERT_EQ(queue.begin() + queue.size(), queue.end());
}

TEST(InlineQueue, OperatorPlusPlus) {
  // Content = {0, 0, 1, 2}, Storage = [0, 0, 1, 2]
  InlineQueue<int, 4> queue = {0, 0, 1, 2};
  // Content = {0, 1, 2, 3}, Storage = [3, 0, 1, 2]
  queue.push_overwrite(3);
  // Content = {1, 2, 3, 4}, Storage = [3, 4, 1, 2]
  queue.push_overwrite(4);

  auto it = queue.begin();

  ASSERT_EQ(*it, 1);
  it++;
  ASSERT_EQ(*it, 2);
  it++;
  ASSERT_EQ(*it, 3);
  it++;
  ASSERT_EQ(*it, 4);
  it++;

  ASSERT_EQ(it, queue.end());
}

TEST(InlineQueue, OperatorPlusEquals) {
  // Content = {0, 0, 1, 2}, Storage = [0, 0, 1, 2]
  InlineQueue<int, 4> queue = {0, 0, 1, 2};
  // Content = {0, 1, 2, 3}, Storage = [3, 0, 1, 2]
  queue.push_overwrite(3);
  // Content = {1, 2, 3, 4}, Storage = [3, 4, 1, 2]
  queue.push_overwrite(4);

  auto it = queue.begin();

  ASSERT_EQ(*it, 1);
  it += 1;
  ASSERT_EQ(*it, 2);
  it += 1;
  ASSERT_EQ(*it, 3);
  it += 1;
  ASSERT_EQ(*it, 4);
  it += 1;
  ASSERT_EQ(it, queue.end());

  it = queue.begin();
  ASSERT_EQ(*it, 1);
  it += 2;
  ASSERT_EQ(*it, 3);
  it += 2;
  ASSERT_EQ(it, queue.end());

  it = queue.begin();
  it += queue.size();

  ASSERT_EQ(it, queue.end());
}

TEST(InlineQueue, OpeartorMinus) {
  // Content = {0, 0, 1, 2}, Storage = [0, 0, 1, 2]
  InlineQueue<int, 4> queue = {0, 0, 1, 2};
  // Content = {0, 1, 2, 3}, Storage = [3, 0, 1, 2]
  queue.push_overwrite(3);
  // Content = {1, 2, 3, 4}, Storage = [3, 4, 1, 2]
  queue.push_overwrite(4);

  for (int i = 1; i <= 4; i++) {
    ASSERT_EQ(*(queue.end() - i), static_cast<int>(5 - i));
  }

  ASSERT_EQ(queue.end() - queue.size(), queue.begin());
}
TEST(InlineQueue, OperatorMinusMinus) {
  // Content = {0, 0, 1, 2}, Storage = [0, 0, 1, 2]
  InlineQueue<int, 4> queue = {0, 0, 1, 2};
  // Content = {0, 1, 2, 3}, Storage = [3, 0, 1, 2]
  queue.push_overwrite(3);
  // Content = {1, 2, 3, 4}, Storage = [3, 4, 1, 2]
  queue.push_overwrite(4);

  auto it = queue.end();

  it--;
  ASSERT_EQ(*it, 4);
  it--;
  ASSERT_EQ(*it, 3);
  it--;
  ASSERT_EQ(*it, 2);
  it--;
  ASSERT_EQ(*it, 1);

  ASSERT_EQ(it, queue.begin());
}

TEST(InlineQueue, OperatorMinusEquals) {
  // Content = {0, 0, 1, 2}, Storage = [0, 0, 1, 2]
  InlineQueue<int, 4> queue = {0, 0, 1, 2};
  // Content = {0, 1, 2, 3}, Storage = [3, 0, 1, 2]
  queue.push_overwrite(3);
  // Content = {1, 2, 3, 4}, Storage = [3, 4, 1, 2]
  queue.push_overwrite(4);

  auto it = queue.end();

  it -= 1;
  ASSERT_EQ(*it, 4);
  it -= 1;
  ASSERT_EQ(*it, 3);
  it -= 1;
  ASSERT_EQ(*it, 2);
  it -= 1;
  ASSERT_EQ(*it, 1);

  ASSERT_EQ(it, queue.begin());

  it = queue.end();

  it -= 2;
  ASSERT_EQ(*it, 3);
  it -= 2;
  ASSERT_EQ(*it, 1);

  ASSERT_EQ(it, queue.begin());

  it = queue.end();
  it -= queue.size();

  ASSERT_EQ(it, queue.begin());
}

TEST(InlineQueue, OperatorSquareBracket) {
  // Content = {0, 0, 1, 2}, Storage = [0, 0, 1, 2]
  InlineQueue<int, 4> queue = {0, 0, 1, 2};
  // Content = {0, 1, 2, 3}, Storage = [3, 0, 1, 2]
  queue.push_overwrite(3);
  // Content = {1, 2, 3, 4}, Storage = [3, 4, 1, 2]
  queue.push_overwrite(4);

  for (unsigned short i = 0; i < queue.size(); i++) {
    ASSERT_EQ(queue.begin()[i], static_cast<int>(i + 1));
  }
}

TEST(InlineQueue, OperatorLessThan) {
  // Content = {0, 0, 1, 2}, Storage = [0, 0, 1, 2]
  InlineQueue<int, 4> queue = {0, 0, 1, 2};
  // Content = {0, 1, 2, 3}, Storage = [3, 0, 1, 2]
  queue.push_overwrite(3);
  // Content = {1, 2, 3, 4}, Storage = [3, 4, 1, 2]
  queue.push_overwrite(4);

  for (int i = 0; i < queue.size(); i++) {
    for (int j = 0; j < i; j++) {
      ASSERT_TRUE((queue.begin() + j) < (queue.begin() + i));
    }

    ASSERT_TRUE((queue.begin() + i) < queue.end());
  }
}

TEST(InlineQueue, OperatorLessThanEqual) {
  // Content = {0, 0, 1, 2}, Storage = [0, 0, 1, 2]
  InlineQueue<int, 4> queue = {0, 0, 1, 2};
  // Content = {0, 1, 2, 3}, Storage = [3, 0, 1, 2]
  queue.push_overwrite(3);
  // Content = {1, 2, 3, 4}, Storage = [3, 4, 1, 2]
  queue.push_overwrite(4);

  for (int i = 0; i < queue.size(); i++) {
    for (int j = 0; j <= i; j++) {
      ASSERT_TRUE((queue.begin() + j) <= (queue.begin() + i));
    }

    ASSERT_TRUE((queue.begin() + i) <= queue.end());
  }
}

TEST(InlineQueue, OperatorGreater) {
  // Content = {0, 0, 1, 2}, Storage = [0, 0, 1, 2]
  InlineQueue<int, 4> queue = {0, 0, 1, 2};
  // Content = {0, 1, 2, 3}, Storage = [3, 0, 1, 2]
  queue.push_overwrite(3);
  // Content = {1, 2, 3, 4}, Storage = [3, 4, 1, 2]
  queue.push_overwrite(4);

  for (int i = 0; i < queue.size(); i++) {
    for (int j = i + 1; j < queue.size(); j++) {
      ASSERT_TRUE((queue.begin() + j) > (queue.begin() + i));
    }

    ASSERT_TRUE(queue.end() > (queue.begin() + i));
  }
}

TEST(InlineQueue, OperatorGreaterThanEqual) {
  // Content = {0, 0, 1, 2}, Storage = [0, 0, 1, 2]
  InlineQueue<int, 4> queue = {0, 0, 1, 2};
  // Content = {0, 1, 2, 3}, Storage = [3, 0, 1, 2]
  queue.push_overwrite(3);
  // Content = {1, 2, 3, 4}, Storage = [3, 4, 1, 2]
  queue.push_overwrite(4);

  for (int i = 0; i < queue.size(); i++) {
    for (int j = i; j < queue.size(); j++) {
      ASSERT_TRUE((queue.begin() + j) >= (queue.begin() + i));
    }

    ASSERT_TRUE(queue.end() >= (queue.begin() + i));
  }
}

TEST(InlineQueue, DereferenceOperator) {
  // Content = {0, 0, 1, 2}, Storage = [0, 0, 1, 2]
  InlineQueue<int, 4> queue = {0, 0, 1, 2};
  // Content = {0, 1, 2, 3}, Storage = [3, 0, 1, 2]
  queue.push_overwrite(3);
  // Content = {1, 2, 3, 4}, Storage = [3, 4, 1, 2]
  queue.push_overwrite(4);

  for (int i = 0; i < queue.size(); i++) {
    const auto it = queue.begin() + i;
    ASSERT_EQ(*(it.operator->()), static_cast<int>(i + 1));
  }
}

// Test that InlineQueue<T> is trivially destructible when its type is.
static_assert(std::is_trivially_destructible_v<InlineQueue<int, 4>>);

static_assert(std::is_trivially_destructible_v<MoveOnly>);
static_assert(std::is_trivially_destructible_v<InlineQueue<MoveOnly, 1>>);

static_assert(std::is_trivially_destructible_v<CopyOnly>);
static_assert(std::is_trivially_destructible_v<InlineQueue<CopyOnly, 99>>);

static_assert(!std::is_trivially_destructible_v<Counter>);
static_assert(!std::is_trivially_destructible_v<InlineQueue<Counter, 99>>);

// Generic-capacity queues cannot be constructed or destructed.
static_assert(!std::is_constructible_v<InlineQueue<int>>);
static_assert(!std::is_destructible_v<InlineQueue<int>>);

// Tests that InlineQueue<T> does not have any extra padding.
static_assert(sizeof(InlineQueue<uint8_t, 1>) ==
              sizeof(InlineQueue<uint8_t>::size_type) * 4 +
                  std::max(sizeof(InlineQueue<uint8_t>::size_type),
                           sizeof(uint8_t)));
static_assert(sizeof(InlineQueue<uint8_t, 2>) ==
              sizeof(InlineQueue<uint8_t>::size_type) * 4 +
                  2 * sizeof(uint8_t));
static_assert(sizeof(InlineQueue<uint16_t, 1>) ==
              sizeof(InlineQueue<uint16_t>::size_type) * 4 + sizeof(uint16_t));
static_assert(sizeof(InlineQueue<uint32_t, 1>) ==
              sizeof(InlineQueue<uint32_t>::size_type) * 4 + sizeof(uint32_t));
static_assert(sizeof(InlineQueue<uint64_t, 1>) ==
              sizeof(InlineQueue<uint64_t>::size_type) * 4 + sizeof(uint64_t));

// Test that InlineQueue<T> is copy assignable
static_assert(std::is_copy_assignable_v<InlineQueue<int>::iterator>);
static_assert(std::is_copy_assignable_v<InlineQueue<int, 4>::iterator>);

// Test that InlineQueue<T>::iterator can be converted to a const_iterator
static_assert(std::is_convertible<InlineQueue<int>::iterator,
                                  InlineQueue<int>::const_iterator>::value);
static_assert(std::is_convertible<InlineQueue<int, 4>::iterator,
                                  InlineQueue<int, 4>::const_iterator>::value);

// Test that InlineQueue<T>::const_iterator can NOT be converted to a iterator
static_assert(!std::is_convertible<InlineQueue<int>::const_iterator,
                                   InlineQueue<int>::iterator>::value);
static_assert(!std::is_convertible<InlineQueue<int, 4>::const_iterator,
                                   InlineQueue<int, 4>::iterator>::value);

}  // namespace
}  // namespace pw::containers
