/*=========================================================================
 *
 *  Copyright NumFOCUS
 *
 *  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.txt
 *
 *  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.
 *
 *=========================================================================*/

#ifndef itkRangeGTestUtilities_h
#define itkRangeGTestUtilities_h

#include <gtest/gtest.h> // For EXPECT_EQ.

#include <utility>  // For move.
#include <iterator> // For begin and end.

namespace itk
{
// Utilities for GoogleTest unit tests of iterator ranges.
// Note: This class is only for internal (testing) purposes.
// It is not part of the public API of ITK.
class RangeGTestUtilities
{
public:
  template <typename TRange>
  static void
  ExpectBeginIsEndWhenRangeIsDefaultConstructed()
  {
    TRange defaultConstructedRange;
    EXPECT_EQ(std::begin(defaultConstructedRange), std::end(defaultConstructedRange));
  }


  template <typename TRange>
  static void
  ExpectReverseBeginIsReverseEndWhenRangeIsDefaultConstructed()
  {
    TRange defaultConstructedRange;
    EXPECT_EQ(std::rbegin(defaultConstructedRange), std::rend(defaultConstructedRange));
  }


  template <typename TRange>
  static void
  ExpectZeroSizeWhenRangeIsDefaultConstructed()
  {
    TRange defaultConstructedRange;
    EXPECT_EQ(defaultConstructedRange.size(), 0);
  }


  template <typename TRange>
  static void
  ExpectRangeIsEmptyWhenDefaultConstructed()
  {
    TRange defaultConstructedRange;
    EXPECT_TRUE(defaultConstructedRange.empty());
  }


  template <typename TRange>
  static void
  ExpectCopyConstructedRangeHasSameIteratorsAsOriginal(const TRange & originalRange)
  {
    const TRange copyConstructedRange(originalRange);

    ExpectRangesHaveEqualBeginAndEnd(copyConstructedRange, originalRange);
  }


  template <typename TRange>
  static void
  ExpectCopyAssignedRangeHasSameIteratorsAsOriginal(const TRange & originalRange)
  {
    TRange copyAssignedRange;
    copyAssignedRange = originalRange;

    ExpectRangesHaveEqualBeginAndEnd(copyAssignedRange, originalRange);
  }


  template <typename TRange>
  static void
  ExpectMoveConstructedRangeHasSameIteratorsAsOriginalBeforeMove(TRange && originalRange)
  {
    const TRange originalRangeBeforeMove = originalRange;
    TRange       moveConstructedRange(std::move(originalRange));

    ExpectRangesHaveEqualBeginAndEnd(moveConstructedRange, originalRangeBeforeMove);
  }


  template <typename TRange>
  static void
  ExpectMoveAssignedRangeHasSameIteratorsAsOriginalBeforeMove(TRange && originalRange)
  {
    const TRange originalRangeBeforeMove = originalRange;

    TRange moveAssignedRange;
    moveAssignedRange = std::move(originalRange);

    ExpectRangesHaveEqualBeginAndEnd(moveAssignedRange, originalRangeBeforeMove);
  }


  template <typename TRange>
  static void
  ExpectIteratorIsDefaultConstructible()
  {
    using IteratorType = typename TRange::iterator;
    constexpr IteratorType defaultConstructedIterator{};

    // Test that a default-constructed iterator behaves according to C++ proposal
    // N3644, "Null Forward Iterators" by Alan Talbot, which is accepted with
    // C++14: "value-initialized iterators may be compared and shall compare
    // equal to other value-initialized iterators of the same type."
    // https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3644.pdf

    ExpectIteratorEqualsItself(defaultConstructedIterator);
    EXPECT_EQ(defaultConstructedIterator, IteratorType());
  }


  // Checks the `constexpr` member functions begin() and end() of a container like FixedArray, Index, Offset and Size.
  template <typename TContainer>
  static constexpr bool
  CheckConstexprBeginAndEndOfContainer()
  {
    using ConstContainerType = const TContainer;
    using ValueType = std::remove_reference_t<decltype(*(TContainer().begin()))>;

    static_assert(std::is_same_v<decltype(*(TContainer().begin())), ValueType &>,
                  "For a non-const container, begin() should return a non-const reference");
    static_assert(std::is_same_v<decltype(*(ConstContainerType().begin())), const ValueType &>,
                  "For a const container, begin() should return a const reference");
    static_assert(std::is_same_v<decltype(*(TContainer().cbegin())), const ValueType &> &&
                    std::is_same_v<decltype(*(ConstContainerType().cbegin())), const ValueType &>,
                  "For any container, cbegin() should return a const reference");

    static_assert(std::is_same_v<decltype(*(TContainer().end())), ValueType &>,
                  "For a non-const container, end() should return a non-const reference");
    static_assert(std::is_same_v<decltype(*(ConstContainerType().end())), const ValueType &>,
                  "For a const container, end() should return a const reference");
    static_assert(std::is_same_v<decltype(*(TContainer().cend())), const ValueType &> &&
                    std::is_same_v<decltype(*(ConstContainerType().cend())), const ValueType &>,
                  "For any container, cend() should return a const reference");

    constexpr TContainer container{};

    static_assert(container.cbegin() == container.begin(), "cbegin() should return the same iterator as begin().");
    static_assert(container.cend() == container.end(), "cend() should return the same iterator as end().");

    // Just return true to ease calling this function inside a static_assert.
    return true;
  }


