// Copyright 2020 The Abseil 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 <cstdlib>
#include <ctime>
#include <memory>
#include <random>
#include <sstream>

#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/base/config.h"
#include "absl/base/internal/raw_logging.h"
#include "absl/base/macros.h"
#include "absl/debugging/leak_check.h"
#include "absl/strings/internal/cord_internal.h"
#include "absl/strings/internal/cord_rep_ring.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"

extern thread_local bool cord_ring;

// TOOD(b/177688959): weird things happened with the original test
#define ASAN_BUG_177688959_FIXED false

namespace absl {
ABSL_NAMESPACE_BEGIN
namespace {

using RandomEngine = std::mt19937_64;

using ::absl::cord_internal::CordRep;
using ::absl::cord_internal::CordRepConcat;
using ::absl::cord_internal::CordRepExternal;
using ::absl::cord_internal::CordRepFlat;
using ::absl::cord_internal::CordRepRing;
using ::absl::cord_internal::CordRepSubstring;

using ::absl::cord_internal::CONCAT;
using ::absl::cord_internal::EXTERNAL;
using ::absl::cord_internal::SUBSTRING;

using testing::ElementsAre;
using testing::ElementsAreArray;
using testing::Eq;
using testing::Ge;
using testing::Le;
using testing::Lt;
using testing::Ne;
using testing::SizeIs;

using index_type = CordRepRing::index_type;

enum InputShareMode { kPrivate, kShared, kSharedIndirect };

// TestParam class used by all test fixtures.
// Not all fixtures use all possible input combinations
struct TestParam {
  TestParam() = default;
  explicit TestParam(InputShareMode input_share_mode)
      : input_share_mode(input_share_mode) {}

  // Run the test with the 'rep under test' to be privately owned.
  // Otherwise, the rep has a shared ref count of 2 or higher.
  bool refcount_is_one = true;

  // Run the test with the 'rep under test' being allocated with enough capacity
  // to accommodate any modifications made to it. Otherwise, the rep has zero
  // extra (reserve) capacity.
  bool with_capacity = true;

  // For test providing possibly shared input such as Append(.., CordpRep*),
  // this field defines if that input is adopted with a refcount of one
  // (privately owned / donated), or shared. For composite inputs such as
  // 'substring of flat', we also have the 'shared indirect' value which means
  // the top level node is not shared, but the contained child node is shared.
  InputShareMode input_share_mode = kPrivate;

  std::string ToString() const {
    return absl::StrCat(refcount_is_one ? "Private" : "Shared",
                        with_capacity ? "" : "_NoCapacity",
                        (input_share_mode == kPrivate) ? ""
                        : (input_share_mode == kShared)
                            ? "_SharedInput"
                            : "_IndirectSharedInput");
  }
};
using TestParams = std::vector<TestParam>;

// Matcher validating when mutable copies are required / performed.
MATCHER_P2(EqIfPrivate, param, rep,
           absl::StrCat("Equal 0x", absl::Hex(rep), " if private")) {
  return param.refcount_is_one ? arg == rep : arg != rep;
}

// Matcher validating when mutable copies are required / performed.
MATCHER_P2(EqIfPrivateAndCapacity, param, rep,
           absl::StrCat("Equal 0x", absl::Hex(rep),
                        " if private and capacity")) {
  return (param.refcount_is_one && param.with_capacity) ? arg == rep
                                                        : arg != rep;
}

MATCHER_P2(EqIfInputPrivate, param, rep, "Equal if input is private") {
  return param.input_share_mode == kPrivate ? arg == rep : arg != rep;
}

// Matcher validating the core in-variants of the CordRepRing instance.
MATCHER(IsValidRingBuffer, "RingBuffer is valid") {
  std::stringstream ss;
  if (!arg->IsValid(ss)) {
    *result_listener << "\nERROR: " << ss.str() << "\nRING = " << *arg;
    return false;
  }
  return true;
}

// Returns the flats contained in the provided CordRepRing
std::vector<string_view> ToFlats(const CordRepRing* r) {
  std::vector<string_view> flats;
  flats.reserve(r->entries());
  index_type pos = r->head();
  do {
    flats.push_back(r->entry_data(pos));
  } while ((pos = r->advance(pos)) != r->tail());
  return flats;
}

class not_a_string_view {
 public:
  explicit not_a_string_view(absl::string_view s)
      : data_(s.data()), size_(s.size()) {}
  explicit not_a_string_view(const void* data, size_t size)
      : data_(data), size_(size) {}

  not_a_string_view remove_prefix(size_t n) const {
    return not_a_string_view(static_cast<const char*>(data_) + n, size_ - n);
  }

  not_a_string_view remove_suffix(size_t n) const {
    return not_a_string_view(data_, size_ - n);
  }

  const void* data() const { return data_; }
  size_t size() const { return size_; }

 private:
  const void* data_;
  size_t size_;
};

bool operator==(not_a_string_view lhs, not_a_string_view rhs) {
  return lhs.data() == rhs.data() && lhs.size() == rhs.size();
}

std::ostream& operator<<(std::ostream& s, not_a_string_view rhs) {
  return s << "{ data: " << rhs.data() << " size: " << rhs.size() << "}";
}

std::vector<not_a_string_view> ToRawFlats(const CordRepRing* r) {
  std::vector<not_a_string_view> flats;
  flats.reserve(r->entries());
  index_type pos = r->head();
  do {
    flats.emplace_back(r->entry_data(pos));
  } while ((pos = r->advance(pos)) != r->tail());
  return flats;
}

// Returns the value contained in the provided CordRepRing
std::string ToString(const CordRepRing* r) {
  std::string value;
  value.reserve(r->length);
  index_type pos = r->head();
  do {
    absl::string_view sv = r->entry_data(pos);
    value.append(sv.data(), sv.size());
  } while ((pos = r->advance(pos)) != r->tail());
  return value;
}

// Creates a flat for testing
CordRep* MakeFlat(absl::string_view s, size_t extra = 0) {
  CordRepFlat* flat = CordRepFlat::New(s.length() + extra);
  memcpy(flat->Data(), s.data(), s.length());
  flat->length = s.length();
  return flat;
}

// Creates an external node for testing
CordRepExternal* MakeExternal(absl::string_view s) {
  struct Rep : public CordRepExternal {
    std::string s;
    explicit Rep(absl::string_view s) : s(s) {
      this->tag = EXTERNAL;
      this->base = s.data();
      this->length = s.length();
      this->releaser_invoker = [](CordRepExternal* self) {
        delete static_cast<Rep*>(self);
      };
    }
  };
  return new Rep(s);
}

CordRepExternal* MakeFakeExternal(size_t length) {
  struct Rep : public CordRepExternal {
    std::string s;
    explicit Rep(size_t len) {
      this->tag = EXTERNAL;
      this->base = this->storage;
      this->length = len;
      this->releaser_invoker = [](CordRepExternal* self) {
        delete static_cast<Rep*>(self);
      };
    }
  };
  return new Rep(length);
}

// Creates a flat or an external node for testing depending on the size.
CordRep* MakeLeaf(absl::string_view s, size_t extra = 0) {
  if (s.size() <= absl::cord_internal::kMaxFlatLength) {
    return MakeFlat(s, extra);
  } else {
    return MakeExternal(s);
  }
}

// Creates a substring node
CordRepSubstring* MakeSubstring(size_t start, size_t len, CordRep* rep) {
  auto* sub = new CordRepSubstring;
  sub->tag = SUBSTRING;
  sub->start = start;
  sub->length = (len <= 0) ? rep->length - start + len : len;
  sub->child = rep;
  return sub;
}

// Creates a substring node removing the specified prefix
CordRepSubstring* RemovePrefix(size_t start, CordRep* rep) {
  return MakeSubstring(start, rep->length - start, rep);
}

// Creates a substring node removing the specified suffix
CordRepSubstring* RemoveSuffix(size_t length, CordRep* rep) {
  return MakeSubstring(0, rep->length - length, rep);
}

CordRepConcat* MakeConcat(CordRep* left, CordRep* right, int depth = 0) {
  auto* concat = new CordRepConcat;
  concat->tag = CONCAT;
  concat->length = left->length + right->length;
  concat->left = left;
  concat->right = right;
  concat->set_depth(depth);
  return concat;
}

enum Composition { kMix, kAppend, kPrepend };

Composition RandomComposition() {
  RandomEngine rng(testing::GTEST_FLAG(random_seed));
  return (rng() & 1) ? kMix : ((rng() & 1) ? kAppend : kPrepend);
}

absl::string_view ToString(Composition composition) {
  switch (composition) {
    case kAppend:
      return "Append";
    case kPrepend:
      return "Prepend";
    case kMix:
      return "Mix";
  }
  assert(false);
  return "???";
}

constexpr const char* kFox = "The quick brown fox jumps over the lazy dog";
constexpr const char* kFoxFlats[] = {"The ", "quick ", "brown ",
                                     "fox ", "jumps ", "over ",
                                     "the ", "lazy ",  "dog"};
constexpr const char* kAlphabet = "abcdefghijklmnopqrstuvwxyz";

CordRepRing* FromFlats(Span<const char* const> flats,
                       Composition composition = kAppend) {
  if (flats.empty()) return nullptr;
  CordRepRing* ring = nullptr;
  switch (composition) {
    case kAppend:
      ring = CordRepRing::Create(MakeLeaf(flats.front()), flats.size() - 1);
      for (int i = 1; i < flats.size(); ++i) {
        ring = CordRepRing::Append(ring, MakeLeaf(flats[i]));
      }
      break;
    case kPrepend:
      ring = CordRepRing::Create(MakeLeaf(flats.back()), flats.size() - 1);
      for (int i = static_cast<int>(flats.size() - 2); i >= 0; --i) {
        ring = CordRepRing::Prepend(ring, MakeLeaf(flats[i]));
      }
      break;
    case kMix:
      size_t middle1 = flats.size() / 2, middle2 = middle1;
      ring = CordRepRing::Create(MakeLeaf(flats[middle1]), flats.size() - 1);
      if (!flats.empty()) {
        if ((flats.size() & 1) == 0) {
          ring = CordRepRing::Prepend(ring, MakeLeaf(flats[--middle1]));
        }
        for (int i = 1; i <= middle1; ++i) {
          ring = CordRepRing::Prepend(ring, MakeLeaf(flats[middle1 - i]));
          ring = CordRepRing::Append(ring, MakeLeaf(flats[middle2 + i]));
        }
      }
      break;
  }
  EXPECT_THAT(ToFlats(ring), ElementsAreArray(flats));
  return ring;
}

std::ostream& operator<<(std::ostream& s, const TestParam& param) {
  return s << param.ToString();
}

std::string TestParamToString(const testing::TestParamInfo<TestParam>& info) {
  return info.param.ToString();
}

class CordRingTest : public testing::Test {
 public:
  ~CordRingTest() override {
#if ASAN_BUG_177688959_FIXED
    for (CordRep* rep : unrefs_) {
      CordRep::Unref(rep);
    }
#endif
  }

