// ---------------------------------------------------------------------
//
// Copyright (C) 2018 by the deal.II authors
//
// This file is part of the deal.II library.
//
// The deal.II library is free software; you can use it, redistribute
// it, and/or modify it under the terms of the GNU Lesser General
// Public License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// The full text of the license can be found in the file LICENSE at
// the top level of the deal.II distribution.
//
// ---------------------------------------------------------------------

#ifndef dealii_linear_index_iterator_h
#define dealii_linear_index_iterator_h

#include <deal.II/base/config.h>
#include <deal.II/base/exceptions.h>


DEAL_II_NAMESPACE_OPEN
/**
 * Many classes in deal.II, such as FullMatrix, TransposeTable, and
 * SparseMatrix, store their data in contiguous buffers (though the
 * <em>interpretation</em> of what the elements of these buffers represent
 * can, of course, be complex). For example, FullMatrix and TransposeTable
 * store their data in row major and column major order respectively, whereas
 * for SparseMatrix the mapping from buffer location to matrix entry
 * $\mathbf{A}(i, j)$ is more complicated. In any case, however, the
 * contiguous arrangements of elements enables random access iteration.

 * LinearIndexIterator provides most of the functionality needed to write
 * iterators for these classes. LinearIndexIterator is essentially a
 * simplified version of <code>boost::iterator_facade</code> that assumes
 * <code>AccessorType</code> provides certain members (documented below) that
 * completely describe the state of the iterator. The intended use of this
 * class is for containers to define their own accessor classes and then use
 * the curiously recurring template pattern (CRTP) technique to define their
 * iterators. For example, here is a container that uses LinearIndexIterator
 * to define its own iterator classes:
 *
 * @code
 * template <typename T>
 * class Container
 * {
 * protected:
 *   // forward declaration for friendship
 *   template <bool Constness>
 *   class Iterator;
 *
 *   template <bool Constness>
 *   class Accessor
 *   {
 *   public:
 *     // const iterators store a const pointer
 *     typedef typename std::conditional<Constness, const Container<T>*, Container<T>*>::type
 *     container_pointer_type;
 *
 *     // This typedef is assumed to exist.
 *     typedef std::size_t size_type;
 *
 *     // constructor.
 *     Accessor(const container_pointer_type container, const std::ptrdiff_t index);
 *
 *     // constructor.
 *     Accessor();
 *
 *     // get a constant reference to the current value.
 *     const T& value() const;
 *
 *   protected:
 *     container_pointer_type container;
 *     std::ptrdiff_t linear_index;
 *
 *     // LinearIndexIterator needs access to linear_index and container.
 *     friend class LinearIndexIterator<Iterator<Constness>, Accessor<Constness>>;
 *   };
 *
 *   template <bool Constness>
 *   class Iterator : public LinearIndexIterator<Iterator<Constness>, Accessor<Constness>>
 *   {
 *     // Constructor.
 *     Iterator(Container<T> * const container, const std::ptrdiff_t index);
 *
 *     // implement additional constructors here, but all state should be contained
 *     // in the Accessor, which is a member of the base class.
 *   };
 *
 * public:
 *   typedef std::size_t size_type;
 *   typedef Iterator<true> const_iterator;
 *   typedef Iterator<false> iterator;
 *
 *   iterator begin ();
 *   iterator end ();
 *
 *   const_iterator begin () const;
 *   const_iterator end () const;
 * };
 * @endcode
 *
 * @tparam DerivedIterator As shown in the example above, concrete iterator
 * classes should use this class with the CRTP technique: this provides the
 * boiler-plate comparison and arithmetic operators for iterators. This is
 * necessary for, e.g., LinearIndexIterator::operator++() to return the
 * correct type.
 *
 * @tparam AccessorType LinearIndexIterator assumes that the
 * <code>AccessorType</code> template parameter has the following members
 * which completely describe the current state of the iterator:
 * <ol>
 *   <li>A pointer named <code>container</code> to the original container (e.g.,
 *   the relevant SparseMatrix). This should be a <code>const</code> pointer
 *   for <code>const</code> iterators.</li>
 *   <li>An array index named <code>linear_index</code> that stores the current
 *   position in the container's storage buffer. <code>linear_index</code> does
 *   not need to be an integer: it could be a class type (convertible to the
 *   correct index type of the container) that implements
 *   <code>operator+=</code>, <code>operator&lt;</code>, and
 *   <code>operator==</code>. For example, one could implement a strided
 *   iterator by implementing <code>operator+=</code> and
 *   <code>operator-</code> with multiplicative factors.</li>
 * </ol>
 * In addition, <code>AccessorType</code> should declare the relevant
 * LinearIndexIterator instantiation to be a <code>friend</code> and define a
 * <code>size_type</code> type.
 *
 * @note TransposeTable uses this template to implement its iterators.
 *
 * @author David Wells, 2018
 */
