// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/containers/checked_range.h"
#include <algorithm>
#include <array>
#include <initializer_list>
#include <type_traits>

#include "base/strings/string_piece.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace base {

TEST(CheckedContiguousRange, Constructor_Default) {
  constexpr CheckedContiguousRange<std::vector<int>> range;
  static_assert(range.data() == nullptr, "");
  static_assert(range.size() == 0, "");
  static_assert(range.empty(), "");
  static_assert(range.begin() == range.end(), "");
}

TEST(CheckedContiguousRange, Constructor_Vector) {
  std::vector<int> vector = {1, 2, 3, 4, 5};
  CheckedContiguousRange<std::vector<int>> range(vector);
  EXPECT_EQ(vector.data(), range.data());
  EXPECT_EQ(vector.size(), range.size());
}

TEST(CheckedContiguousRange, Constructor_String) {
  std::string str = "Hello World";
  CheckedContiguousRange<std::string> range(str);
  EXPECT_EQ(str.data(), range.data());
  EXPECT_EQ(str.size(), range.size());
}

TEST(CheckedContiguousRange, Constructor_Array) {
  static constexpr int array[] = {1, 2, 3, 4, 5};
  constexpr CheckedContiguousRange<const int[5]> range(array);
  static_assert(data(array) == range.data(), "");
  static_assert(size(array) == range.size(), "");
}

TEST(CheckedContiguousRange, Constructor_StdArray) {
  static constexpr std::array<int, 5> array = {1, 2, 3, 4, 5};
  constexpr CheckedContiguousRange<const std::array<int, 5>> range(array);
  static_assert(data(array) == range.data(), "");
  static_assert(base::size(array) == range.size(), "");
}

TEST(CheckedContiguousRange, Constructor_StringPiece) {
  static constexpr base::StringPiece str = "Hello World";
  constexpr CheckedContiguousRange<const base::StringPiece> range(str);
  static_assert(str.data() == range.data(), "");
  static_assert(str.size() == range.size(), "");
}

TEST(CheckedContiguousRange, Constructor_InitializerList) {
  static constexpr std::initializer_list<int> il = {1, 2, 3, 4, 5};
  constexpr CheckedContiguousRange<const std::initializer_list<int>> range(il);
  static_assert(base::data(il) == range.data(), "");
  static_assert(base::size(il) == range.size(), "");
}

TEST(CheckedContiguousRange, Constructor_Copy) {
  std::vector<int> vector = {1, 2, 3, 4, 5};
  CheckedContiguousRange<std::vector<int>> range(vector);
  CheckedContiguousRange<std::vector<int>> copy(range);
  EXPECT_EQ(vector.data(), copy.data());
  EXPECT_EQ(vector.size(), copy.size());
}

TEST(CheckedContiguousRange, Constructor_Move) {
  std::vector<int> vector = {1, 2, 3, 4, 5};
  CheckedContiguousRange<std::vector<int>> range(vector);
  CheckedContiguousRange<std::vector<int>> move(std::move(range));
  EXPECT_EQ(vector.data(), move.data());
  EXPECT_EQ(vector.size(), move.size());
}

TEST(CheckedContiguousRange, Copy_Assign) {
  std::vector<int> vector = {1, 2, 3, 4, 5};
  CheckedContiguousRange<std::vector<int>> range(vector);
  CheckedContiguousRange<std::vector<int>> copy;
  copy = range;
  EXPECT_EQ(vector.data(), copy.data());
  EXPECT_EQ(vector.size(), copy.size());
}

TEST(CheckedContiguousRange, Move_Assign) {
  std::vector<int> vector = {1, 2, 3, 4, 5};
  CheckedContiguousRange<std::vector<int>> range(vector);
  CheckedContiguousRange<std::vector<int>> move;
  move = std::move(range);
  EXPECT_EQ(vector.data(), move.data());
  EXPECT_EQ(vector.size(), move.size());
}

TEST(CheckedContiguousRange, Iterators) {
  std::vector<int> vector;
  CheckedContiguousRange<const std::vector<int>> range(vector);

  // Check that all ranges by the iterators compare equal, even when elements
  // are added.
  for (size_t i = 0; i < 5; ++i) {
    vector.push_back(i);
    EXPECT_TRUE(
        std::equal(vector.begin(), vector.end(), range.begin(), range.end()));
    EXPECT_TRUE(std::equal(vector.cbegin(), vector.cend(), range.cbegin(),
                           range.cend()));
    EXPECT_TRUE(std::equal(vector.rbegin(), vector.rend(), range.rbegin(),
                           range.rend()));
    EXPECT_TRUE(std::equal(vector.crbegin(), vector.crend(), range.crbegin(),
                           range.crend()));
  }
}

TEST(CheckedContiguousRange, Front) {
  static constexpr std::array<int, 5> array = {1, 2, 3, 4, 5};
  constexpr CheckedContiguousRange<const std::array<int, 5>> range(array);
  static_assert(array.front() == range.front(), "");
}