  template <typename CordRepType>
  CordRepType* NeedsUnref(CordRepType* rep) {
    assert(rep);
#if ASAN_BUG_177688959_FIXED
    unrefs_.push_back(rep);
#endif
    return rep;
  }

  template <typename CordRepType>
  CordRepType* Ref(CordRepType* rep) {
    CordRep::Ref(rep);
    return NeedsUnref(rep);
  }

  void Unref(CordRep* rep) {
#if !ASAN_BUG_177688959_FIXED
    CordRep::Unref(rep);
#endif
  }

 private:
#if ASAN_BUG_177688959_FIXED
  std::vector<CordRep*> unrefs_;
#endif
};

class CordRingTestWithParam : public testing::TestWithParam<TestParam> {
 public:
  ~CordRingTestWithParam() override {
#if ASAN_BUG_177688959_FIXED
    for (CordRep* rep : unrefs_) {
      CordRep::Unref(rep);
    }
#endif
  }

  CordRepRing* CreateWithCapacity(CordRep* child, size_t extra_capacity) {
    if (!GetParam().with_capacity) extra_capacity = 0;
    CordRepRing* ring = CordRepRing::Create(child, extra_capacity);
    ring->SetCapacityForTesting(1 + extra_capacity);
    return RefIfShared(ring);
  }

  bool Shared() const { return !GetParam().refcount_is_one; }
  bool InputShared() const { return GetParam().input_share_mode == kShared; }
  bool InputSharedIndirect() const {
    return GetParam().input_share_mode == kSharedIndirect;
  }

  template <typename CordRepType>
  CordRepType* NeedsUnref(CordRepType* rep) {
    assert(rep);
#if ASAN_BUG_177688959_FIXED
    unrefs_.push_back(rep);
#endif
    return rep;
  }

  template <typename CordRepType>
  CordRepType* Ref(CordRepType* rep) {
    CordRep::Ref(rep);
    return NeedsUnref(rep);
  }

  void Unref(CordRep* rep) {
#if !ASAN_BUG_177688959_FIXED
    CordRep::Unref(rep);
#endif
  }

  template <typename CordRepType>
  CordRepType* RefIfShared(CordRepType* rep) {
    return Shared() ? Ref(rep) : rep;
  }

  void UnrefIfShared(CordRep* rep) {
    if (Shared()) Unref(rep);
  }

  template <typename CordRepType>
  CordRepType* RefIfInputShared(CordRepType* rep) {
    return InputShared() ? Ref(rep) : rep;
  }

  void UnrefIfInputShared(CordRep* rep) {
    if (InputShared()) Unref(rep);
  }

  template <typename CordRepType>
  CordRepType* RefIfInputSharedIndirect(CordRepType* rep) {
    return InputSharedIndirect() ? Ref(rep) : rep;
  }

  void UnrefIfInputSharedIndirect(CordRep* rep) {
    if (InputSharedIndirect()) Unref(rep);
  }

