/***************************************************************************
                             qgsspatialindexkdbush.h
                             -----------------
    begin                : July 2018
    copyright            : (C) 2018 by Nyall Dawson
    email                : nyall dot dawson at gmail dot com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#ifndef QGSSPATIALINDEXKDBUSH_H
#define QGSSPATIALINDEXKDBUSH_H

class QgsFeatureIterator;
class QgsFeedback;
class QgsFeatureSource;
class QgsSpatialIndexKDBushPrivate;
class QgsRectangle;
class QgsFeature;

#include <functional>
#include <memory>

#include "qgis_core.h"
#include "qgspointxy.h"
#include "qgsspatialindexkdbushdata.h"

#include <QList>

/**
 * \class QgsSpatialIndexKDBush
 * \ingroup core
 *
 * \brief A very fast static spatial index for 2D points based on a flat KD-tree.
 *
 * Compared to QgsSpatialIndex, this index:
 *
 * - supports single point features only (no multipoints)
 * - is static (features cannot be added or removed from the index after construction)
 * - is much faster!
 * - allows direct retrieval of the original feature's points, without requiring additional feature requests
 * - supports true "distance based" searches, i.e. return all points within a radius from a search point
 *
 * QgsSpatialIndexKDBush objects are implicitly shared and can be inexpensively copied.
 *
 * \see QgsSpatialIndex, which is an general, mutable index for geometry bounding boxes.
 * \since QGIS 3.4
*/
class CORE_EXPORT QgsSpatialIndexKDBush
{
  public:
    /**
     * Constructor - creates KDBush index and bulk loads it with features from the iterator.
     *
     * The optional \a feedback object can be used to allow cancellation of bulk feature loading. Ownership
     * of \a feedback is not transferred, and callers must take care that the lifetime of feedback exceeds
     * that of the spatial index construction.
     *
     * Any non-single point features encountered during iteration will be ignored and not included in the index.
     */
    explicit QgsSpatialIndexKDBush( QgsFeatureIterator &fi, QgsFeedback *feedback = nullptr );

    /**
     * Constructor - creates KDBush index and bulk loads it with features from the source.
     *
     * The optional \a feedback object can be used to allow cancellation of bulk feature loading. Ownership
     * of \a feedback is not transferred, and callers must take care that the lifetime of feedback exceeds
     * that of the spatial index construction.
     *
     * Any non-single point features encountered during iteration will be ignored and not included in the index.
     */
    explicit QgsSpatialIndexKDBush( const QgsFeatureSource &source, QgsFeedback *feedback = nullptr );

#ifndef SIP_RUN

    /**
     * Constructor - creates KDBush index and bulk loads it with features from the source.
     *
     * This constructor allows for a \a callback function to be specified, which is
     * called for each added feature in turn. It allows for bulk spatial index load along with other feature
     * based operations on a single iteration through a feature source. If \a callback returns FALSE, the
     * load and iteration is canceled.
     *
     * The optional \a feedback object can be used to allow cancellation of bulk feature loading. Ownership
     * of \a feedback is not transferred, and callers must take care that the lifetime of feedback exceeds
     * that of the spatial index construction.
     *
     * Any non-single point features encountered during iteration will be ignored and not included in the index.
     *
     * \note Not available in Python bindings
     * \since QGIS 3.22
     */
    explicit QgsSpatialIndexKDBush( QgsFeatureIterator &fi, const std::function< bool( const QgsFeature & ) > &callback, QgsFeedback *feedback = nullptr );
#endif

    /**
     * Constructs an empty index.
     *
     * The index must be manually populated by calls to addFeature() followed by finalize().
     *
     * \since QGIS 4.0
     */
    QgsSpatialIndexKDBush();

    QgsSpatialIndexKDBush( const QgsSpatialIndexKDBush &other );
    QgsSpatialIndexKDBush &operator=( const QgsSpatialIndexKDBush &other );

    ~QgsSpatialIndexKDBush();

    /**
     * Adds a single \a feature to the index.
     *
     * \note A call to finalize() must be made after all features have been added. It is not
     * possible to add features after finalizing the spatial index.
     *
     * \returns TRUE if feature was successfully added to index.
     * \since QGIS 4.0
     */
    bool addFeature( QgsFeatureId id, const QgsPointXY &point );

    /**
     * Finalizes the index after manually adding features.
     *
     * This method must be called after adding all features via addFeature() and before
     * performing any queries on the index.
     *
     * \since QGIS 4.0
     */
    void finalize();

    /**
     * Returns the list of features which fall within the specified \a rectangle.
     */
    QList<QgsSpatialIndexKDBushData> intersects( const QgsRectangle &rectangle ) const;

    /**
     * Calls a \a visitor function for all features which fall within the specified \a rectangle.
     *
     * \note Not available in Python bindings
     */
    void intersects( const QgsRectangle &rectangle, const std::function<void( QgsSpatialIndexKDBushData )> &visitor ) const SIP_SKIP;

    /**
     * Returns the list of features which are within the given search \a radius
     * of \a point.
     */
    QList<QgsSpatialIndexKDBushData> within( const QgsPointXY &point, double radius ) const;

    /**
     * Calls a \a visitor function for all features which are within the given search \a radius
     * of \a point.
     *
     * \note Not available in Python bindings
     */
    void within( const QgsPointXY &point, double radius, const std::function<void( QgsSpatialIndexKDBushData )> &visitor ) SIP_SKIP;

    /**
     * Returns the size of the index, i.e. the number of points contained within the index.
     */
    qgssize size() const;

  private:
    //! Implicitly shared data pointer
    QgsSpatialIndexKDBushPrivate *d = nullptr;

    friend class TestQgsSpatialIndexKdBush;
};

#endif // QGSSPATIALINDEXKDBUSH_H
