// Copyright 2021 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 "absl/strings/internal/cord_rep_btree_navigator.h"

#include <string>
#include <vector>

#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/base/config.h"
#include "absl/base/internal/raw_logging.h"
#include "absl/strings/internal/cord_internal.h"
#include "absl/strings/internal/cord_rep_btree.h"
#include "absl/strings/internal/cord_rep_test_util.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"

namespace absl {
ABSL_NAMESPACE_BEGIN
namespace cord_internal {
namespace {

using ::testing::Eq;
using ::testing::Ne;

using ::absl::cordrep_testing::CordRepBtreeFromFlats;
using ::absl::cordrep_testing::CordToString;
using ::absl::cordrep_testing::CreateFlatsFromString;
using ::absl::cordrep_testing::CreateRandomString;
using ::absl::cordrep_testing::MakeFlat;
using ::absl::cordrep_testing::MakeSubstring;

using ReadResult = CordRepBtreeNavigator::ReadResult;
using Position = CordRepBtreeNavigator::Position;

// CordRepBtreeNavigatorTest is a test fixture which automatically creates a
// tree to test navigation logic on. The parameter `count' defines the number of
// data edges in the test tree.
class CordRepBtreeNavigatorTest : public testing::TestWithParam<int> {
 public:
  using Flats = std::vector<CordRep*>;
  static constexpr size_t kCharsPerFlat = 3;

  CordRepBtreeNavigatorTest() {
    data_ = CreateRandomString(count() * kCharsPerFlat);
    flats_ = CreateFlatsFromString(data_, kCharsPerFlat);

    // Turn flat 0 or 1 into a substring to cover partial reads on substrings.
    if (count() > 1) {
      CordRep::Unref(flats_[1]);
      flats_[1] = MakeSubstring(kCharsPerFlat, kCharsPerFlat, MakeFlat(data_));
    } else {
      CordRep::Unref(flats_[0]);
      flats_[0] = MakeSubstring(0, kCharsPerFlat, MakeFlat(data_));
    }

    tree_ = CordRepBtreeFromFlats(flats_);
  }

  ~CordRepBtreeNavigatorTest() override { CordRep::Unref(tree_); }

  int count() const { return GetParam(); }
  CordRepBtree* tree() { return tree_; }
  const std::string& data() const { return data_; }
  const std::vector<CordRep*>& flats() const { return flats_; }

  static std::string ToString(testing::TestParamInfo<int> param) {
    return absl::StrCat(param.param, "_Flats");
  }