TEST(CheckedContiguousRange, Back) {
  static constexpr std::array<int, 5> array = {1, 2, 3, 4, 5};
  constexpr CheckedContiguousRange<const std::array<int, 5>> range(array);
  static_assert(array.back() == range.back(), "");
}

TEST(CheckedContiguousRange, OperatorAt_Constexpr) {
  static constexpr std::array<int, 5> array = {1, 2, 3, 4, 5};
  constexpr CheckedContiguousRange<const std::array<int, 5>> range(array);
  static_assert(array[0] == range[0], "");
  static_assert(array[1] == range[1], "");
  static_assert(array[2] == range[2], "");
  static_assert(array[3] == range[3], "");
  static_assert(array[4] == range[4], "");
}

TEST(CheckedContiguousRange, Mutable_OperatorAt) {
  std::vector<int> vector = {1, 2, 3, 4, 5};
  CheckedContiguousRange<std::vector<int>> range(vector);

  EXPECT_EQ(vector[0], range[0]);
  range[0] = 2;
  EXPECT_EQ(vector[0], 2);
}

TEST(CheckedContiguousRange, Mutable_Data) {
  std::vector<int> vector = {3, 1, 4, 2, 5};
  CheckedContiguousRange<std::vector<int>> range(vector);

  EXPECT_FALSE(std::is_sorted(vector.begin(), vector.end()));
  std::sort(range.data(), range.data() + range.size());
  EXPECT_TRUE(std::is_sorted(vector.begin(), vector.end()));
}

TEST(CheckedContiguousRange, DataSizeEmpty_Constexpr) {
  static constexpr std::array<int, 0> array = {};
  constexpr CheckedContiguousRange<const std::array<int, 0>> range(array);
  static_assert(data(array) == range.data(), "");
  static_assert(data(array) == range.cdata(), "");
  static_assert(base::size(array) == range.size(), "");
  static_assert(range.empty(), "");
}

TEST(CheckedContiguousRange, MakeCheckedContiguousRange) {
  using Vec = std::vector<int>;

  static_assert(std::is_same<CheckedContiguousRange<Vec>,
                             decltype(MakeCheckedContiguousRange(
                                 std::declval<Vec&>()))>::value,
                "");

  static_assert(std::is_same<CheckedContiguousRange<const Vec>,
                             decltype(MakeCheckedContiguousRange(
                                 std::declval<const Vec&>()))>::value,
                "");

  static_assert(std::is_same<CheckedContiguousRange<Vec>,
                             decltype(MakeCheckedContiguousRange(
                                 std::declval<Vec&&>()))>::value,
                "");

  static_assert(std::is_same<CheckedContiguousRange<const Vec>,
                             decltype(MakeCheckedContiguousRange(
                                 std::declval<const Vec&&>()))>::value,
                "");
}

TEST(CheckedContiguousRange, MakeCheckedContiguousConstRange) {
  using Vec = std::vector<int>;

  static_assert(std::is_same<CheckedContiguousRange<const Vec>,
                             decltype(MakeCheckedContiguousConstRange(
                                 std::declval<Vec&>()))>::value,
                "");

  static_assert(std::is_same<CheckedContiguousRange<const Vec>,
                             decltype(MakeCheckedContiguousConstRange(
                                 std::declval<const Vec&>()))>::value,
                "");

  static_assert(std::is_same<CheckedContiguousRange<const Vec>,
                             decltype(MakeCheckedContiguousConstRange(
                                 std::declval<Vec&&>()))>::value,
                "");

  static_assert(std::is_same<CheckedContiguousRange<const Vec>,
                             decltype(MakeCheckedContiguousConstRange(
                                 std::declval<const Vec&&>()))>::value,
                "");
}

TEST(CheckedContiguousRange, Conversions) {
  using T = std::vector<int>;
  // Check that adding const is allowed, but removing const is not.
  static_assert(std::is_convertible<CheckedContiguousRange<T>,
                                    CheckedContiguousRange<const T>>::value,
                "");
  static_assert(!std::is_constructible<CheckedContiguousRange<T>,
                                       CheckedContiguousRange<const T>>::value,
                "");

  struct Base {};
  struct Derived : Base {};
  // Check that trying to assign arrays from a derived class fails.
  static_assert(
      !std::is_constructible<CheckedContiguousRange<Base[5]>,
                             CheckedContiguousRange<Derived[5]>>::value,
      "");
}

TEST(CheckedContiguousRange, OutOfBoundsDeath) {
  std::vector<int> empty_vector;
  CheckedContiguousRange<std::vector<int>> empty_range(empty_vector);
  ASSERT_DEATH_IF_SUPPORTED(empty_range[0], "");
  ASSERT_DEATH_IF_SUPPORTED(empty_range.front(), "");
  ASSERT_DEATH_IF_SUPPORTED(empty_range.back(), "");

  static constexpr int array[] = {0, 1, 2};
  constexpr CheckedContiguousRange<const int[3]> range(array);
  ASSERT_DEATH_IF_SUPPORTED(range[3], "");
}

}  // namespace base
