// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause

#ifndef vtkCollectionRange_h
#define vtkCollectionRange_h

#include "vtkCollection.h"
#include "vtkMeta.h"
#include "vtkRange.h"
#include "vtkSmartPointer.h"

#include <cassert>

namespace vtk
{
namespace detail
{
VTK_ABI_NAMESPACE_BEGIN

template <typename CollectionType>
struct CollectionRange;
template <typename CollectionType>
struct CollectionIterator;

//------------------------------------------------------------------------------
// Detect vtkCollection types
template <typename T>
struct IsCollection : std::is_base_of<vtkCollection, T>
{
};

template <typename CollectionType, typename T = CollectionType>
using EnableIfIsCollection = typename std::enable_if<IsCollection<CollectionType>::value, T>::type;

//------------------------------------------------------------------------------
// Detect the type of items held by the collection by checking the return type
// of GetNextItem(), or GetNextItemAsObject() as a fallback.
template <typename CollectionType>
struct GetCollectionItemType
{
  static_assert(IsCollection<CollectionType>::value, "Invalid vtkCollection subclass.");

private:
  // The GetType methods are only used in a decltype context and are left
  // unimplemented as we only care about their signatures. They are used to
  // determine the type of object held by the collection.
  //
  // By passing literal 0 as the argument, the overload taking `int` is
  // preferred and returns the same type as CollectionType::GetNextItem, which
  // is usually the exact type held by the collection (e.g.
  // vtkRendererCollection::GetNextItem returns vtkRenderer*).
  //
  // If the collection class does not define GetNextItem, SFINAE removes the
  // preferred `int` overload, and the `...` overload is used instead. This
  // method returns the same type as vtkCollection::GetNextItemAsObject, which
  // is vtkObject*. This lets us define a more derived collection item type
  // when possible, while falling back to the general vtkObject if a more
  // refined type is not known.

  // not implemented
  template <typename T>
  static auto GetType(...) -> decltype(std::declval<T>().GetNextItemAsObject());

  // not implemented
  template <typename T>
  static auto GetType(int) -> decltype(std::declval<T>().GetNextItem());

  using PointerType = decltype(GetType<CollectionType>(0));

public:
  // Just use std::remove pointer, vtk::detail::StripPointer is overkill.
  using Type = typename std::remove_pointer<PointerType>::type;
};

//------------------------------------------------------------------------------
// Collection iterator. Reference, value, and pointer types are all ItemType
// pointers, since:
// a) values: ItemType* instead of ItemType because vtkObjects can't be
//    copied/assigned.
// b) references: No good usecase to change the pointers held by the collection
//    by returning ItemType*&, nor would returning ItemType& be useful, since
//    it'd have to be dereferenced anyway to pass it anywhere, and vtkObjects
//    are conventionally held by address.
// c) pointers: Returning ItemType** from operator-> would be useless.
//
// There are no const_reference, etc, since VTK is not const correct and marking
// vtkObjects consts makes them unusable.
template <typename CollectionType>
struct CollectionIterator
{
  static_assert(IsCollection<CollectionType>::value, "Invalid vtkCollection subclass.");

private:
  using ItemType = typename GetCollectionItemType<CollectionType>::Type;

public:
  using iterator_category = std::forward_iterator_tag;
  using value_type = typename GetCollectionItemType<CollectionType>::Type*;
  using difference_type = int;
  using pointer = typename GetCollectionItemType<CollectionType>::Type*;
  using reference = typename GetCollectionItemType<CollectionType>::Type*;

  CollectionIterator() noexcept
    : Element(nullptr)
  {
  }

  CollectionIterator(const CollectionIterator& o) noexcept = default;
  CollectionIterator& operator=(const CollectionIterator& o) noexcept = default;

  CollectionIterator& operator++() noexcept // prefix
  {
    this->Increment();
    return *this;
  }

  CollectionIterator operator++(int) noexcept // postfix
  {
    auto elem = this->Element;
    this->Increment();
    return CollectionIterator{ elem };
  }

  reference operator*() const noexcept { return this->GetItem(); }

  pointer operator->() const noexcept { return this->GetItem(); }

  friend bool operator==(const CollectionIterator& lhs, const CollectionIterator& rhs) noexcept
  {
    return lhs.Element == rhs.Element;
  }

  friend bool operator!=(const CollectionIterator& lhs, const CollectionIterator& rhs) noexcept
  {
    return lhs.Element != rhs.Element;
  }

  friend void swap(CollectionIterator& lhs, CollectionIterator& rhs) noexcept
  {
    using std::swap;
    swap(lhs.Element, rhs.Element);
  }

  friend struct CollectionRange<CollectionType>;

protected:
  CollectionIterator(vtkCollectionElement* element) noexcept
    : Element(element)
  {
  }

private:
  void Increment() noexcept
  { // incrementing an invalid iterator is UB, no need to check for non-null.
    this->Element = this->Element->Next;
  }

  ItemType* GetItem() const noexcept { return static_cast<ItemType*>(this->Element->Item); }

  vtkCollectionElement* Element;
};

//------------------------------------------------------------------------------
// Collection range proxy.
// The const_iterators/references are the same as the non-const versions, since
// vtkObjects marked const are unusable.
template <typename CollectionType>
struct CollectionRange
{
  static_assert(IsCollection<CollectionType>::value, "Invalid vtkCollection subclass.");

  using ItemType = typename GetCollectionItemType<CollectionType>::Type;

  // NOTE: The const items are the same as the mutable ones, since const
  // vtkObjects are generally unusable.
  using size_type = int; // int is used by the vtkCollection API.
  using iterator = CollectionIterator<CollectionType>;
  using const_iterator = CollectionIterator<CollectionType>;
  using reference = ItemType*;
  using const_reference = ItemType*;
  using value_type = ItemType*;

  CollectionRange(CollectionType* coll) noexcept
    : Collection(coll)
  {
    assert(this->Collection);
  }

  CollectionType* GetCollection() const noexcept { return this->Collection; }

  size_type size() const noexcept { return this->Collection->GetNumberOfItems(); }

  iterator begin() const
  {
    vtkCollectionSimpleIterator cookie;
    this->Collection->InitTraversal(cookie);
    // The cookie is a linked list node pointer, vtkCollectionElement:
    return iterator{ static_cast<vtkCollectionElement*>(cookie) };
  }

  iterator end() const { return iterator{ nullptr }; }

  // Note: These return mutable objects because const vtkObject are unusable.
  const_iterator cbegin() const
  {
    vtkCollectionSimpleIterator cookie;
    this->Collection->InitTraversal(cookie);
    // The cookie is a linked list node pointer, vtkCollectionElement:
    return const_iterator{ static_cast<vtkCollectionElement*>(cookie) };
  }

  // Note: These return mutable objects because const vtkObjects are unusable.
  const_iterator cend() const { return const_iterator{ nullptr }; }

private:
  vtkSmartPointer<CollectionType> Collection;
};

VTK_ABI_NAMESPACE_END
}
} // end namespace vtk::detail

#endif // vtkCollectionRange_h

// VTK-HeaderTest-Exclude: vtkCollectionRange.h