 private:
#if ASAN_BUG_177688959_FIXED
  std::vector<CordRep*> unrefs_;
#endif
};

class CordRingCreateTest : public CordRingTestWithParam {
 public:
  static TestParams CreateTestParams() {
    TestParams params;
    params.emplace_back(InputShareMode::kPrivate);
    params.emplace_back(InputShareMode::kShared);
    return params;
  }
};

class CordRingSubTest : public CordRingTestWithParam {
 public:
  static TestParams CreateTestParams() {
    TestParams params;
    for (bool refcount_is_one : {true, false}) {
      TestParam param;
      param.refcount_is_one = refcount_is_one;
      params.push_back(param);
    }
    return params;
  }
};

class CordRingBuildTest : public CordRingTestWithParam {
 public:
  static TestParams CreateTestParams() {
    TestParams params;
    for (bool refcount_is_one : {true, false}) {
      for (bool with_capacity : {true, false}) {
        TestParam param;
        param.refcount_is_one = refcount_is_one;
        param.with_capacity = with_capacity;
        params.push_back(param);
      }
    }
    return params;
  }
};

class CordRingCreateFromTreeTest : public CordRingTestWithParam {
 public:
  static TestParams CreateTestParams() {
    TestParams params;
    params.emplace_back(InputShareMode::kPrivate);
    params.emplace_back(InputShareMode::kShared);
    params.emplace_back(InputShareMode::kSharedIndirect);
    return params;
  }
};

class CordRingBuildInputTest : public CordRingTestWithParam {
 public:
  static TestParams CreateTestParams() {
    TestParams params;
    for (bool refcount_is_one : {true, false}) {
      for (bool with_capacity : {true, false}) {
        for (InputShareMode share_mode : {kPrivate, kShared, kSharedIndirect}) {
          TestParam param;
          param.refcount_is_one = refcount_is_one;
          param.with_capacity = with_capacity;
          param.input_share_mode = share_mode;
          params.push_back(param);
        }
      }
    }
    return params;
  }
};

INSTANTIATE_TEST_CASE_P(WithParam, CordRingSubTest,
                        testing::ValuesIn(CordRingSubTest::CreateTestParams()),
                        TestParamToString);

INSTANTIATE_TEST_CASE_P(
    WithParam, CordRingCreateTest,
    testing::ValuesIn(CordRingCreateTest::CreateTestParams()),
    TestParamToString);

INSTANTIATE_TEST_CASE_P(
    WithParam, CordRingCreateFromTreeTest,
    testing::ValuesIn(CordRingCreateFromTreeTest::CreateTestParams()),
    TestParamToString);

INSTANTIATE_TEST_CASE_P(
    WithParam, CordRingBuildTest,
    testing::ValuesIn(CordRingBuildTest::CreateTestParams()),
    TestParamToString);

INSTANTIATE_TEST_CASE_P(
    WithParam, CordRingBuildInputTest,
    testing::ValuesIn(CordRingBuildInputTest::CreateTestParams()),
    TestParamToString);

TEST_P(CordRingCreateTest, CreateFromFlat) {
  absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
  CordRepRing* result = NeedsUnref(CordRepRing::Create(MakeFlat(str1)));
  ASSERT_THAT(result, IsValidRingBuffer());
  EXPECT_THAT(result->length, Eq(str1.size()));
  EXPECT_THAT(ToFlats(result), ElementsAre(str1));
  Unref(result);
}

TEST_P(CordRingCreateTest, CreateFromRing) {
  CordRepRing* ring = RefIfShared(FromFlats(kFoxFlats));
  CordRepRing* result = NeedsUnref(CordRepRing::Create(ring));
  ASSERT_THAT(result, IsValidRingBuffer());
  EXPECT_THAT(result, EqIfPrivate(GetParam(), ring));
  EXPECT_THAT(ToFlats(result), ElementsAreArray(kFoxFlats));
  UnrefIfShared(ring);
  Unref(result);
}

TEST_P(CordRingCreateFromTreeTest, CreateFromSubstringRing) {
  CordRepRing* ring = RefIfInputSharedIndirect(FromFlats(kFoxFlats));
  CordRep* sub = RefIfInputShared(MakeSubstring(2, 11, ring));
  CordRepRing* result = NeedsUnref(CordRepRing::Create(sub));
  ASSERT_THAT(result, IsValidRingBuffer());
  EXPECT_THAT(result, EqIfInputPrivate(GetParam(), ring));
  EXPECT_THAT(ToString(result), string_view(kFox).substr(2, 11));
  UnrefIfInputSharedIndirect(ring);
  UnrefIfInputShared(sub);
  Unref(result);
}

TEST_F(CordRingTest, CreateWithIllegalExtraCapacity) {
  CordRep* flat = NeedsUnref(MakeFlat("Hello world"));
#if defined(ABSL_HAVE_EXCEPTIONS)
  try {
    CordRepRing::Create(flat, CordRepRing::kMaxCapacity);
    GTEST_FAIL() << "expected std::length_error exception";
  } catch (const std::length_error&) {
  }
#elif defined(GTEST_HAS_DEATH_TEST)
  EXPECT_DEATH(CordRepRing::Create(flat, CordRepRing::kMaxCapacity), ".*");
#endif
  Unref(flat);
}

TEST_P(CordRingCreateFromTreeTest, CreateFromSubstringOfFlat) {
  absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
  auto* flat = RefIfInputShared(MakeFlat(str1));
  auto* child = RefIfInputSharedIndirect(MakeSubstring(4, 20, flat));
  CordRepRing* result = NeedsUnref(CordRepRing::Create(child));
  ASSERT_THAT(result, IsValidRingBuffer());
  EXPECT_THAT(result->length, Eq(20));
  EXPECT_THAT(ToFlats(result), ElementsAre(str1.substr(4, 20)));
  Unref(result);
  UnrefIfInputShared(flat);
  UnrefIfInputSharedIndirect(child);
}

TEST_P(CordRingCreateTest, CreateFromExternal) {
  absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
  auto* child = RefIfInputShared(MakeExternal(str1));
  CordRepRing* result = NeedsUnref(CordRepRing::Create(child));
  ASSERT_THAT(result, IsValidRingBuffer());
  EXPECT_THAT(result->length, Eq(str1.size()));
  EXPECT_THAT(ToFlats(result), ElementsAre(str1));
  Unref(result);
  UnrefIfInputShared(child);
}

TEST_P(CordRingCreateFromTreeTest, CreateFromSubstringOfExternal) {
  absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
  auto* external = RefIfInputShared(MakeExternal(str1));
  auto* child = RefIfInputSharedIndirect(MakeSubstring(1, 24, external));
  CordRepRing* result = NeedsUnref(CordRepRing::Create(child));
  ASSERT_THAT(result, IsValidRingBuffer());
  EXPECT_THAT(result->length, Eq(24));
  EXPECT_THAT(ToFlats(result), ElementsAre(str1.substr(1, 24)));
  Unref(result);
  UnrefIfInputShared(external);
  UnrefIfInputSharedIndirect(child);
}

TEST_P(CordRingCreateFromTreeTest, CreateFromSubstringOfLargeExternal) {
  auto* external = RefIfInputShared(MakeFakeExternal(1 << 20));
  auto str = not_a_string_view(external->base, 1 << 20)
                 .remove_prefix(1 << 19)
                 .remove_suffix(6);
  auto* child =
      RefIfInputSharedIndirect(MakeSubstring(1 << 19, (1 << 19) - 6, external));
  CordRepRing* result = NeedsUnref(CordRepRing::Create(child));
  ASSERT_THAT(result, IsValidRingBuffer());
  EXPECT_THAT(result->length, Eq(str.size()));
  EXPECT_THAT(ToRawFlats(result), ElementsAre(str));
  Unref(result);
  UnrefIfInputShared(external);
  UnrefIfInputSharedIndirect(child);
}

TEST_P(CordRingBuildInputTest, CreateFromConcat) {
  CordRep* flats[] = {MakeFlat("abcdefgh"), MakeFlat("ijklm"),
                      MakeFlat("nopqrstuv"), MakeFlat("wxyz")};
  auto* left = MakeConcat(RefIfInputSharedIndirect(flats[0]), flats[1]);
  auto* right = MakeConcat(flats[2], RefIfInputSharedIndirect(flats[3]));
  auto* concat = RefIfInputShared(MakeConcat(left, right));
  CordRepRing* result = NeedsUnref(CordRepRing::Create(concat));
  ASSERT_THAT(result, IsValidRingBuffer());
  EXPECT_THAT(result->length, Eq(26));
  EXPECT_THAT(ToString(result), Eq(kAlphabet));
  UnrefIfInputSharedIndirect(flats[0]);
  UnrefIfInputSharedIndirect(flats[3]);
  UnrefIfInputShared(concat);
  Unref(result);
}

TEST_P(CordRingBuildInputTest, CreateFromSubstringConcat) {
  for (size_t off = 0; off < 26; ++off) {
    for (size_t len = 1; len < 26 - off; ++len) {
      CordRep* flats[] = {MakeFlat("abcdefgh"), MakeFlat("ijklm"),
                          MakeFlat("nopqrstuv"), MakeFlat("wxyz")};
      auto* left = MakeConcat(RefIfInputSharedIndirect(flats[0]), flats[1]);
      auto* right = MakeConcat(flats[2], RefIfInputSharedIndirect(flats[3]));
      auto* concat = MakeConcat(left, right);
      auto* child = RefIfInputShared(MakeSubstring(off, len, concat));
      CordRepRing* result = NeedsUnref(CordRepRing::Create(child));
      ASSERT_THAT(result, IsValidRingBuffer());
      ASSERT_THAT(result->length, Eq(len));
      ASSERT_THAT(ToString(result), string_view(kAlphabet).substr(off, len));
      UnrefIfInputSharedIndirect(flats[0]);
      UnrefIfInputSharedIndirect(flats[3]);
      UnrefIfInputShared(child);
      Unref(result);
    }
  }
}

TEST_P(CordRingCreateTest, Properties) {
  absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
  CordRepRing* result = NeedsUnref(CordRepRing::Create(MakeFlat(str1), 120));
  ASSERT_THAT(result, IsValidRingBuffer());
  EXPECT_THAT(result->head(), Eq(0));
  EXPECT_THAT(result->tail(), Eq(1));
  EXPECT_THAT(result->capacity(), Ge(120 + 1));
  EXPECT_THAT(result->capacity(), Le(2 * 120 + 1));
  EXPECT_THAT(result->entries(), Eq(1));
  EXPECT_THAT(result->begin_pos(), Eq(0));
  Unref(result);
}

TEST_P(CordRingCreateTest, EntryForNewFlat) {
  absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
  CordRep* child = MakeFlat(str1);
  CordRepRing* result = NeedsUnref(CordRepRing::Create(child, 120));
  ASSERT_THAT(result, IsValidRingBuffer());
  EXPECT_THAT(result->entry_child(0), Eq(child));
  EXPECT_THAT(result->entry_end_pos(0), Eq(str1.length()));
  EXPECT_THAT(result->entry_data_offset(0), Eq(0));
  Unref(result);
}

TEST_P(CordRingCreateTest, EntryForNewFlatSubstring) {
  absl::string_view str1 = "1234567890abcdefghijklmnopqrstuvwxyz";
  CordRep* child = MakeFlat(str1);
  CordRep* substring = MakeSubstring(10, 26, child);
  CordRepRing* result = NeedsUnref(CordRepRing::Create(substring, 1));
  ASSERT_THAT(result, IsValidRingBuffer());
  EXPECT_THAT(result->entry_child(0), Eq(child));
  EXPECT_THAT(result->entry_end_pos(0), Eq(26));
  EXPECT_THAT(result->entry_data_offset(0), Eq(10));
  Unref(result);
}

TEST_P(CordRingBuildTest, AppendFlat) {
  absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
  absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  CordRepRing* ring = CreateWithCapacity(MakeExternal(str1), 1);
  CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, MakeFlat(str2)));
  ASSERT_THAT(result, IsValidRingBuffer());
  EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
  EXPECT_THAT(result->length, Eq(str1.size() + str2.size()));
  EXPECT_THAT(ToFlats(result), ElementsAre(str1, str2));
  UnrefIfShared(ring);
  Unref(result);
}