template <class DerivedIterator, class AccessorType>
class LinearIndexIterator
{
public:
  /**
   * Iterator category.
   */
  typedef std::random_access_iterator_tag iterator_category;

  /**
   * A typedef for the type you get when you dereference an iterator of the
   * current kind.
   */
  typedef
  AccessorType value_type;

  /**
   * Difference type.
   */
  typedef std::ptrdiff_t difference_type;

  /**
   * Reference type.
   */
  typedef const value_type &reference;

  /**
   * Pointer type.
   */
  typedef const value_type *pointer;

  /**
   * Size type used by the underlying container.
   */
  typedef typename value_type::size_type size_type;

  /**
   * Copy operator.
   */
  DerivedIterator &
  operator = (const DerivedIterator &it);

  /**
   * Prefix increment.
   */
  DerivedIterator &operator ++ ();

  /**
   * Postfix increment.
   */
  DerivedIterator operator ++ (int);

  /**
   * Prefix decrement.
   */
  DerivedIterator &operator -- ();

  /**
   * Postfix decrement.
   */
  DerivedIterator operator -- (int);

  /**
   * Return an iterator that is @p n entries ahead of the current one.
   */
  DerivedIterator operator + (const difference_type n) const;

  /**
   * Return an iterator that is @p n entries behind the current one.
   */
  DerivedIterator operator - (const difference_type n) const;

  /**
   * Increment the iterator position by @p n.
   */
  DerivedIterator &operator += (const difference_type n);

  /**
   * Decrement the iterator position by @p n.
   */
  DerivedIterator &operator -= (const difference_type n);

  /**
   * Return the distance between the current iterator and the argument. The
   * distance is given by how many times one has to apply operator++() to the
   * current iterator to get the argument (for a positive return value), or
   * operator--() (for a negative return value).
   */
  difference_type operator - (const DerivedIterator &p) const;

  /**
   * Dereferencing operator.
   */
  reference operator * () const;

  /**
   * Dereferencing operator.
   */
  pointer operator -> () const;

  /**
   * Comparison operator. Returns <code>true</code> if both iterators point to
   * the same entry in the same container.
   */
  bool operator == (const DerivedIterator &) const;

  /**
   * Inverse of operator==().
   */
  bool operator != (const DerivedIterator &) const;

  /**
   * Comparison operator: uses the same ordering as operator<(), but also
   * checks for equality.
   *
   * This function is only valid if both iterators point into the same
   * container.
   */
  bool operator <= (const DerivedIterator &) const;

  /**
   * Comparison operator: uses the same ordering as operator>(), but also
   * checks for equality.
   *
   * This function is only valid if both iterators point into the same
   * container.
   */
  bool operator >= (const DerivedIterator &) const;

  /**
   * Comparison operator. Result is true if either the first row number is
   * smaller or if the row numbers are equal and the first index is smaller.
   *
   * This function is only valid if both iterators point into the same
   * container.
   */
  bool operator < (const DerivedIterator &) const;

  /**
   * Comparison operator. Works in the same way as operator<(), just the other
   * way round.
   */
  bool operator > (const DerivedIterator &) const;

protected:
  /**
   * The inheriting class should have a default constructor.
   */
  LinearIndexIterator () = default;

  /**
   * Constructor that copies an accessor.
   */
  LinearIndexIterator (const AccessorType accessor);

protected:
  /**
   * Store an object of the accessor class.
   */
  AccessorType accessor;
};



template <class DerivedIterator, class AccessorType>
inline
DerivedIterator &
LinearIndexIterator<DerivedIterator, AccessorType>::operator = (const DerivedIterator &it)
{
  accessor.container = it.container;
  accessor.linear_index = it.linear_index;
  return static_cast<DerivedIterator &>(*this);
}



template <class DerivedIterator, class AccessorType>
inline
DerivedIterator &
LinearIndexIterator<DerivedIterator, AccessorType>::operator ++ ()
{
  return operator+=(1);
}



