/* SPDX-FileCopyrightText: 2023 Blender Authors
 *
 * SPDX-License-Identifier: GPL-2.0-or-later */

#pragma once

/** \file
 * \ingroup freestyle
 * \brief Iterators used to iterate over the elements of the Stroke
 */

#include "Stroke.h"

namespace Freestyle {

namespace StrokeInternal {

//
// StrokeVertexIterator
//
/////////////////////////////////////////////////

/** Class defining an iterator designed to iterate over the StrokeVertex of a Stroke.
 *  An instance of a StrokeVertexIterator can only be obtained from a Stroke by calling
 * strokeVerticesBegin() or strokeVerticesEnd(). It is iterating over the same vertices as an
 * Interface0DIterator. The difference resides in the object access. Indeed, an Interface0DIterator
 * allows only an access to an Interface0D whereas we could need to access the specialized
 * StrokeVertex type. In this case, one should use a StrokeVertexIterator. The
 * castToInterface0DIterator() method is useful to get an Interface0DIterator from a
 * StrokeVertexIterator in order to call any functions of the type UnaryFunction0D. \attention In
 * the scripting language, you must call \code it2 = StrokeVertexIterator(it1) \endcode instead of
 * \code it2 = it1 \endcode where \a it1 and \a it2 are 2 StrokeVertexIterator.
 *  Otherwise, incrementing \a it1 will also increment \a it2.
 */
class StrokeVertexIterator : public Interface0DIteratorNested {
 public:
  /** Default constructor. */
  StrokeVertexIterator() {}

  /** Copy constructor. */
  StrokeVertexIterator(const StrokeVertexIterator &vi)
  {
    _it = vi._it;
    _begin = vi._begin;
    _end = vi._end;
  }

  StrokeVertexIterator(const Stroke::vertex_container::iterator &it,
                       const Stroke::vertex_container::iterator &begin,
                       const Stroke::vertex_container::iterator &end)
  {
    _it = it;
    _begin = begin;
    _end = end;
  }

  virtual ~StrokeVertexIterator() {}

  /** Casts this StrokeVertexIterator into an Interface0DIterator.
   *  Useful for any call to a function of the type UnaryFunction0D.
   */
  inline Interface0DIterator castToInterface0DIterator() const
  {
    Interface0DIterator ret(new StrokeVertexIterator(*this));
    return ret;
  }

  /** operator=
   *  \attention In the scripting language, you must call \code it2 = StrokeVertexIterator(it1)
   * \endcode instead of \code it2 = it1 \endcode where \a it1 and \a it2 are 2
   * StrokeVertexIterator. Otherwise, incrementing \a it1 will also increment \a it2.
   */
  StrokeVertexIterator &operator=(const StrokeVertexIterator &vi)
  {
    _it = vi._it;
    _begin = vi._begin;
    _end = vi._end;
    return *this;
  }

  /** Returns the string "StrokeVertexIterator". */
  virtual string getExactTypeName() const
  {
    return "StrokeVertexIterator";
  }

  /** Returns a reference to the pointed StrokeVertex.
   *  In the scripting language, you must call "getObject()"instead.
   */
  virtual StrokeVertex &operator*()
  {
    return **_it;
  }

  /** Returns a pointer to the pointed StrokeVertex.
   * Can't be called in the scripting language.
   */
  virtual StrokeVertex *operator->()
  {
    return &(operator*());
  }

  /** Increments. In the scripting language, call "increment()". */
  virtual StrokeVertexIterator &operator++()
  {
    increment();
    return *this;
  }

  /** Increments. In the scripting language, call "increment()". */
  virtual StrokeVertexIterator operator++(int)
  {
    StrokeVertexIterator ret(*this);
    increment();
    return ret;
  }

  /** Decrements. In the scripting language, call "decrement()". */
  virtual StrokeVertexIterator &operator--()
  {
    decrement();
    return *this;
  }

  /** Decrements. In the scripting language, call "decrement()". */
  virtual StrokeVertexIterator operator--(int)
  {
    StrokeVertexIterator ret(*this);
    decrement();
    return ret;
  }

  /** Increments. */
  virtual int increment()
  {
    ++_it;
    return 0;
  }

  /** Decrements. */
  virtual int decrement()
  {
    --_it;
    return 0;
  }

  /** Returns true if the pointed StrokeVertex is the first of the Stroke. */
  bool isBegin() const
  {
    return _it == _begin;
  }

  /** Returns true if the pointed StrokeVertex is the final valid StrokeVertex of the Stroke. */
  bool atLast()
  {
    if (_it == _end) {
      return false;
    }

    ++_it;
    bool result = (_it == _end);
    --_it;
    return result;
  }

  /** Returns true if the pointed StrokeVertex is after the last StrokeVertex of the Stroke. */
  bool isEnd() const
  {
    return _it == _end;
  }

  /** operator == */
  virtual bool operator==(const Interface0DIteratorNested &it) const
  {
    const StrokeVertexIterator *it_exact = dynamic_cast<const StrokeVertexIterator *>(&it);
    if (!it_exact) {
      return false;
    }
    return (_it == it_exact->_it);
  }

  /** Returns the curvilinear abscissa of the current point */
  virtual float t() const
  {
    return (*_it)->curvilinearAbscissa();
  }

  /** Returns the point's parameter in the stroke */
  virtual float u() const
  {
    return (*_it)->u();
  }

  /** Cloning method */
  virtual StrokeVertexIterator *copy() const
  {
    return new StrokeVertexIterator(*this);
  }

  //
  // Not exported in Python
  //
  //////////////////////////////////////////////////
  const Stroke::vertex_container::iterator &getIt()
  {
    return _it;
  }

 private:
  Stroke::vertex_container::iterator _it;
  Stroke::vertex_container::iterator _begin;
  Stroke::vertex_container::iterator _end;
};

}  // end of namespace StrokeInternal

} /* namespace Freestyle */
