/* -*- mode: c++; indent-tabs-mode: nil -*- */
/** @file QC_RangeIterator.qpp RangeIterator class definition */
/*
    Qore Programming Language

    Copyright 2003 - 2023 Qore Technologies s.r.o.

    Permission is hereby granted, free of charge, to any person obtaining a
    copy of this software and associated documentation files (the "Software"),
    to deal in the Software without restriction, including without limitation
    the rights to use, copy, modify, merge, publish, distribute, sublicense,
    and/or sell copies of the Software, and to permit persons to whom the
    Software is furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    DEALINGS IN THE SOFTWARE.

    Note that the Qore library is released under a choice of three open-source
    licenses: MIT (as above), LGPL 2+, or GPL 2+; see README-LICENSE for more
    information.
*/

#include <qore/Qore.h>
#include "qore/intern/RangeIterator.h"

//! This class defines a range-like iterator to be used to iterate numerical sequences
/** The main purpose is to provide resource friendly iterator to generate
    numerical rows (sequences) with ascending and descending ordering.

    The RangeIterator class provides an iterator for loop statements with functionality
    similar to @ref range(). Unlike range(), RangeIterator objects
    do not generate real @ref list "lists" but calculate iteration values on demand.

    This results in memory-friendly handling for large numerical sequences compared to
    generating a list in memory and iterating that list (as with @ref Qore::range()).

    @par Example: RangeIterator basic usage
    @code{.py}
RangeIterator r(0, 2);
foreach int i in (r) {
    printf("i=%d\n", i);
}
# i=0
# i=1
# i=2
    @endcode

    @see range()
    @see xrange()

    @since
    - %Qore 0.8.6
    - %Qore 0.9.5 does not include the upper limit in the range unless @ref broken-range "%broken-range" is set
 */
qclass RangeIterator [arg=RangeIterator* i; ns=Qore; vparent=AbstractIterator];

//! creates the numerical sequence iterator with the initial arguments
/** @param start an initial value
    @param stop a final value
    @param step is the interval. Default = 1
    @param val an optional value to be returned instead of the default integer value

    @par Example:
    @code{.py}
RangeIterator i(5, 10, 2);
    @endcode

    @see xrange()

    @since %Qore 0.8.11.1 this method takes the optional \a val argument
 */
RangeIterator::constructor(int start, int stop, int step = 1, auto val) {
    ReferenceHolder<RangeIterator> r(new RangeIterator(start, stop, step, val, xsink), xsink);
    if (*xsink)
        return;
    self->setPrivate(CID_RANGEITERATOR, r.release());
}

//! creates the numerical sequence iterator with the initial arguments
/** @param stop a final value

    @par Example:
    @code{.py}
RangeIterator i(5);
    @endcode

    @see xrange()

    @since %Qore 0.8.11.1
 */
RangeIterator::constructor(int stop) {
    ReferenceHolder<RangeIterator> r(new RangeIterator(0, stop, 1, QoreValue(), xsink), xsink);
    if (*xsink)
        return;
    self->setPrivate(CID_RANGEITERATOR, r.release());
}

//! Creates a copy of the RangeIterator object, iterating the same object as the original and in the same position
/** @par Example:
    @code{.py}
RangeIterator ni = i.copy();
    @endcode
 */
RangeIterator::copy() {
    self->setPrivate(CID_RANGEITERATOR, new RangeIterator(*i));
}

//! This method returns @ref True "True" while there are more numbers to iterate and @ref False "False" when the range has been completely iterated
/** The iterator object should not be used after this method returns @ref False "False".

    @return @ref True "True" and @ref False "False" alternately unless it has no value iterate.

    @par Example:
    @code{.py}
while (i.next()) {
    printf("value: %y\n", i.getValue());
}
    @endcode

    @throw ITERATOR-THREAD-ERROR this exception is thrown if this method is called from any thread other than the thread that created the object
 */
bool RangeIterator::next() {
    if (i->check(xsink))
        return false;
    return i->next();
}

//! returns the current value or throws an \c INVALID-ITERATOR exception if the iterator is invalid
/** @return the current value or throws an \c INVALID-ITERATOR exception if the iterator is invalid

    @par Example:
    @code{.py}
while (i.next()) {
    printf("+ %y\n", i.getValue());
}
    @endcode

    @throw INVALID-ITERATOR the iterator is not pointing at a valid element
    @throw ITERATOR-THREAD-ERROR this exception is thrown if this method is called from any thread other than the thread that created the object

    @note if a value was passed to the constructor, then that value is returned instead of the current range index
 */