TEST_P(CordRingBuildTest, PrependFlat) {
  absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
  absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  CordRepRing* ring = CreateWithCapacity(MakeExternal(str1), 1);
  CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, MakeFlat(str2)));
  ASSERT_THAT(result, IsValidRingBuffer());
  EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
  EXPECT_THAT(result->length, Eq(str1.size() + str2.size()));
  EXPECT_THAT(ToFlats(result), ElementsAre(str2, str1));
  UnrefIfShared(ring);
  Unref(result);
}

TEST_P(CordRingBuildTest, AppendString) {
  absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
  absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  CordRepRing* ring = CreateWithCapacity(MakeExternal(str1), 1);
  CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, str2));
  ASSERT_THAT(result, IsValidRingBuffer());
  EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
  EXPECT_THAT(result->length, Eq(str1.size() + str2.size()));
  EXPECT_THAT(ToFlats(result), ElementsAre(str1, str2));
  UnrefIfShared(ring);
  Unref(result);
}

TEST_P(CordRingBuildTest, AppendStringHavingExtra) {
  absl::string_view str1 = "1234";
  absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  CordRepRing* ring = CreateWithCapacity(MakeFlat(str1, 26), 0);
  CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, str2));
  ASSERT_THAT(result, IsValidRingBuffer());
  EXPECT_THAT(result->length, Eq(str1.size() + str2.size()));
  EXPECT_THAT(result, EqIfPrivate(GetParam(), ring));
  UnrefIfShared(ring);
  Unref(result);
}

TEST_P(CordRingBuildTest, AppendStringHavingPartialExtra) {
  absl::string_view str1 = "1234";
  absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

  // Create flat with at least one extra byte. We don't expect to have sized
  // alloc and capacity rounding to grant us enough to not make it partial.
  auto* flat = MakeFlat(str1, 1);
  size_t avail = flat->flat()->Capacity() - flat->length;
  ASSERT_THAT(avail, Lt(str2.size())) << " adjust test for larger flats!";

  // Construct the flats we do expect using all of `avail`.
  absl::string_view str1a = str2.substr(0, avail);
  absl::string_view str2a = str2.substr(avail);

  CordRepRing* ring = CreateWithCapacity(flat, 1);
  CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, str2));
  ASSERT_THAT(result, IsValidRingBuffer());
  EXPECT_THAT(result->length, Eq(str1.size() + str2.size()));
  EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
  if (GetParam().refcount_is_one) {
    EXPECT_THAT(ToFlats(result), ElementsAre(StrCat(str1, str1a), str2a));
  } else {
    EXPECT_THAT(ToFlats(result), ElementsAre(str1, str2));
  }
  UnrefIfShared(ring);
  Unref(result);
}

TEST_P(CordRingBuildTest, AppendStringHavingExtraInSubstring) {
  absl::string_view str1 = "123456789_1234";
  absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  CordRep* flat = RemovePrefix(10, MakeFlat(str1, 26));
  CordRepRing* ring = CreateWithCapacity(flat, 0);
  CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, str2));
  ASSERT_THAT(result, IsValidRingBuffer());
  EXPECT_THAT(result, EqIfPrivate(GetParam(), ring));
  EXPECT_THAT(result->length, Eq(4 + str2.size()));
  if (GetParam().refcount_is_one) {
    EXPECT_THAT(ToFlats(result), ElementsAre(StrCat("1234", str2)));
  } else {
    EXPECT_THAT(ToFlats(result), ElementsAre("1234", str2));
  }
  UnrefIfShared(ring);
  Unref(result);
}

TEST_P(CordRingBuildTest, AppendStringHavingSharedExtra) {
  absl::string_view str1 = "123456789_1234";
  absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  for (int shared_type = 0; shared_type < 2; ++shared_type) {
    SCOPED_TRACE(absl::StrCat("Shared extra type ", shared_type));

    // Create a flat that is shared in some way.
    CordRep* flat = nullptr;
    CordRep* flat1 = nullptr;
    if (shared_type == 0) {
      // Shared flat
      flat = CordRep::Ref(MakeFlat(str1.substr(10), 100));
    } else if (shared_type == 1) {
      // Shared flat inside private substring
      flat1 = CordRep::Ref(MakeFlat(str1));
      flat = RemovePrefix(10, flat1);
    } else {
      // Private flat inside shared substring
      flat = CordRep::Ref(RemovePrefix(10, MakeFlat(str1, 100)));
    }

    CordRepRing* ring = CreateWithCapacity(flat, 1);
    CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, str2));
    ASSERT_THAT(result, IsValidRingBuffer());
    EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
    EXPECT_THAT(result->length, Eq(4 + str2.size()));
    EXPECT_THAT(ToFlats(result), ElementsAre("1234", str2));
    UnrefIfShared(ring);
    Unref(result);

    CordRep::Unref(shared_type == 1 ? flat1 : flat);
  }
}

TEST_P(CordRingBuildTest, AppendStringWithExtra) {
  absl::string_view str1 = "1234";
  absl::string_view str2 = "1234567890";
  absl::string_view str3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  CordRepRing* ring = CreateWithCapacity(MakeExternal(str1), 1);
  CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, str2, 26));
  result = CordRepRing::Append(result, str3);
  ASSERT_THAT(result, IsValidRingBuffer());
  EXPECT_THAT(result->length, Eq(str1.size() + str2.size() + str3.size()));
  EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
  EXPECT_THAT(ToFlats(result), ElementsAre(str1, StrCat(str2, str3)));
  UnrefIfShared(ring);
  Unref(result);
}

TEST_P(CordRingBuildTest, PrependString) {
  absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
  absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  // Use external rep to avoid appending to first flat
  CordRepRing* ring = CreateWithCapacity(MakeExternal(str1), 1);
  CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, str2));
  ASSERT_THAT(result, IsValidRingBuffer());
  if (GetParam().with_capacity && GetParam().refcount_is_one) {
    EXPECT_THAT(result, Eq(ring));
  } else {
    EXPECT_THAT(result, Ne(ring));
  }
  EXPECT_THAT(result->length, Eq(str1.size() + str2.size()));
  EXPECT_THAT(ToFlats(result), ElementsAre(str2, str1));
  UnrefIfShared(ring);
  Unref(result);
}

TEST_P(CordRingBuildTest, PrependStringHavingExtra) {
  absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz1234";
  absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  CordRep* flat = RemovePrefix(26, MakeFlat(str1));
  CordRepRing* ring = CreateWithCapacity(flat, 0);
  CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, str2));
  ASSERT_THAT(result, IsValidRingBuffer());
  EXPECT_THAT(result, EqIfPrivate(GetParam(), ring));
  EXPECT_THAT(result->length, Eq(4 + str2.size()));
  if (GetParam().refcount_is_one) {
    EXPECT_THAT(ToFlats(result), ElementsAre(StrCat(str2, "1234")));
  } else {
    EXPECT_THAT(ToFlats(result), ElementsAre(str2, "1234"));
  }
  UnrefIfShared(ring);
  Unref(result);
}

TEST_P(CordRingBuildTest, PrependStringHavingSharedExtra) {
  absl::string_view str1 = "123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  absl::string_view str2 = "abcdefghij";
  absl::string_view str1a = str1.substr(10);
  for (int shared_type = 1; shared_type < 2; ++shared_type) {
    SCOPED_TRACE(absl::StrCat("Shared extra type ", shared_type));

    // Create a flat that is shared in some way.
    CordRep* flat = nullptr;
    CordRep* flat1 = nullptr;
    if (shared_type == 1) {
      // Shared flat inside private substring
      flat = RemovePrefix(10, flat1 = CordRep::Ref(MakeFlat(str1)));
    } else {
      // Private flat inside shared substring
      flat = CordRep::Ref(RemovePrefix(10, MakeFlat(str1, 100)));
    }

    CordRepRing* ring = CreateWithCapacity(flat, 1);
    CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, str2));
    ASSERT_THAT(result, IsValidRingBuffer());
    EXPECT_THAT(result->length, Eq(str1a.size() + str2.size()));
    EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
    EXPECT_THAT(ToFlats(result), ElementsAre(str2, str1a));
    UnrefIfShared(ring);
    Unref(result);
    CordRep::Unref(shared_type == 1 ? flat1 : flat);
  }
}