template <class DerivedIterator, class AccessorType>
inline
DerivedIterator
LinearIndexIterator<DerivedIterator, AccessorType>::operator ++ (int)
{
  const DerivedIterator copy(this->accessor);
  operator+=(1);
  return copy;
}



template <class DerivedIterator, class AccessorType>
inline
DerivedIterator &
LinearIndexIterator<DerivedIterator, AccessorType>::operator -- ()
{
  return operator+=(-1);
}



template <class DerivedIterator, class AccessorType>
inline
DerivedIterator
LinearIndexIterator<DerivedIterator, AccessorType>::operator -- (int)
{
  const DerivedIterator copy(this->accessor);
  operator+=(-1);
  return copy;
}



template <class DerivedIterator, class AccessorType>
inline
DerivedIterator
LinearIndexIterator<DerivedIterator, AccessorType>::
operator + (const difference_type n) const
{
  DerivedIterator copy(this->accessor);
  copy += n;
  return copy;
}



template <class DerivedIterator, class AccessorType>
inline
DerivedIterator
LinearIndexIterator<DerivedIterator, AccessorType>::
operator - (const difference_type n) const
{
  DerivedIterator copy(this->accessor);
  copy += -n;
  return copy;
}



template <class DerivedIterator, class AccessorType>
inline
DerivedIterator &
LinearIndexIterator<DerivedIterator, AccessorType>::operator += (const difference_type n)
{
  accessor.linear_index += n;
  return static_cast<DerivedIterator &>(*this);
}



template <class DerivedIterator, class AccessorType>
inline
DerivedIterator &
LinearIndexIterator<DerivedIterator, AccessorType>::operator -= (const difference_type n)
{
  return operator+=(-n);
}



template <class DerivedIterator, class AccessorType>
inline
typename LinearIndexIterator<DerivedIterator, AccessorType>::difference_type
LinearIndexIterator<DerivedIterator, AccessorType>::
operator - (const DerivedIterator &other) const
{
  Assert(this->accessor.container == other.accessor.container,
         ExcMessage("Only iterators pointing to the same container can be compared."));
  return this->accessor.linear_index - other.accessor.linear_index;
}



template <class DerivedIterator, class AccessorType>
inline
typename LinearIndexIterator<DerivedIterator, AccessorType>::reference
LinearIndexIterator<DerivedIterator, AccessorType>::operator * () const
{
  return accessor;
}



template <class DerivedIterator, class AccessorType>
inline
typename LinearIndexIterator<DerivedIterator, AccessorType>::pointer
LinearIndexIterator<DerivedIterator, AccessorType>::operator -> () const
{
  return &accessor;
}



template <class DerivedIterator, class AccessorType>
inline
bool
LinearIndexIterator<DerivedIterator, AccessorType>::
operator == (const DerivedIterator &other) const
{
  const auto &other_2 = static_cast<decltype(*this) &>(other);
  return accessor.container == other_2.accessor.container &&
         accessor.linear_index == other_2.accessor.linear_index;
}



template <class DerivedIterator, class AccessorType>
inline
bool
LinearIndexIterator<DerivedIterator, AccessorType>::
operator != (const DerivedIterator &other) const
{
  return !(*this == other);
}



template <class DerivedIterator, class AccessorType>
inline
bool
LinearIndexIterator<DerivedIterator, AccessorType>::
operator <= (const DerivedIterator &other) const
{
  return (*this == other) || (*this < other);
}



template <class DerivedIterator, class AccessorType>
inline
bool
LinearIndexIterator<DerivedIterator, AccessorType>::
operator >= (const DerivedIterator &other) const
{
  return !(*this < other);
}



template <class DerivedIterator, class AccessorType>
inline
bool
LinearIndexIterator<DerivedIterator, AccessorType>::
operator < (const DerivedIterator &other) const
{
  Assert(this->accessor.container == other.accessor.container,
         ExcMessage("Only iterators pointing to the same container can be compared."));
  return this->accessor.linear_index < other.accessor.linear_index;
}



template <class DerivedIterator, class AccessorType>
inline
bool
LinearIndexIterator<DerivedIterator, AccessorType>::
operator > (const DerivedIterator &other) const
{
  return other < *this;
}



template <class DerivedIterator, class AccessorType>
inline
LinearIndexIterator<DerivedIterator, AccessorType>::LinearIndexIterator
(const AccessorType accessor)
  :
  accessor(accessor)
{}


DEAL_II_NAMESPACE_CLOSE

#endif