auto RangeIterator::getValue() [flags=RET_VALUE_ONLY] {
    if (i->check(xsink))
        return QoreValue();
    return i->getValue(xsink);
}

//! Reset the iterator instance to its initial state (start, stop, and step).
/** Reset the iterator instance to its initial state (start, stop, and step).

   @par Example
   @code{.py}
# raw RangeIterator object usage
RangeIterator r(0, 2);
foreach int i in (r) {
    printf("i=%d\n", i);
    if (i == 1) {
        break;
    }
}
# i=0
# i=1

# show the reset feature; start iterating all over again
r.reset();
foreach int i in (r) {
    printf("reused i=%d\n", i);
}
# reused i=0
# reused i=1
   @endcode

    @throw ITERATOR-THREAD-ERROR this exception is thrown if this method is called from any thread other than the thread that created the object
 */
RangeIterator::reset() {
    if (i->check(xsink))
        return QoreValue();
    i->reset();
}

//! returns @ref True "True" if the iterator is currently pointing at a valid element, @ref False "False" if not
/** @return @ref True "True" if the iterator is currently pointing at a valid element, @ref False "False" if not

    @par Example:
    @code{.py}
if (i.valid()) {
    printf("current value: %y\n", i.getValue());
}
    @endcode
 */
bool RangeIterator::valid() [flags=CONSTANT] {
    return i->valid();
}

/** @defgroup range_functions RangeIterator helper functions
    RangeIterator helper functions allow to use RangeItartor
    in easier usage in loop statements.

    @par Example
    @code{.py}
foreach int i in (xrange(5)) {
    printf("i=%d\n", i);
}
# resulting in:
# i=0
# i=1
# i=2
# i=3
# i=4
	@endcode
*/
///@{
//! Returns a RangeIterator containing an arithmetic progression of integers.
/**
    @par Example:
    @code{.py}
xrange(2, 5); # 2, 3, 4
xrange(2, -2); # 2, 1, 0, -1
xrange(1, 10, 5); # 1, 6
xrange(0, 10, 5); # 0, 5
xrange(-10, 10, 5); # -10, -5, 0, 5
xrange(10, -10, 5); # 10, 5, 0, -5
    @endcode

    @param start the initial value
    @param stop the final value
    @param step the step; the default is 1; must be greater than 0; the function throws a \c RANGE-ERROR exception when this argument is < 1
    @param val an optional value to be returned instead of the default integer value

    @retval Returns a RangeIterator containing an arithmetic progression of integers.

    @throw RANGEITERATOR-ERROR this exception is thrown if \c step < 1

    @see range

    @note the main difference between range() and xrange() is that range returns a real list and xrange() returns a RangeIterator

    @since
    - %Qore 0.8.6
    - %Qore 0.8.11.1 this function takes the optional \a val argument
    - %Qore 0.9.5 does not include the upper limit in the range unless @ref broken-range "%broken-range" is set
*/
RangeIterator xrange(int start, int stop, int step = 1, auto val) [flags=RET_VALUE_ONLY] {
    ReferenceHolder<RangeIterator> r(new RangeIterator(start, stop, step, val, xsink), xsink);
    if (*xsink)
        return QoreValue();

    return new QoreObject(QC_RANGEITERATOR, 0, r.release());
}

//! Returns a RangeIterator containing an arithmetic progression of integers with start = 0 and step = 1.
/**
    This is an overloaded version of xrange(int, int, int) meaning \c xrange(0, \c stop, 1)

    @par Example:
    @code{.py}
xrange(1); # 0
xrange(-3); # 0, -1, -2
    @endcode

    @param stop the final value

    @retval Returns a RangeIterator containing an arithmetic progression of integers with start = 0 and step = 1.

    @see range

    @note the main difference between range() and xrange() is that range() returns a real list and xrange() returns a RangeIterator

    @since
    - %Qore 0.8.6
    - %Qore 0.9.5 does not include the upper limit in the range unless @ref broken-range "%broken-range" is set
 */
RangeIterator xrange(int stop) [flags=CONSTANT] {
    ReferenceHolder<RangeIterator> r(new RangeIterator(0, stop, 1, QoreValue(), xsink), xsink);
    if (*xsink)
        return QoreValue();

    return new QoreObject(QC_RANGEITERATOR, 0, r.release());
}
///@}