TEST_P(CordRingBuildTest, PrependStringWithExtra) {
  absl::string_view str1 = "1234";
  absl::string_view str2 = "1234567890";
  absl::string_view str3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  CordRepRing* ring = CreateWithCapacity(MakeExternal(str1), 1);
  CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, str2, 26));
  ASSERT_THAT(result, IsValidRingBuffer());
  result = CordRepRing::Prepend(result, str3);
  EXPECT_THAT(result->length, Eq(str1.size() + str2.size() + str3.size()));
  EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
  EXPECT_THAT(ToFlats(result), ElementsAre(StrCat(str3, str2), str1));
  UnrefIfShared(ring);
  Unref(result);
}

TEST_P(CordRingBuildTest, AppendPrependStringMix) {
  const auto& flats = kFoxFlats;
  CordRepRing* ring = CreateWithCapacity(MakeFlat(flats[4]), 8);
  CordRepRing* result = ring;
  for (int i = 1; i <= 4; ++i) {
    result = CordRepRing::Prepend(result, flats[4 - i]);
    result = CordRepRing::Append(result, flats[4 + i]);
  }
  UnrefIfShared(ring);
  NeedsUnref(result);
  ASSERT_THAT(result, IsValidRingBuffer());
  EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
  EXPECT_THAT(ToString(result), kFox);
  Unref(result);
}

TEST_P(CordRingBuildTest, AppendPrependStringMixWithExtra) {
  const auto& flats = kFoxFlats;
  CordRepRing* ring = CreateWithCapacity(MakeFlat(flats[4], 100), 8);
  CordRepRing* result = ring;
  for (int i = 1; i <= 4; ++i) {
    result = CordRepRing::Prepend(result, flats[4 - i], 100);
    result = CordRepRing::Append(result, flats[4 + i], 100);
  }
  NeedsUnref(result);
  ASSERT_THAT(result, IsValidRingBuffer());
  EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
  if (GetParam().refcount_is_one) {
    EXPECT_THAT(ToFlats(result),
                ElementsAre("The quick brown fox ", "jumps over the lazy dog"));
  } else {
    EXPECT_THAT(ToFlats(result), ElementsAre("The quick brown fox ", "jumps ",
                                             "over the lazy dog"));
  }
  UnrefIfShared(ring);
  Unref(result);
}

TEST_P(CordRingBuildTest, AppendPrependStringMixWithPrependedExtra) {
  const auto& flats = kFoxFlats;
  CordRep* flat = MakeFlat(StrCat(std::string(50, '.'), flats[4]), 50);
  CordRepRing* ring = CreateWithCapacity(RemovePrefix(50, flat), 0);
  CordRepRing* result = ring;
  for (int i = 1; i <= 4; ++i) {
    result = CordRepRing::Prepend(result, flats[4 - i], 100);
    result = CordRepRing::Append(result, flats[4 + i], 100);
  }
  result = NeedsUnref(result);
  ASSERT_THAT(result, IsValidRingBuffer());
  EXPECT_THAT(result, EqIfPrivate(GetParam(), ring));
  if (GetParam().refcount_is_one) {
    EXPECT_THAT(ToFlats(result), ElementsAre(kFox));
  } else {
    EXPECT_THAT(ToFlats(result), ElementsAre("The quick brown fox ", "jumps ",
                                             "over the lazy dog"));
  }
  UnrefIfShared(ring);
  Unref(result);
}

TEST_P(CordRingSubTest, SubRing) {
  auto composition = RandomComposition();
  SCOPED_TRACE(ToString(composition));
  auto flats = MakeSpan(kFoxFlats);
  string_view all = kFox;
  for (size_t offset = 0; offset < all.size() - 1; ++offset) {
    CordRepRing* ring = RefIfShared(FromFlats(flats, composition));
    CordRepRing* result = CordRepRing::SubRing(ring, offset, 0);
    EXPECT_THAT(result, nullptr);
    UnrefIfShared(ring);

    for (size_t len = 1; len < all.size() - offset; ++len) {
      ring = RefIfShared(FromFlats(flats, composition));
      result = NeedsUnref(CordRepRing::SubRing(ring, offset, len));
      ASSERT_THAT(result, IsValidRingBuffer());
      ASSERT_THAT(result, EqIfPrivate(GetParam(), ring));
      ASSERT_THAT(ToString(result), Eq(all.substr(offset, len)));
      UnrefIfShared(ring);
      Unref(result);
    }
  }
}

TEST_P(CordRingSubTest, SubRingFromLargeExternal) {
  auto composition = RandomComposition();
  std::string large_string(1 << 20, '.');
  const char* flats[] = {
      "abcdefghijklmnopqrstuvwxyz",
      large_string.c_str(),
      "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
  };
  std::string buffer = absl::StrCat(flats[0], flats[1], flats[2]);
  absl::string_view all = buffer;
  for (size_t offset = 0; offset < 30; ++offset) {
    CordRepRing* ring = RefIfShared(FromFlats(flats, composition));
    CordRepRing* result = CordRepRing::SubRing(ring, offset, 0);
    EXPECT_THAT(result, nullptr);
    UnrefIfShared(ring);

    for (size_t len = all.size() - 30; len < all.size() - offset; ++len) {
      ring = RefIfShared(FromFlats(flats, composition));
      result = NeedsUnref(CordRepRing::SubRing(ring, offset, len));
      ASSERT_THAT(result, IsValidRingBuffer());
      ASSERT_THAT(result, EqIfPrivate(GetParam(), ring));
      auto str = ToString(result);
      ASSERT_THAT(str, SizeIs(len));
      ASSERT_THAT(str, Eq(all.substr(offset, len)));
      UnrefIfShared(ring);
      Unref(result);
    }
  }
}

TEST_P(CordRingSubTest, RemovePrefix) {
  auto composition = RandomComposition();
  SCOPED_TRACE(ToString(composition));
  auto flats = MakeSpan(kFoxFlats);
  string_view all = kFox;
  CordRepRing* ring = RefIfShared(FromFlats(flats, composition));
  CordRepRing* result = CordRepRing::RemovePrefix(ring, all.size());
  EXPECT_THAT(result, nullptr);
  UnrefIfShared(ring);

  for (size_t len = 1; len < all.size(); ++len) {
    ring = RefIfShared(FromFlats(flats, composition));
    result = NeedsUnref(CordRepRing::RemovePrefix(ring, len));
    ASSERT_THAT(result, IsValidRingBuffer());
    EXPECT_THAT(result, EqIfPrivate(GetParam(), ring));
    EXPECT_THAT(ToString(result), Eq(all.substr(len)));
    UnrefIfShared(ring);
    Unref(result);
  }
}

TEST_P(CordRingSubTest, RemovePrefixFromLargeExternal) {
  CordRepExternal* external1 = MakeFakeExternal(1 << 20);
  CordRepExternal* external2 = MakeFakeExternal(1 << 20);
  CordRepRing* ring = CordRepRing::Create(external1, 1);
  ring = CordRepRing::Append(ring, external2);
  CordRepRing* result = NeedsUnref(CordRepRing::RemovePrefix(ring, 1 << 16));
  EXPECT_THAT(
      ToRawFlats(result),
      ElementsAre(
          not_a_string_view(external1->base, 1 << 20).remove_prefix(1 << 16),
          not_a_string_view(external2->base, 1 << 20)));
  Unref(result);
}

TEST_P(CordRingSubTest, RemoveSuffix) {
  auto composition = RandomComposition();
  SCOPED_TRACE(ToString(composition));
  auto flats = MakeSpan(kFoxFlats);
  string_view all = kFox;
  CordRepRing* ring = RefIfShared(FromFlats(flats, composition));
  CordRepRing* result = CordRepRing::RemoveSuffix(ring, all.size());
  EXPECT_THAT(result, nullptr);
  UnrefIfShared(ring);

  for (size_t len = 1; len < all.size(); ++len) {
    ring = RefIfShared(FromFlats(flats, composition));
    result = NeedsUnref(CordRepRing::RemoveSuffix(ring, len));
    ASSERT_THAT(result, IsValidRingBuffer());
    EXPECT_THAT(result, EqIfPrivate(GetParam(), ring));
    EXPECT_THAT(ToString(result), Eq(all.substr(0, all.size() - len)));
    UnrefIfShared(ring);
    Unref(result);
  }
}