  // Checks the nested types that are required according to the C++ Standard section "Container requirements"
  // [container.reqmts].
  template <typename TContainer, typename TValue = typename TContainer::ValueType>
  static constexpr bool
  CheckContainerRequirementsOnNestedTypes()
  {
    AssertSameType<typename TContainer::value_type, TValue>();
    AssertSameType<typename TContainer::reference, TValue &>();
    AssertSameType<typename TContainer::const_reference, const TValue &>();

    // The C++ Standard requires that "the type `iterator` is convertible to `const_iterator`".
    static_assert(std::is_convertible_v<typename TContainer::iterator, typename TContainer::const_iterator>);

    using difference_type = typename TContainer::difference_type;
    using size_type = typename TContainer::size_type;

    static_assert(std::is_signed_v<difference_type>);
    static_assert(std::is_integral_v<difference_type>);

    // `size_type` must be "an unsigned integer type that can represent any non-negative value of `difference_type`."
    static_assert(std::is_unsigned_v<size_type>);
    static_assert(sizeof(size_type) >= sizeof(difference_type));
    return true;
  }


  // Expects that `distance(begin, end)` is equal to `size`, for the specified container.
  template <typename TContainer>
  static void
  ExpectDistanceFromBeginToEndEqualsSize(const TContainer & container)
  {
    const auto distance = std::distance(std::begin(container), std::end(container));
    ASSERT_GE(distance, 0);
    EXPECT_EQ(static_cast<size_t>(distance), std::size(container));
  }


  // Expects that `distance(&front, &back) + 1` is equal to `size`, for the specified (non-empty) container.
  template <typename TContainer>
  static void
  ExpectDistanceFromFrontToBackPlusOneEqualsSize(const TContainer & container)
  {
    ASSERT_FALSE(std::empty(container));
    const auto distance = std::distance(&container.front(), &container.back());
    ASSERT_GE(distance, 0);
    EXPECT_EQ(static_cast<size_t>(distance + 1), std::size(container));
  }


  // Tells if `distance(&front, &back) + 1` is equal to `size`, for the specified container.
  template <typename TContainer>
  static constexpr bool
  IsDistanceFromFrontToBackPlusOneEqualToSize(const TContainer & container)
  {
    return std::distance(&container.front(), &container.back()) + 1 == container.size();
  }


  template <typename TRange>
  static void
  CheckFrontAndBack(TRange && range)
  {
    ASSERT_FALSE(std::empty(range));

    // Check that front() is at the begin, and back() is just before the end.
    EXPECT_EQ(&range.front(), &*std::cbegin(range));
    EXPECT_EQ(&range.back(), &*std::prev(std::cend(range)));

    // Check that const and non-const overloads return a reference to the same element.
    EXPECT_EQ(&std::as_const(range).front(), &range.front());
    EXPECT_EQ(&std::as_const(range).back(), &range.back());
  }


  // Checks that each element of the specified range is zero.
  template <typename TRange>
  static void
  ExpectEachElementIsZero(const TRange & range)
  {
    for (const auto & element : range)
    {
      EXPECT_EQ(element, 0);
    }
  }

private:
  template <typename T1, typename T2>
  static constexpr void
  AssertSameType()
  {
    static_assert(std::is_same_v<T1, T2>);
  }

  template <typename TRange>
  static void
  ExpectRangesHaveEqualBeginAndEnd(const TRange & range1, const TRange & range2)
  {
    EXPECT_EQ(std::begin(range1), std::begin(range2));
    EXPECT_EQ(std::end(range1), std::end(range2));
  }

  template <typename TIterator>
  static void
  ExpectIteratorEqualsItself(const TIterator & it)
  {
    static_assert(!std::is_pointer_v<TIterator>,
                  "There should be a specific `ExpectIteratorEqualsItself` overload for a pointer as argument");

    // Checks the (typically) user-defined `operator==` and `operator!=` of TIterator.
    EXPECT_TRUE(it == it);
    EXPECT_FALSE(it != it);
  }

  template <typename TValue>
  static void
  ExpectIteratorEqualsItself(TValue *)
  {
    // Overload for the use of a pointer type as iterator. Intentionally does "nothing", as a pointer always
    // equals itself, by definition. Aims to avoid "warning: self-comparison always evaluates to false/true
    // spell-check-disable
    // [-Wtautological-compare]", as produced by clang 13.0.1 on Ubuntu 20.04.
    // spell-check-enable
  }
};

} // end namespace itk

#endif
