// ---------------------------------------------------------------------
//
// Copyright (C) 1999 - 2023 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.md at
// the top level directory of deal.II.
//
// ---------------------------------------------------------------------

#ifndef dealii_matrix_iterator_h
#define dealii_matrix_iterator_h


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

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

DEAL_II_NAMESPACE_OPEN

/**
 * Iterator for constant and non-constant matrices.
 *
 * This iterator is abstracted from the actual matrix type and can be used for
 * any matrix having the required ACCESSOR type.
 */
template <class ACCESSOR>
class MatrixIterator
{
public:
  /**
   * Declare type for container size.
   */
  using size_type = types::global_dof_index;

  /**
   * Typedef for the matrix type (including constness) we are to operate on.
   */
  using MatrixType = typename ACCESSOR::MatrixType;

  /**
   * Constructor. Create an iterator into the matrix <tt>matrix</tt> for the
   * given <tt>row</tt> and the <tt>index</tt> within it.
   */
  MatrixIterator(MatrixType *    matrix,
                 const size_type row   = 0,
                 const size_type index = 0);

  /**
   * Copy from another matrix iterator. Mostly implemented to allow
   * initialization of a constant iterator from a non constant, this function
   * only requires that a conversion from the other iterator's accessor to
   * this accessor object is possible.
   */
  template <class OtherAccessor>
  MatrixIterator(const MatrixIterator<OtherAccessor> &other);

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

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

  /**
   * Dereferencing operator.
   */
  const ACCESSOR &
  operator*() const;

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

  /**
   * Comparison. True, if both accessors are equal.
   */
  template <class OtherAccessor>
  bool
  operator==(const MatrixIterator<OtherAccessor> &) const;

  /**
   * Inverse of <tt>==</tt>.
   */
  template <class OtherAccessor>
  bool
  operator!=(const MatrixIterator<OtherAccessor> &) 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 matrix.
   */
  bool
  operator<(const MatrixIterator &) const;

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

private:
  /**
   * Store an object of the accessor class.
   */
  ACCESSOR accessor;

  // Allow other iterators access to private data.
  template <class OtherAccessor>
  friend class MatrixIterator;
};


//----------------------------------------------------------------------//

template <class ACCESSOR>
inline MatrixIterator<ACCESSOR>::MatrixIterator(MatrixType *    matrix,
                                                const size_type r,
                                                const size_type i)
  : accessor(matrix, r, i)
{}


template <class ACCESSOR>
template <class OtherAccessor>
inline MatrixIterator<ACCESSOR>::MatrixIterator(
  const MatrixIterator<OtherAccessor> &other)
  : accessor(other.accessor)
{}


template <class ACCESSOR>
inline MatrixIterator<ACCESSOR> &
MatrixIterator<ACCESSOR>::operator++()
{
  accessor.advance();
  return *this;
}


template <class ACCESSOR>
inline MatrixIterator<ACCESSOR>
MatrixIterator<ACCESSOR>::operator++(int)
{
  const MatrixIterator iter = *this;
  accessor.advance();
  return iter;
}


template <class ACCESSOR>
inline const ACCESSOR &
MatrixIterator<ACCESSOR>::operator*() const
{
  return accessor;
}


template <class ACCESSOR>
inline const ACCESSOR *
MatrixIterator<ACCESSOR>::operator->() const
{
  return &accessor;
}


template <class ACCESSOR>
template <class OtherAccessor>
inline bool
MatrixIterator<ACCESSOR>::operator==(
  const MatrixIterator<OtherAccessor> &other) const
{
  return (accessor == other.accessor);
}


template <class ACCESSOR>
template <class OtherAccessor>
inline bool
MatrixIterator<ACCESSOR>::operator!=(
  const MatrixIterator<OtherAccessor> &other) const
{
  return !(*this == other);
}


template <class ACCESSOR>
inline bool
MatrixIterator<ACCESSOR>::operator<(const MatrixIterator &other) const
{
  Assert(&accessor.get_matrix() == &other.accessor.get_matrix(),
         ExcInternalError());

  return (accessor < other.accessor);
}


template <class ACCESSOR>
inline bool
MatrixIterator<ACCESSOR>::operator>(const MatrixIterator &other) const
{
  return (other < *this);
}

DEAL_II_NAMESPACE_CLOSE

#endif