TEST_P(CordRingSubTest, AppendRing) {
  auto composition = RandomComposition();
  SCOPED_TRACE(ToString(composition));
  auto flats = MakeSpan(kFoxFlats).subspan(1);
  CordRepRing* ring = CreateWithCapacity(MakeFlat(kFoxFlats[0]), flats.size());
  CordRepRing* child = FromFlats(flats, composition);
  CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, child));
  ASSERT_THAT(result, IsValidRingBuffer());
  EXPECT_THAT(result, EqIfPrivate(GetParam(), ring));
  EXPECT_THAT(ToFlats(result), ElementsAreArray(kFoxFlats));
  UnrefIfShared(ring);
  Unref(result);
}

TEST_P(CordRingBuildInputTest, AppendRingWithFlatOffset) {
  auto composition = RandomComposition();
  SCOPED_TRACE(ToString(composition));
  auto flats = MakeSpan(kFoxFlats);
  CordRepRing* ring = CreateWithCapacity(MakeFlat("Head"), flats.size());
  CordRep* child = RefIfInputSharedIndirect(FromFlats(flats, composition));
  CordRep* stripped = RemovePrefix(10, child);
  CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped));
  ASSERT_THAT(result, IsValidRingBuffer());
  EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
  EXPECT_THAT(ToFlats(result), ElementsAre("Head", "brown ", "fox ", "jumps ",
                                           "over ", "the ", "lazy ", "dog"));
  UnrefIfInputSharedIndirect(child);
  UnrefIfShared(ring);
  Unref(result);
}

TEST_P(CordRingBuildInputTest, AppendRingWithBrokenOffset) {
  auto composition = RandomComposition();
  SCOPED_TRACE(ToString(composition));
  auto flats = MakeSpan(kFoxFlats);
  CordRepRing* ring = CreateWithCapacity(MakeFlat("Head"), flats.size());
  CordRep* child = RefIfInputSharedIndirect(FromFlats(flats, composition));
  CordRep* stripped = RemovePrefix(21, child);
  CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped));
  ASSERT_THAT(result, IsValidRingBuffer());
  EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
  EXPECT_THAT(ToFlats(result),
              ElementsAre("Head", "umps ", "over ", "the ", "lazy ", "dog"));
  UnrefIfInputSharedIndirect(child);
  UnrefIfShared(ring);
  Unref(result);
}

TEST_P(CordRingBuildInputTest, AppendRingWithFlatLength) {
  auto composition = RandomComposition();
  SCOPED_TRACE(ToString(composition));
  auto flats = MakeSpan(kFoxFlats);
  CordRepRing* ring = CreateWithCapacity(MakeFlat("Head"), flats.size());
  CordRep* child = RefIfInputSharedIndirect(FromFlats(flats, composition));
  CordRep* stripped = RemoveSuffix(8, child);
  CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped));
  ASSERT_THAT(result, IsValidRingBuffer());
  EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
  EXPECT_THAT(ToFlats(result), ElementsAre("Head", "The ", "quick ", "brown ",
                                           "fox ", "jumps ", "over ", "the "));
  UnrefIfInputSharedIndirect(child);
  UnrefIfShared(ring);
  Unref(result);
}

TEST_P(CordRingBuildTest, AppendRingWithBrokenFlatLength) {
  auto composition = RandomComposition();
  SCOPED_TRACE(ToString(composition));
  auto flats = MakeSpan(kFoxFlats);
  CordRepRing* ring = CreateWithCapacity(MakeFlat("Head"), flats.size());
  CordRep* child = RefIfInputSharedIndirect(FromFlats(flats, composition));
  CordRep* stripped = RemoveSuffix(15, child);
  CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped));
  ASSERT_THAT(result, IsValidRingBuffer());
  EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
  EXPECT_THAT(ToFlats(result), ElementsAre("Head", "The ", "quick ", "brown ",
                                           "fox ", "jumps ", "ov"));
  UnrefIfInputSharedIndirect(child);
  UnrefIfShared(ring);
  Unref(result);
}

TEST_P(CordRingBuildTest, AppendRingMiddlePiece) {
  auto composition = RandomComposition();
  SCOPED_TRACE(ToString(composition));
  auto flats = MakeSpan(kFoxFlats);
  CordRepRing* ring = CreateWithCapacity(MakeFlat("Head"), flats.size());
  CordRep* child = RefIfInputSharedIndirect(FromFlats(flats, composition));
  CordRep* stripped = MakeSubstring(7, child->length - 27, child);
  CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped));
  ASSERT_THAT(result, IsValidRingBuffer());
  EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
  EXPECT_THAT(ToFlats(result),
              ElementsAre("Head", "ck ", "brown ", "fox ", "jum"));
  UnrefIfInputSharedIndirect(child);
  UnrefIfShared(ring);
  Unref(result);
}

TEST_P(CordRingBuildTest, AppendRingSinglePiece) {
  auto composition = RandomComposition();
  SCOPED_TRACE(ToString(composition));
  auto flats = MakeSpan(kFoxFlats);
  CordRepRing* ring = CreateWithCapacity(MakeFlat("Head"), flats.size());
  CordRep* child = RefIfInputSharedIndirect(FromFlats(flats, composition));
  CordRep* stripped = RefIfInputShared(MakeSubstring(11, 3, child));
  CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped));
  ASSERT_THAT(result, IsValidRingBuffer());
  EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
  EXPECT_THAT(ToFlats(result), ElementsAre("Head", "row"));
  UnrefIfInputSharedIndirect(child);
  UnrefIfInputShared(stripped);
  UnrefIfShared(ring);
  Unref(result);
}

TEST_P(CordRingBuildInputTest, AppendRingSinglePieceWithPrefix) {
  auto composition = RandomComposition();
  SCOPED_TRACE(ToString(composition));
  auto flats = MakeSpan(kFoxFlats);
  size_t extra_capacity = 1 + (GetParam().with_capacity ? flats.size() : 0);
  CordRepRing* ring = CordRepRing::Create(MakeFlat("Head"), extra_capacity);
  ring->SetCapacityForTesting(1 + extra_capacity);
  ring = RefIfShared(CordRepRing::Prepend(ring, MakeFlat("Prepend")));
  assert(ring->IsValid(std::cout));
  CordRepRing* child = RefIfInputSharedIndirect(FromFlats(flats, composition));
  CordRep* stripped = RefIfInputShared(MakeSubstring(11, 3, child));
  CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped));
  ASSERT_THAT(result, IsValidRingBuffer());
  EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
  EXPECT_THAT(ToFlats(result), ElementsAre("Prepend", "Head", "row"));
  UnrefIfInputSharedIndirect(child);
  UnrefIfInputShared(stripped);
  UnrefIfShared(ring);
  Unref(result);
}

TEST_P(CordRingBuildInputTest, PrependRing) {
  auto composition = RandomComposition();
  SCOPED_TRACE(ToString(composition));
  auto fox = MakeSpan(kFoxFlats);
  auto flats = MakeSpan(fox).subspan(0, fox.size() - 1);
  CordRepRing* ring = CreateWithCapacity(MakeFlat(fox.back()), flats.size());
  CordRepRing* child = RefIfInputShared(FromFlats(flats, composition));
  CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, child));
  ASSERT_THAT(result, IsValidRingBuffer());
  EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
  EXPECT_THAT(ToFlats(result), ElementsAreArray(kFoxFlats));
  UnrefIfInputShared(child);
  UnrefIfShared(ring);
  Unref(result);
}

TEST_P(CordRingBuildInputTest, PrependRingWithFlatOffset) {
  auto composition = RandomComposition();
  SCOPED_TRACE(ToString(composition));
  auto flats = MakeSpan(kFoxFlats);
  CordRepRing* ring = CreateWithCapacity(MakeFlat("Tail"), flats.size());
  CordRep* child = RefIfInputShared(FromFlats(flats, composition));
  CordRep* stripped = RefIfInputSharedIndirect(RemovePrefix(10, child));
  CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped));
  ASSERT_THAT(result, IsValidRingBuffer());
  EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
  EXPECT_THAT(ToFlats(result), ElementsAre("brown ", "fox ", "jumps ", "over ",
                                           "the ", "lazy ", "dog", "Tail"));
  UnrefIfInputShared(child);
  UnrefIfInputSharedIndirect(stripped);
  UnrefIfShared(ring);
  Unref(result);
}