 private:
  std::string data_;
  Flats flats_;
  CordRepBtree* tree_;
};

INSTANTIATE_TEST_SUITE_P(
    WithParam, CordRepBtreeNavigatorTest,
    testing::Values(1, CordRepBtree::kMaxCapacity - 1,
                    CordRepBtree::kMaxCapacity,
                    CordRepBtree::kMaxCapacity* CordRepBtree::kMaxCapacity - 1,
                    CordRepBtree::kMaxCapacity* CordRepBtree::kMaxCapacity,
                    CordRepBtree::kMaxCapacity* CordRepBtree::kMaxCapacity + 1,
                    CordRepBtree::kMaxCapacity* CordRepBtree::kMaxCapacity * 2 +
                        17),
    CordRepBtreeNavigatorTest::ToString);

TEST(CordRepBtreeNavigatorTest, Uninitialized) {
  CordRepBtreeNavigator nav;
  EXPECT_FALSE(nav);
  EXPECT_THAT(nav.btree(), Eq(nullptr));
#if defined(GTEST_HAS_DEATH_TEST) && !defined(NDEBUG)
  EXPECT_DEATH(nav.Current(), ".*");
#endif
}

TEST_P(CordRepBtreeNavigatorTest, InitFirst) {
  CordRepBtreeNavigator nav;
  CordRep* edge = nav.InitFirst(tree());
  EXPECT_TRUE(nav);
  EXPECT_THAT(nav.btree(), Eq(tree()));
  EXPECT_THAT(nav.Current(), Eq(flats().front()));
  EXPECT_THAT(edge, Eq(flats().front()));
}

TEST_P(CordRepBtreeNavigatorTest, InitLast) {
  CordRepBtreeNavigator nav;
  CordRep* edge = nav.InitLast(tree());
  EXPECT_TRUE(nav);
  EXPECT_THAT(nav.btree(), Eq(tree()));
  EXPECT_THAT(nav.Current(), Eq(flats().back()));
  EXPECT_THAT(edge, Eq(flats().back()));
}

TEST_P(CordRepBtreeNavigatorTest, NextPrev) {
  CordRepBtreeNavigator nav;
  nav.InitFirst(tree());
  const Flats& flats = this->flats();

  EXPECT_THAT(nav.Previous(), Eq(nullptr));
  EXPECT_THAT(nav.Current(), Eq(flats.front()));
  for (int i = 1; i < flats.size(); ++i) {
    ASSERT_THAT(nav.Next(), Eq(flats[i]));
    EXPECT_THAT(nav.Current(), Eq(flats[i]));
  }
  EXPECT_THAT(nav.Next(), Eq(nullptr));
  EXPECT_THAT(nav.Current(), Eq(flats.back()));
  for (int i = static_cast<int>(flats.size()) - 2; i >= 0; --i) {
    ASSERT_THAT(nav.Previous(), Eq(flats[i]));
    EXPECT_THAT(nav.Current(), Eq(flats[i]));
  }
  EXPECT_THAT(nav.Previous(), Eq(nullptr));
  EXPECT_THAT(nav.Current(), Eq(flats.front()));
}

TEST_P(CordRepBtreeNavigatorTest, PrevNext) {
  CordRepBtreeNavigator nav;
  nav.InitLast(tree());
  const Flats& flats = this->flats();

  EXPECT_THAT(nav.Next(), Eq(nullptr));
  EXPECT_THAT(nav.Current(), Eq(flats.back()));
  for (int i = static_cast<int>(flats.size()) - 2; i >= 0; --i) {
    ASSERT_THAT(nav.Previous(), Eq(flats[i]));
    EXPECT_THAT(nav.Current(), Eq(flats[i]));
  }
  EXPECT_THAT(nav.Previous(), Eq(nullptr));
  EXPECT_THAT(nav.Current(), Eq(flats.front()));
  for (int i = 1; i < flats.size(); ++i) {
    ASSERT_THAT(nav.Next(), Eq(flats[i]));
    EXPECT_THAT(nav.Current(), Eq(flats[i]));
  }
  EXPECT_THAT(nav.Next(), Eq(nullptr));
  EXPECT_THAT(nav.Current(), Eq(flats.back()));
}

TEST(CordRepBtreeNavigatorTest, Reset) {
  CordRepBtree* tree = CordRepBtree::Create(MakeFlat("abc"));
  CordRepBtreeNavigator nav;
  nav.InitFirst(tree);
  nav.Reset();
  EXPECT_FALSE(nav);
  EXPECT_THAT(nav.btree(), Eq(nullptr));
#if defined(GTEST_HAS_DEATH_TEST) && !defined(NDEBUG)
  EXPECT_DEATH(nav.Current(), ".*");
#endif
  CordRep::Unref(tree);
}

TEST_P(CordRepBtreeNavigatorTest, Skip) {
  int count = this->count();
  const Flats& flats = this->flats();
  CordRepBtreeNavigator nav;
  nav.InitFirst(tree());

  for (int char_offset = 0; char_offset < kCharsPerFlat; ++char_offset) {
    Position pos = nav.Skip(char_offset);
    EXPECT_THAT(pos.edge, Eq(nav.Current()));
    EXPECT_THAT(pos.edge, Eq(flats[0]));
    EXPECT_THAT(pos.offset, Eq(char_offset));
  }

  for (int index1 = 0; index1 < count; ++index1) {
    for (int index2 = index1; index2 < count; ++index2) {
      for (int char_offset = 0; char_offset < kCharsPerFlat; ++char_offset) {
        CordRepBtreeNavigator nav;
        nav.InitFirst(tree());

        size_t length1 = index1 * kCharsPerFlat;
        Position pos1 = nav.Skip(length1 + char_offset);
        ASSERT_THAT(pos1.edge, Eq(flats[index1]));
        ASSERT_THAT(pos1.edge, Eq(nav.Current()));
        ASSERT_THAT(pos1.offset, Eq(char_offset));

        size_t length2 = index2 * kCharsPerFlat;
        Position pos2 = nav.Skip(length2 - length1 + char_offset);
        ASSERT_THAT(pos2.edge, Eq(flats[index2]));
        ASSERT_THAT(pos2.edge, Eq(nav.Current()));
        ASSERT_THAT(pos2.offset, Eq(char_offset));
      }
    }
  }
}

TEST_P(CordRepBtreeNavigatorTest, Seek) {
  int count = this->count();
  const Flats& flats = this->flats();
  CordRepBtreeNavigator nav;
  nav.InitFirst(tree());

  for (int char_offset = 0; char_offset < kCharsPerFlat; ++char_offset) {
    Position pos = nav.Seek(char_offset);
    EXPECT_THAT(pos.edge, Eq(nav.Current()));
    EXPECT_THAT(pos.edge, Eq(flats[0]));
    EXPECT_THAT(pos.offset, Eq(char_offset));
  }

  for (int index = 0; index < count; ++index) {
    for (int char_offset = 0; char_offset < kCharsPerFlat; ++char_offset) {
      size_t offset = index * kCharsPerFlat + char_offset;
      Position pos1 = nav.Seek(offset);
      ASSERT_THAT(pos1.edge, Eq(flats[index]));
      ASSERT_THAT(pos1.edge, Eq(nav.Current()));
      ASSERT_THAT(pos1.offset, Eq(char_offset));
    }
  }
}

TEST(CordRepBtreeNavigatorTest, InitOffset) {
  // Whitebox: InitOffset() is implemented in terms of Seek() which is
  // exhaustively tested. Only test it initializes / forwards properly..
  CordRepBtree* tree = CordRepBtree::Create(MakeFlat("abc"));
  tree = CordRepBtree::Append(tree, MakeFlat("def"));
  CordRepBtreeNavigator nav;
  Position pos = nav.InitOffset(tree, 5);
  EXPECT_TRUE(nav);
  EXPECT_THAT(nav.btree(), Eq(tree));
  EXPECT_THAT(pos.edge, Eq(tree->Edges()[1]));
  EXPECT_THAT(pos.edge, Eq(nav.Current()));
  EXPECT_THAT(pos.offset, Eq(2));
  CordRep::Unref(tree);
}

TEST(CordRepBtreeNavigatorTest, InitOffsetAndSeekBeyondLength) {
  CordRepBtree* tree1 = CordRepBtree::Create(MakeFlat("abc"));
  CordRepBtree* tree2 = CordRepBtree::Create(MakeFlat("def"));

  CordRepBtreeNavigator nav;
  nav.InitFirst(tree1);
  EXPECT_THAT(nav.Seek(3).edge, Eq(nullptr));
  EXPECT_THAT(nav.Seek(100).edge, Eq(nullptr));
  EXPECT_THAT(nav.btree(), Eq(tree1));
  EXPECT_THAT(nav.Current(), Eq(tree1->Edges().front()));

  EXPECT_THAT(nav.InitOffset(tree2, 3).edge, Eq(nullptr));
  EXPECT_THAT(nav.InitOffset(tree2, 100).edge, Eq(nullptr));
  EXPECT_THAT(nav.btree(), Eq(tree1));
  EXPECT_THAT(nav.Current(), Eq(tree1->Edges().front()));

  CordRep::Unref(tree1);
  CordRep::Unref(tree2);
}

TEST_P(CordRepBtreeNavigatorTest, Read) {
  const Flats& flats = this->flats();
  const std::string& data = this->data();

  for (size_t offset = 0; offset < data.size(); ++offset) {
    for (size_t length = 1; length <= data.size() - offset; ++length) {
      CordRepBtreeNavigator nav;
      nav.InitFirst(tree());

      // Skip towards edge holding offset
      size_t edge_offset = nav.Skip(offset).offset;

      // Read node
      ReadResult result = nav.Read(edge_offset, length);
      ASSERT_THAT(result.tree, Ne(nullptr));
      EXPECT_THAT(result.tree->length, Eq(length));
      if (result.tree->tag == BTREE) {
        ASSERT_TRUE(CordRepBtree::IsValid(result.tree->btree()));
      }

      // Verify contents
      std::string value = CordToString(result.tree);
      EXPECT_THAT(value, Eq(data.substr(offset, length)));

      // Verify 'partial last edge' reads.
      size_t partial = (offset + length) % kCharsPerFlat;
      ASSERT_THAT(result.n, Eq(partial));

      // Verify ending position if not EOF
      if (offset + length < data.size()) {
        size_t index = (offset + length) / kCharsPerFlat;
        EXPECT_THAT(nav.Current(), Eq(flats[index]));
      }

      CordRep::Unref(result.tree);
    }
  }
}

TEST_P(CordRepBtreeNavigatorTest, ReadBeyondLengthOfTree) {
  CordRepBtreeNavigator nav;
  nav.InitFirst(tree());
  ReadResult result = nav.Read(2, tree()->length);
  ASSERT_THAT(result.tree, Eq(nullptr));
}

}  // namespace
}  // namespace cord_internal
ABSL_NAMESPACE_END
}  // namespace absl
