/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file LICENSE.rst or https://cmake.org/licensing for details.  */
#pragma once

#include "cmConfigure.h" // IWYU pragma: keep

#include <cassert>
#include <vector>

/**
  @brief A adaptor for traversing a tree structure in a vector

  This class is not intended to be wholly generic like a standard library
  container adaptor.  Mostly it exists to facilitate code sharing for the
  needs of the cmState.  For example, the Truncate() method is a specific
  requirement of the cmState.

  An empty cmLinkedTree provides a Root() method, and an Push() method,
  each of which return iterators.  A Tree can be built up by extending
  from the root, and then extending from any other iterator.

  An iterator resulting from this tree construction can be
  forward-only-iterated toward the root.  Extending the tree never
  invalidates existing iterators.
 */
template <typename T>
class cmLinkedTree
{
  using PositionType = typename std::vector<T>::size_type;
  using PointerType = T*;
  using ReferenceType = T&;

public:
  class iterator
  {
    friend class cmLinkedTree;
    cmLinkedTree* Tree;

    // The Position is always 'one past the end'.
    PositionType Position;

    iterator(cmLinkedTree* tree, PositionType pos)
      : Tree(tree)
      , Position(pos)
    {
    }

  public:
    iterator()
      : Tree(nullptr)
      , Position(0)
    {
    }

    void operator++()
    {
      assert(this->Tree);
      assert(this->Tree->UpPositions.size() == this->Tree->Data.size());
      assert(this->Position <= this->Tree->Data.size());
      assert(this->Position > 0);
      this->Position = this->Tree->UpPositions[this->Position - 1];
    }

    PointerType operator->() const
    {
      assert(this->Tree);
      assert(this->Tree->UpPositions.size() == this->Tree->Data.size());
      assert(this->Position <= this->Tree->Data.size());
      assert(this->Position > 0);
      return this->Tree->GetPointer(this->Position - 1);
    }

    PointerType operator->()
    {
      assert(this->Tree);
      assert(this->Tree->UpPositions.size() == this->Tree->Data.size());
      assert(this->Position <= this->Tree->Data.size());
      assert(this->Position > 0);
      return this->Tree->GetPointer(this->Position - 1);
    }

    ReferenceType operator*() const
    {
      assert(this->Tree);
      assert(this->Tree->UpPositions.size() == this->Tree->Data.size());
      assert(this->Position <= this->Tree->Data.size());
      assert(this->Position > 0);
      return this->Tree->GetReference(this->Position - 1);
    }

    ReferenceType operator*()
    {
      assert(this->Tree);
      assert(this->Tree->UpPositions.size() == this->Tree->Data.size());
      assert(this->Position <= this->Tree->Data.size());
      assert(this->Position > 0);
      return this->Tree->GetReference(this->Position - 1);
    }

    bool operator==(iterator other) const
    {
      assert(this->Tree);
      assert(this->Tree->UpPositions.size() == this->Tree->Data.size());
      assert(this->Tree == other.Tree);
      return this->Position == other.Position;
    }

    bool operator!=(iterator other) const
    {
      assert(this->Tree);
      assert(this->Tree->UpPositions.size() == this->Tree->Data.size());
      return !(*this == other);
    }

    bool IsValid() const
    {
      if (!this->Tree) {
        return false;
      }
      return this->Position <= this->Tree->Data.size();
    }

    bool StrictWeakOrdered(iterator other) const
    {
      assert(this->Tree);
      assert(this->Tree == other.Tree);
      return this->Position < other.Position;
    }
  };

  iterator Root() const
  {
    return iterator(const_cast<cmLinkedTree*>(this), 0);
  }

  iterator Push(iterator it) { return this->Push_impl(it, T()); }

  iterator Push(iterator it, T t) { return this->Push_impl(it, std::move(t)); }

  bool IsLast(iterator it) { return it.Position == this->Data.size(); }

  iterator Pop(iterator it)
  {
    assert(!this->Data.empty());
    assert(this->UpPositions.size() == this->Data.size());
    bool const isLast = this->IsLast(it);
    ++it;
    // If this is the last entry then no other entry can refer
    // to it so we can drop its storage.
    if (isLast) {
      this->Data.pop_back();
      this->UpPositions.pop_back();
    }
    return it;
  }

  iterator Truncate()
  {
    assert(!this->UpPositions.empty());
    this->UpPositions.erase(this->UpPositions.begin() + 1,
                            this->UpPositions.end());
    assert(!this->Data.empty());
    this->Data.erase(this->Data.begin() + 1, this->Data.end());
    return iterator(this, 1);
  }

  void Clear()
  {
    this->UpPositions.clear();
    this->Data.clear();
  }

private:
  T& GetReference(PositionType pos) { return this->Data[pos]; }

  T* GetPointer(PositionType pos) { return &this->Data[pos]; }

  iterator Push_impl(iterator it, T&& t)
  {
    assert(this->UpPositions.size() == this->Data.size());
    assert(it.Position <= this->UpPositions.size());
    this->UpPositions.push_back(it.Position);
    this->Data.push_back(std::move(t));
    return iterator(this, this->UpPositions.size());
  }

  std::vector<T> Data;
  std::vector<PositionType> UpPositions;
};