TEST_P(CordRingBuildInputTest, PrependRingWithBrokenOffset) {
  auto composition = RandomComposition();
  SCOPED_TRACE(ToString(composition));
  auto flats = MakeSpan(kFoxFlats);
  CordRepRing* ring = CreateWithCapacity(MakeFlat("Tail"), flats.size());
  CordRep* child = RefIfInputShared(FromFlats(flats, composition));
  CordRep* stripped = RefIfInputSharedIndirect(RemovePrefix(21, child));
  CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped));
  EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
  EXPECT_THAT(ToFlats(result),
              ElementsAre("umps ", "over ", "the ", "lazy ", "dog", "Tail"));
  UnrefIfInputShared(child);
  UnrefIfInputSharedIndirect(stripped);
  UnrefIfShared(ring);
  Unref(result);
}

TEST_P(CordRingBuildInputTest, PrependRingWithFlatLength) {
  auto composition = RandomComposition();
  SCOPED_TRACE(ToString(composition));
  auto flats = MakeSpan(kFoxFlats);
  CordRepRing* ring = CreateWithCapacity(MakeFlat("Tail"), flats.size());
  CordRep* child = RefIfInputShared(FromFlats(flats, composition));
  CordRep* stripped = RefIfInputSharedIndirect(RemoveSuffix(8, child));
  CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped));
  ASSERT_THAT(result, IsValidRingBuffer());
  EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
  EXPECT_THAT(ToFlats(result), ElementsAre("The ", "quick ", "brown ", "fox ",
                                           "jumps ", "over ", "the ", "Tail"));
  UnrefIfShared(ring);
  UnrefIfInputShared(child);
  UnrefIfInputSharedIndirect(stripped);
  Unref(result);
}

TEST_P(CordRingBuildInputTest, PrependRingWithBrokenFlatLength) {
  auto composition = RandomComposition();
  SCOPED_TRACE(ToString(composition));
  auto flats = MakeSpan(kFoxFlats);
  CordRepRing* ring = CreateWithCapacity(MakeFlat("Tail"), flats.size());
  CordRep* child = RefIfInputShared(FromFlats(flats, composition));
  CordRep* stripped = RefIfInputSharedIndirect(RemoveSuffix(15, child));
  CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped));
  ASSERT_THAT(result, IsValidRingBuffer());
  EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
  EXPECT_THAT(ToFlats(result), ElementsAre("The ", "quick ", "brown ", "fox ",
                                           "jumps ", "ov", "Tail"));
  UnrefIfInputShared(child);
  UnrefIfInputSharedIndirect(stripped);
  UnrefIfShared(ring);
  Unref(result);
}

TEST_P(CordRingBuildInputTest, PrependRingMiddlePiece) {
  auto composition = RandomComposition();
  SCOPED_TRACE(ToString(composition));
  auto flats = MakeSpan(kFoxFlats);
  CordRepRing* ring = CreateWithCapacity(MakeFlat("Tail"), flats.size());
  CordRep* child = RefIfInputShared(FromFlats(flats, composition));
  CordRep* stripped =
      RefIfInputSharedIndirect(MakeSubstring(7, child->length - 27, child));
  CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped));
  ASSERT_THAT(result, IsValidRingBuffer());
  EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
  EXPECT_THAT(ToFlats(result),
              ElementsAre("ck ", "brown ", "fox ", "jum", "Tail"));
  UnrefIfInputShared(child);
  UnrefIfInputSharedIndirect(stripped);
  UnrefIfShared(ring);
  Unref(result);
}

TEST_P(CordRingBuildInputTest, PrependRingSinglePiece) {
  auto composition = RandomComposition();
  SCOPED_TRACE(ToString(composition));
  auto flats = MakeSpan(kFoxFlats);
  CordRepRing* ring = CreateWithCapacity(MakeFlat("Tail"), flats.size());
  CordRep* child = RefIfInputShared(FromFlats(flats, composition));
  CordRep* stripped = RefIfInputSharedIndirect(MakeSubstring(11, 3, child));
  CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped));
  ASSERT_THAT(result, IsValidRingBuffer());
  EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
  EXPECT_THAT(ToFlats(result), ElementsAre("row", "Tail"));
  UnrefIfInputShared(child);
  UnrefIfInputSharedIndirect(stripped);
  UnrefIfShared(ring);
  Unref(result);
}

TEST_P(CordRingBuildInputTest, PrependRingSinglePieceWithPrefix) {
  auto composition = RandomComposition();
  SCOPED_TRACE(ToString(composition));
  auto flats = MakeSpan(kFoxFlats);
  size_t extra_capacity = 1 + (GetParam().with_capacity ? flats.size() : 0);
  CordRepRing* ring = CordRepRing::Create(MakeFlat("Tail"), extra_capacity);
  ring->SetCapacityForTesting(1 + extra_capacity);
  ring = RefIfShared(CordRepRing::Prepend(ring, MakeFlat("Prepend")));
  CordRep* child = RefIfInputShared(FromFlats(flats, composition));
  CordRep* stripped = RefIfInputSharedIndirect(MakeSubstring(11, 3, child));
  CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped));
  ASSERT_THAT(result, IsValidRingBuffer());
  EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
  EXPECT_THAT(ToFlats(result), ElementsAre("row", "Prepend", "Tail"));
  UnrefIfInputShared(child);
  UnrefIfInputSharedIndirect(stripped);
  UnrefIfShared(ring);
  Unref(result);
}

TEST_F(CordRingTest, Find) {
  constexpr const char* flats[] = {
      "abcdefghij", "klmnopqrst", "uvwxyz",     "ABCDEFGHIJ",
      "KLMNOPQRST", "UVWXYZ",     "1234567890", "~!@#$%^&*()_",
      "+-=",        "[]\\{}|;':", ",/<>?",      "."};
  auto composition = RandomComposition();
  SCOPED_TRACE(ToString(composition));
  CordRepRing* ring = NeedsUnref(FromFlats(flats, composition));
  std::string value = ToString(ring);
  for (int i = 0; i < value.length(); ++i) {
    CordRepRing::Position found = ring->Find(i);
    auto data = ring->entry_data(found.index);
    ASSERT_THAT(found.offset, Lt(data.length()));
    ASSERT_THAT(data[found.offset], Eq(value[i]));
  }
  Unref(ring);
}

TEST_F(CordRingTest, FindWithHint) {
  constexpr const char* flats[] = {
      "abcdefghij", "klmnopqrst", "uvwxyz",     "ABCDEFGHIJ",
      "KLMNOPQRST", "UVWXYZ",     "1234567890", "~!@#$%^&*()_",
      "+-=",        "[]\\{}|;':", ",/<>?",      "."};
  auto composition = RandomComposition();
  SCOPED_TRACE(ToString(composition));
  CordRepRing* ring = NeedsUnref(FromFlats(flats, composition));
  std::string value = ToString(ring);

#if defined(GTEST_HAS_DEATH_TEST)
  // Test hint beyond valid position
  index_type head = ring->head();
  EXPECT_DEBUG_DEATH(ring->Find(ring->advance(head), 0), ".*");
  EXPECT_DEBUG_DEATH(ring->Find(ring->advance(head), 9), ".*");
  EXPECT_DEBUG_DEATH(ring->Find(ring->advance(head, 3), 24), ".*");
#endif

  int flat_pos = 0;
  size_t flat_offset = 0;
  for (auto sflat : flats) {
    string_view flat(sflat);
    for (int offset = 0; offset < flat.length(); ++offset) {
      for (int start = 0; start <= flat_pos; ++start) {
        index_type hint = ring->advance(ring->head(), start);
        CordRepRing::Position found = ring->Find(hint, flat_offset + offset);
        ASSERT_THAT(found.index, Eq(ring->advance(ring->head(), flat_pos)));
        ASSERT_THAT(found.offset, Eq(offset));
      }
    }
    ++flat_pos;
    flat_offset += flat.length();
  }
  Unref(ring);
}

TEST_F(CordRingTest, FindInLargeRing) {
  constexpr const char* flats[] = {
      "abcdefghij", "klmnopqrst", "uvwxyz",     "ABCDEFGHIJ",
      "KLMNOPQRST", "UVWXYZ",     "1234567890", "~!@#$%^&*()_",
      "+-=",        "[]\\{}|;':", ",/<>?",      "."};
  auto composition = RandomComposition();
  SCOPED_TRACE(ToString(composition));
  CordRepRing* ring = FromFlats(flats, composition);
  for (int i = 0; i < 13; ++i) {
    ring = CordRepRing::Append(ring, FromFlats(flats, composition));
  }
  NeedsUnref(ring);
  std::string value = ToString(ring);
  for (int i = 0; i < value.length(); ++i) {
    CordRepRing::Position pos = ring->Find(i);
    auto data = ring->entry_data(pos.index);
    ASSERT_THAT(pos.offset, Lt(data.length()));
    ASSERT_THAT(data[pos.offset], Eq(value[i]));
  }
  Unref(ring);
}

TEST_F(CordRingTest, FindTail) {
  constexpr const char* flats[] = {
      "abcdefghij", "klmnopqrst", "uvwxyz",     "ABCDEFGHIJ",
      "KLMNOPQRST", "UVWXYZ",     "1234567890", "~!@#$%^&*()_",
      "+-=",        "[]\\{}|;':", ",/<>?",      "."};
  auto composition = RandomComposition();
  SCOPED_TRACE(ToString(composition));
  CordRepRing* ring = NeedsUnref(FromFlats(flats, composition));
  std::string value = ToString(ring);

  for (int i = 0; i < value.length(); ++i) {
    CordRepRing::Position pos = ring->FindTail(i + 1);
    auto data = ring->entry_data(ring->retreat(pos.index));
    ASSERT_THAT(pos.offset, Lt(data.length()));
    ASSERT_THAT(data[data.length() - pos.offset - 1], Eq(value[i]));
  }
  Unref(ring);
}

TEST_F(CordRingTest, FindTailWithHint) {
  constexpr const char* flats[] = {
      "abcdefghij", "klmnopqrst", "uvwxyz",     "ABCDEFGHIJ",
      "KLMNOPQRST", "UVWXYZ",     "1234567890", "~!@#$%^&*()_",
      "+-=",        "[]\\{}|;':", ",/<>?",      "."};
  auto composition = RandomComposition();
  SCOPED_TRACE(ToString(composition));
  CordRepRing* ring = NeedsUnref(FromFlats(flats, composition));
  std::string value = ToString(ring);

  // Test hint beyond valid position
#if defined(GTEST_HAS_DEATH_TEST)
  index_type head = ring->head();
  EXPECT_DEBUG_DEATH(ring->FindTail(ring->advance(head), 1), ".*");
  EXPECT_DEBUG_DEATH(ring->FindTail(ring->advance(head), 10), ".*");
  EXPECT_DEBUG_DEATH(ring->FindTail(ring->advance(head, 3), 26), ".*");
#endif

  for (int i = 0; i < value.length(); ++i) {
    CordRepRing::Position pos = ring->FindTail(i + 1);
    auto data = ring->entry_data(ring->retreat(pos.index));
    ASSERT_THAT(pos.offset, Lt(data.length()));
    ASSERT_THAT(data[data.length() - pos.offset - 1], Eq(value[i]));
  }
  Unref(ring);
}

TEST_F(CordRingTest, FindTailInLargeRing) {
  constexpr const char* flats[] = {
      "abcdefghij", "klmnopqrst", "uvwxyz",     "ABCDEFGHIJ",
      "KLMNOPQRST", "UVWXYZ",     "1234567890", "~!@#$%^&*()_",
      "+-=",        "[]\\{}|;':", ",/<>?",      "."};
  auto composition = RandomComposition();
  SCOPED_TRACE(ToString(composition));
  CordRepRing* ring = FromFlats(flats, composition);
  for (int i = 0; i < 13; ++i) {
    ring = CordRepRing::Append(ring, FromFlats(flats, composition));
  }
  NeedsUnref(ring);
  std::string value = ToString(ring);
  for (int i = 0; i < value.length(); ++i) {
    CordRepRing::Position pos = ring->FindTail(i + 1);
    auto data = ring->entry_data(ring->retreat(pos.index));
    ASSERT_THAT(pos.offset, Lt(data.length()));
    ASSERT_THAT(data[data.length() - pos.offset - 1], Eq(value[i]));
  }
  Unref(ring);
}

TEST_F(CordRingTest, GetCharacter) {
  auto flats = MakeSpan(kFoxFlats);
  CordRepRing* ring = CordRepRing::Create(MakeFlat("Tail"), flats.size());
  CordRep* child = FromFlats(flats, kAppend);
  CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, child));
  std::string value = ToString(result);
  for (int i = 0; i < value.length(); ++i) {
    ASSERT_THAT(result->GetCharacter(i), Eq(value[i]));
  }
  Unref(result);
}

TEST_F(CordRingTest, GetCharacterWithSubstring) {
  absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
  auto* child = MakeSubstring(4, 20, MakeFlat(str1));
  CordRepRing* result = NeedsUnref(CordRepRing::Create(child));
  ASSERT_THAT(result, IsValidRingBuffer());
  std::string value = ToString(result);
  for (int i = 0; i < value.length(); ++i) {
    ASSERT_THAT(result->GetCharacter(i), Eq(value[i]));
  }
  Unref(result);
}

TEST_F(CordRingTest, IsFlatSingleFlat) {
  for (bool external : {false, true}) {
    SCOPED_TRACE(external ? "With External" : "With Flat");
    absl::string_view str = "Hello world";
    CordRep* rep = external ? MakeExternal(str) : MakeFlat(str);
    CordRepRing* ring = NeedsUnref(CordRepRing::Create(rep));

    // The ring is a single non-fragmented flat:
    absl::string_view fragment;
    EXPECT_TRUE(ring->IsFlat(nullptr));
    EXPECT_TRUE(ring->IsFlat(&fragment));
    EXPECT_THAT(fragment, Eq("Hello world"));
    fragment = "";
    EXPECT_TRUE(ring->IsFlat(0, 11, nullptr));
    EXPECT_TRUE(ring->IsFlat(0, 11, &fragment));
    EXPECT_THAT(fragment, Eq("Hello world"));

    // Arbitrary ranges must check true as well.
    EXPECT_TRUE(ring->IsFlat(1, 4, &fragment));
    EXPECT_THAT(fragment, Eq("ello"));
    EXPECT_TRUE(ring->IsFlat(6, 5, &fragment));
    EXPECT_THAT(fragment, Eq("world"));

    Unref(ring);
  }
}

TEST_F(CordRingTest, IsFlatMultiFlat) {
  for (bool external : {false, true}) {
    SCOPED_TRACE(external ? "With External" : "With Flat");
    absl::string_view str1 = "Hello world";
    absl::string_view str2 = "Halt and catch fire";
    CordRep* rep1 = external ? MakeExternal(str1) : MakeFlat(str1);
    CordRep* rep2 = external ? MakeExternal(str2) : MakeFlat(str2);
    CordRepRing* ring = CordRepRing::Append(CordRepRing::Create(rep1), rep2);
    NeedsUnref(ring);

    // The ring is fragmented, IsFlat() on the entire cord must be false.
    EXPECT_FALSE(ring->IsFlat(nullptr));
    absl::string_view fragment = "Don't touch this";
    EXPECT_FALSE(ring->IsFlat(&fragment));
    EXPECT_THAT(fragment, Eq("Don't touch this"));

    // Check for ranges exactly within both flats.
    EXPECT_TRUE(ring->IsFlat(0, 11, &fragment));
    EXPECT_THAT(fragment, Eq("Hello world"));
    EXPECT_TRUE(ring->IsFlat(11, 19, &fragment));
    EXPECT_THAT(fragment, Eq("Halt and catch fire"));

    // Check for arbitrary partial range inside each flat.
    EXPECT_TRUE(ring->IsFlat(1, 4, &fragment));
    EXPECT_THAT(fragment, "ello");
    EXPECT_TRUE(ring->IsFlat(26, 4, &fragment));
    EXPECT_THAT(fragment, "fire");

    // Check ranges spanning across both flats
    fragment = "Don't touch this";
    EXPECT_FALSE(ring->IsFlat(1, 18, &fragment));
    EXPECT_FALSE(ring->IsFlat(10, 2, &fragment));
    EXPECT_THAT(fragment, Eq("Don't touch this"));

    Unref(ring);
  }
}

TEST_F(CordRingTest, Dump) {
  std::stringstream ss;
  auto flats = MakeSpan(kFoxFlats);
  CordRepRing* ring = NeedsUnref(FromFlats(flats, kPrepend));
  ss << *ring;
  Unref(ring);
}

}  // namespace
ABSL_NAMESPACE_END
}  // namespace absl
