/***************************************************************************
    qgsrasterface.h - Internal raster processing modules interface
     --------------------------------------
    Date                 : Jun 21, 2012
    Copyright            : (C) 2012 by Radim Blazek
    email                : radim dot blazek 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 QGSRASTERINTERFACE_H
#define QGSRASTERINTERFACE_H

#include <limits>

#include "qgis.h"
#include "qgis_core.h"
#include "qgis_sip.h"
#include "qgsfeedback.h"
#include "qgsrasterbandstats.h"
#include "qgsrasterblock.h"
#include "qgsrasterhistogram.h"
#include "qgsrectangle.h"
#include "qgsrendercontext.h"

#include <QCoreApplication>
#include <QImage>
#include <QString>

using namespace Qt::StringLiterals;

/**
 * \ingroup core
 * \brief Feedback object tailored for raster block reading.
 *
 */
class CORE_EXPORT QgsRasterBlockFeedback : public QgsFeedback
{
    Q_OBJECT

  public:
    //! Construct a new raster block feedback object
    QgsRasterBlockFeedback( QObject *parent = nullptr )
      : QgsFeedback( parent )
    {}

    /**
     * May be emitted by raster data provider to indicate that some partial data are available
     * and a new preview image may be produced
     */
    virtual void onNewData() {}

    /**
     * Whether the raster provider should return only data that are already available
     * without waiting for full result. By default this flag is not enabled.
     * \see setPreviewOnly()
     */
    bool isPreviewOnly() const { return mPreviewOnly; }

    /**
     * set flag whether the block request is for preview purposes only
     * \see isPreviewOnly()
     */
    void setPreviewOnly( bool preview ) { mPreviewOnly = preview; }

    /**
     * Whether our painter is drawing to a temporary image used just by this layer
     * \see setRenderPartialOutput()
     */
    bool renderPartialOutput() const { return mRenderPartialOutput; }

    /**
     * Set whether our painter is drawing to a temporary image used just by this layer
     * \see renderPartialOutput()
     */
    void setRenderPartialOutput( bool enable ) { mRenderPartialOutput = enable; }

    /**
     * Appends an error message to the stored list of errors. Should be called
     * whenever an error is encountered while retrieving a raster block.
     *
     * \see errors()
     * \since QGIS 3.8.0
     */
    void appendError( const QString &error ) { mErrors.append( error ); }

    /**
     * Returns a list of any errors encountered while retrieving the raster block.
     *
     * \see appendError()
     * \since QGIS 3.8.0
     */
    QStringList errors() const { return mErrors; }

    /**
     * Returns the render context of the associated block reading
     *
     * \see setRenderContext()
     * \since QGIS 3.24.0
     */
    QgsRenderContext renderContext() const;

    /**
     * Sets the render context of the associated block reading
     *
     * \see renderContext()
     * \since QGIS 3.24.0
     */
    void setRenderContext( const QgsRenderContext &renderContext );

  private:
    /**
     * Whether the raster provider should return only data that are already available
     * without waiting for full result
     */
    bool mPreviewOnly = false;

    //! Whether our painter is drawing to a temporary image used just by this layer
    bool mRenderPartialOutput = false;

    //! List of errors encountered while retrieving block
    QStringList mErrors;

    QgsRenderContext mRenderContext;
};


/**
 * \ingroup core
 * \brief Base class for processing filters like renderers, reprojector, resampler etc.
 */
class CORE_EXPORT QgsRasterInterface
{
    // QgsRasterInterface subclasses
    //SIP_TYPEHEADER_INCLUDE( "qgsbrightnesscontrastfilter.h" );
    //SIP_TYPEHEADER_INCLUDE( "qgshuesaturationfilter.h" );
    //SIP_TYPEHEADER_INCLUDE( "qgsrasterdataprovider.h" );
    //SIP_TYPEHEADER_INCLUDE( "qgsrasternuller.h" );
    //SIP_TYPEHEADER_INCLUDE( "qgsrasterprojector.h" );
    //SIP_TYPEHEADER_INCLUDE( "qgsrasterrenderer.h" );
    //SIP_TYPEHEADER_INCLUDE( "qgsrasterresamplefilter.h" );
    // QgsRasterRenderer subclasses
    //SIP_TYPEHEADER_INCLUDE( "qgshillshaderenderer.h" );
    //SIP_TYPEHEADER_INCLUDE( "qgsmultibandcolorrenderer.h" );
    //SIP_TYPEHEADER_INCLUDE( "qgspalettedrasterrenderer.h" );
    //SIP_TYPEHEADER_INCLUDE( "qgssinglebandcolordatarenderer.h" );
    //SIP_TYPEHEADER_INCLUDE( "qgssinglebandgrayrenderer.h" );
    //SIP_TYPEHEADER_INCLUDE( "qgssinglebandpseudocolorrenderer.h" );
    //SIP_TYPEHEADER_INCLUDE( "qgsrastersinglecolorrenderer.h" );
    //SIP_TYPEHEADER_INCLUDE( "qgsrastercontourrenderer.h" );


#ifdef SIP_RUN
    SIP_CONVERT_TO_SUBCLASS_CODE
    if ( dynamic_cast<QgsBrightnessContrastFilter *>( sipCpp ) )
      sipType = sipType_QgsBrightnessContrastFilter;
    else if ( dynamic_cast<QgsHueSaturationFilter *>( sipCpp ) )
      sipType = sipType_QgsHueSaturationFilter;
    else if ( dynamic_cast<QgsRasterDataProvider *>( sipCpp ) )
    {
      sipType = sipType_QgsRasterDataProvider;
      // use static cast because QgsRasterDataProvider has multiple inheritance
      // and we would end up with bad pointer otherwise!
      *sipCppRet = static_cast<QgsRasterDataProvider *>( sipCpp );
    }
    else if ( dynamic_cast<QgsRasterNuller *>( sipCpp ) )
      sipType = sipType_QgsRasterNuller;
    else if ( dynamic_cast<QgsRasterProjector *>( sipCpp ) )
      sipType = sipType_QgsRasterProjector;
    else if ( dynamic_cast<QgsRasterRenderer *>( sipCpp ) )
    {
      if ( dynamic_cast<QgsHillshadeRenderer *>( sipCpp ) )
        sipType = sipType_QgsHillshadeRenderer;
      else if ( dynamic_cast<QgsMultiBandColorRenderer *>( sipCpp ) )
        sipType = sipType_QgsMultiBandColorRenderer;
      else if ( dynamic_cast<QgsPalettedRasterRenderer *>( sipCpp ) )
        sipType = sipType_QgsPalettedRasterRenderer;
      else if ( dynamic_cast<QgsSingleBandColorDataRenderer *>( sipCpp ) )
        sipType = sipType_QgsSingleBandColorDataRenderer;
      else if ( dynamic_cast<QgsSingleBandGrayRenderer *>( sipCpp ) )
        sipType = sipType_QgsSingleBandGrayRenderer;
      else if ( dynamic_cast<QgsSingleBandPseudoColorRenderer *>( sipCpp ) )
        sipType = sipType_QgsSingleBandPseudoColorRenderer;
      else if ( dynamic_cast<QgsRasterSingleColorRenderer *>( sipCpp ) )
        sipType = sipType_QgsRasterSingleColorRenderer;
      else if ( dynamic_cast<QgsRasterContourRenderer *>( sipCpp ) )
        sipType = sipType_QgsRasterContourRenderer;
      else
        sipType = sipType_QgsRasterRenderer;
    }
    else if ( dynamic_cast<QgsRasterResampleFilter *>( sipCpp ) )
      sipType = sipType_QgsRasterResampleFilter;
    else
      sipType = 0;
  SIP_END
#endif

  // clang-format off
  Q_DECLARE_TR_FUNCTIONS( QgsRasterInterface )

  public:
    // clang-format on

    QgsRasterInterface( QgsRasterInterface *input = nullptr );

    virtual ~QgsRasterInterface() = default;

    //! Clone itself, create deep copy
    virtual QgsRasterInterface *clone() const = 0 SIP_FACTORY;

    // TODO QGIS 5.0 -- rename to interfaceCapabilities, to avoid confusion with QgsRasterDataProvider::providerCapabilities
    // (which inherits this class)

    /**
     * Returns the capabilities supported by the interface.
     */
    virtual Qgis::RasterInterfaceCapabilities capabilities() const;

    /**
     * Returns the raster interface capabilities in friendly format.
     *
     * \deprecated QGIS 3.40. Will be removed in QGIS 5.0.
     */
    Q_DECL_DEPRECATED QString capabilitiesString() const SIP_DEPRECATED;

    //! Returns data type for the band specified by number
    virtual Qgis::DataType dataType( int bandNo ) const = 0;

    /**
     * Returns source data type for the band specified by number,
     *  source data type may be shorter than dataType
    */
    virtual Qgis::DataType sourceDataType( int bandNo ) const { return mInput ? mInput->sourceDataType( bandNo ) : Qgis::DataType::UnknownDataType; }

    /**
     * Gets the extent of the interface.
     * \returns QgsRectangle containing the extent of the layer
     */
    virtual QgsRectangle extent() const { return mInput ? mInput->extent() : QgsRectangle(); }

    /**
     * Returns the size (in bytes) for the data type for the specified band.
     */
    int dataTypeSize( int bandNo ) const { return QgsRasterBlock::typeSize( dataType( bandNo ) ); }

    //! Gets number of bands
    virtual int bandCount() const = 0;

    //! Gets block size
    virtual int xBlockSize() const { return mInput ? mInput->xBlockSize() : 0; }
    virtual int yBlockSize() const { return mInput ? mInput->yBlockSize() : 0; }

    //! Gets raster size
    virtual int xSize() const { return mInput ? mInput->xSize() : 0; }
    virtual int ySize() const { return mInput ? mInput->ySize() : 0; }

    //! \brief helper function to create zero padded band names
    virtual QString generateBandName( int bandNumber ) const;

    /**
     * Returns the name of the color interpretation for the specified \a bandNumber.
     * It is translated since QGIS 3.40
     *
     * \since QGIS 3.18
     */
    virtual QString colorInterpretationName( int bandNumber ) const;

    /**
     * Generates a friendly, descriptive name for the specified \a bandNumber.
     *
     * \since QGIS 3.18
     */
    QString displayBandName( int bandNumber ) const;

    /**
     * Read block of data using given extent and size.
     *  Returns pointer to data.
     *  Caller is responsible to free the memory returned.
     * \param bandNo band number
     * \param extent extent of block
     * \param width pixel width of block
     * \param height pixel height of block
     * \param feedback optional raster feedback object for cancellation/preview. Added in QGIS 3.0.
     */
    virtual QgsRasterBlock *block( int bandNo, const QgsRectangle &extent, int width, int height, QgsRasterBlockFeedback *feedback = nullptr ) = 0 SIP_FACTORY;

    /**
     * Set input.
      * Returns TRUE if set correctly, FALSE if cannot use that input
    */
    virtual bool setInput( QgsRasterInterface *input )
    {
      mInput = input;
      return true;
    }

    //! Current input
    virtual QgsRasterInterface *input() const { return mInput; }

    //! Returns whether the interface is on or off
    virtual bool on() const { return mOn; }

    //! Sets whether the interface is on or off
    virtual void setOn( bool on ) { mOn = on; }

    /**
     * Gets source / raw input, the first in pipe, usually provider.
     *  It may be used to get info about original data, e.g. resolution to decide
     *  resampling etc.
     * \note not available in Python bindings.
     */
    virtual const QgsRasterInterface *sourceInput() const SIP_SKIP
    {
      QgsDebugMsgLevel( u"Entered"_s, 4 );
      return mInput ? mInput->sourceInput() : this;
    }

    /**
     * Gets source / raw input, the first in pipe, usually provider.
     *  It may be used to get info about original data, e.g. resolution to decide
     *  resampling etc.
     */
    virtual QgsRasterInterface *sourceInput()
    {
      QgsDebugMsgLevel( u"Entered"_s, 4 );
      return mInput ? mInput->sourceInput() : this;
    }

    // clang-format off
    /**
     * Returns the band statistics.
     * \param bandNo The band (number).
     * \param stats Requested statistics
     * \param extent Extent used to calc statistics, if empty, whole raster extent is used.
     * \param sampleSize Approximate number of cells in sample. If 0, all cells (whole raster will be used). If raster does not have exact size (WCS without exact size for example), provider decides size of sample.
     * \param feedback optional feedback object
     * \deprecated QGIS 3.40. Use Qgis::RasterBandStatistic instead of int for \a stats argument.
     */
    Q_DECL_DEPRECATED QgsRasterBandStats bandStatistics( int bandNo, int stats, const QgsRectangle &extent = QgsRectangle(), int sampleSize = 0, QgsRasterBlockFeedback *feedback = nullptr ) SIP_DEPRECATED;
    // clang-format on

    /**
     * Returns the band statistics.
     * \param bandNo The band (number).
     * \param stats Requested statistics
     * \param extent Extent used to calc statistics, if empty, whole raster extent is used.
     * \param sampleSize Approximate number of cells in sample. If 0, all cells (whole raster will be used). If raster does not have exact size (WCS without exact size for example), provider decides size of sample.
     * \param feedback optional feedback object
     */
    virtual QgsRasterBandStats bandStatistics(
      int bandNo, Qgis::RasterBandStatistics stats = Qgis::RasterBandStatistic::All, const QgsRectangle &extent = QgsRectangle(), int sampleSize = 0, QgsRasterBlockFeedback *feedback = nullptr
    );

    /**
     * \brief Returns TRUE if histogram is available (cached, already calculated).
     * The parameters are the same as in bandStatistics()
     * \returns TRUE if statistics are available (ready to use)
     * \deprecated QGIS 3.40. Use Qgis::RasterBandStatistic instead of int for \a stats argument.
     */
    Q_DECL_DEPRECATED bool hasStatistics( int bandNo, int stats, const QgsRectangle &extent = QgsRectangle(), int sampleSize = 0 ) SIP_DEPRECATED;

    /**
     * \brief Returns TRUE if histogram is available (cached, already calculated).
     * The parameters are the same as in bandStatistics()
     * \returns TRUE if statistics are available (ready to use)
     */
    virtual bool hasStatistics( int bandNo, Qgis::RasterBandStatistics stats = Qgis::RasterBandStatistic::All, const QgsRectangle &extent = QgsRectangle(), int sampleSize = 0 );


    // clang-format off
    /**
     * Returns a band histogram. Histograms are cached in providers.
     * \param bandNo The band (number).
     * \param binCount Number of bins (intervals,buckets). If 0, the number of bins is decided automatically according to data type, raster size etc.
     * \param minimum Minimum value, if NaN (None for Python), raster minimum value will be used.
     * \param maximum Maximum value, if NaN (None for Python), raster maximum value will be used.
     * \param extent Extent used to calc histogram, if empty, whole raster extent is used.
     * \param sampleSize Approximate number of cells in sample. If 0, all cells (whole raster will be used). If raster does not have exact size (WCS without exact size for example), provider decides size of sample.
     * \param includeOutOfRange include out of range values
     * \param feedback optional feedback object
     * \returns Vector of non NULL cell counts for each bin.
     * \note binCount, minimum and maximum not optional in Python bindings
     */
#ifndef SIP_RUN
    virtual QgsRasterHistogram histogram( int bandNo,
                                          int binCount = 0,
                                          double minimum = std::numeric_limits<double>::quiet_NaN(),
                                          double maximum = std::numeric_limits<double>::quiet_NaN(),
                                          const QgsRectangle &extent = QgsRectangle(),
                                          int sampleSize = 0,
                                          bool includeOutOfRange = false,
                                          QgsRasterBlockFeedback *feedback = nullptr );
#else
// clang-format off
    virtual QgsRasterHistogram histogram( int bandNo,
                                          int binCount = 0,
                                          SIP_PYOBJECT minimum = Py_None,
                                          SIP_PYOBJECT maximum = Py_None,
                                          const QgsRectangle &extent = QgsRectangle(),
                                          int sampleSize = 0,
                                          bool includeOutOfRange = false,
                                          QgsRasterBlockFeedback *feedback = nullptr )
    [QgsRasterHistogram( int bandNo,
                         int binCount = 0,
                         double minimum = 0.0,
                         double maximum = 0.0,
                         const QgsRectangle &extent = QgsRectangle(),
                         int sampleSize = 0,
                         bool includeOutOfRange = false,
                         QgsRasterBlockFeedback *feedback = nullptr )];
    % MethodCode
    double minimum;
    double maximum;
    if ( a2 == Py_None )
    {
      minimum = std::numeric_limits<double>::quiet_NaN();
    }
    else
    {
      minimum = PyFloat_AsDouble( a2 );
    }

    if ( a3 == Py_None )
    {
      maximum = std::numeric_limits<double>::quiet_NaN();
    }
    else
    {
      maximum = PyFloat_AsDouble( a3 );
    }

    QgsRasterHistogram *h = new QgsRasterHistogram( sipCpp->histogram( a0, a1, minimum, maximum, *a4, a5, a6, a7 ) );
    return sipConvertFromType( h, sipType_QgsRasterHistogram, Py_None );
    % End
// clang-format on
#endif
    // clang-format on


    // clang-format off
    /**
     * \brief Returns TRUE if histogram is available (cached, already calculated)
     * \note the parameters are the same as in \see histogram()
     */
#ifndef SIP_RUN
    virtual bool hasHistogram( int bandNo,
                               int binCount,
                               double minimum = std::numeric_limits<double>::quiet_NaN(),
                               double maximum = std::numeric_limits<double>::quiet_NaN(),
                               const QgsRectangle &extent = QgsRectangle(),
                               int sampleSize = 0,
                               bool includeOutOfRange = false );
#else
// clang-format off
    virtual bool hasHistogram( int bandNo,
                               int binCount,
                               SIP_PYOBJECT minimum = Py_None,
                               SIP_PYOBJECT maximum = Py_None,
                               const QgsRectangle &extent = QgsRectangle(),
                               int sampleSize = 0,
                               bool includeOutOfRange = false )
    [bool( int bandNo,
           int binCount,
           double minimum = 0.0,
           double maximum = 0.0,
           const QgsRectangle &extent = QgsRectangle(),
           int sampleSize = 0,
           bool includeOutOfRange = false )];
    % MethodCode
    double minimum;
    double maximum;
    if ( a2 == Py_None )
    {
      minimum = std::numeric_limits<double>::quiet_NaN();
    }
    else
    {
      minimum = PyFloat_AsDouble( a2 );
    }

    if ( a3 == Py_None )
    {
      maximum = std::numeric_limits<double>::quiet_NaN();
    }
    else
    {
      maximum = PyFloat_AsDouble( a3 );
    }

    sipRes = sipCpp->hasHistogram( a0, a1, minimum, maximum, *a4, a5, a6 );
    % End
// clang-format on
#endif
    // clang-format on


    /**
     * \brief Find values for cumulative pixel count cut.
     * \param bandNo The band (number).
     * \param lowerCount The lower count as fraction of 1, e.g. 0.02 = 2%
     * \param upperCount The upper count as fraction of 1, e.g. 0.98 = 98%
     * \param lowerValue Location into which the lower value will be set.
     * \param upperValue  Location into which the upper value will be set.
     * \param extent Extent used to calc histogram, if empty, whole raster extent is used.
     * \param sampleSize Approximate number of cells in sample. If 0, all cells (whole raster will be used). If raster does not have exact size (WCS without exact size for example), provider decides size of sample.
     */
    virtual void cumulativeCut( int bandNo, double lowerCount, double upperCount, double &lowerValue, double &upperValue, const QgsRectangle &extent = QgsRectangle(), int sampleSize = 0 );

    //! Write base class members to xml.
    virtual void writeXml( QDomDocument &doc, QDomElement &parentElem ) const
    {
      Q_UNUSED( doc )
      Q_UNUSED( parentElem );
    }
    //! Sets base class members from xml. Usually called from create() methods of subclasses
    virtual void readXml( const QDomElement &filterElem ) { Q_UNUSED( filterElem ) }

  protected:
    // QgsRasterInterface used as input
    QgsRasterInterface *mInput = nullptr;

    //! \brief List  of cached statistics, all bands mixed
    QList<QgsRasterBandStats> mStatistics;

    //! \brief List  of cached histograms, all bands mixed
    QList<QgsRasterHistogram> mHistograms;

    // On/off state, if off, it does not do anything, replicates input
    bool mOn = true;

    // clang-format off
    /**
     * Fill in histogram defaults if not specified
     * \note the parameters are the same as in \see histogram()
     */
#ifndef SIP_RUN
    void initHistogram( QgsRasterHistogram &histogram,
                        int bandNo,
                        int binCount,
                        double minimum = std::numeric_limits<double>::quiet_NaN(),
                        double maximum = std::numeric_limits<double>::quiet_NaN(),
                        const QgsRectangle &boundingBox = QgsRectangle(),
                        int sampleSize = 0,
                        bool includeOutOfRange = false );
#else
// clang-format off
    void initHistogram( QgsRasterHistogram &histogram,
                        int bandNo,
                        int binCount,
                        SIP_PYOBJECT minimum = Py_None,
                        SIP_PYOBJECT maximum = Py_None,
                        const QgsRectangle &boundingBox = QgsRectangle(),
                        int sampleSize = 0,
                        bool includeOutOfRange = false )
    [void ( QgsRasterHistogram & histogram,
            int bandNo,
            int binCount,
            double minimum = 0.0,
            double maximum = 0.0,
            const QgsRectangle &boundingBox = QgsRectangle(),
            int sampleSize = 0,
            bool includeOutOfRange = false )];
    % MethodCode
    double minimum;
    double maximum;
    if ( a3 == Py_None )
    {
      minimum = std::numeric_limits<double>::quiet_NaN();
    }
    else
    {
      minimum = PyFloat_AsDouble( a3 );
    }

    if ( a4 == Py_None )
    {
      maximum = std::numeric_limits<double>::quiet_NaN();
    }
    else
    {
      maximum = PyFloat_AsDouble( a4 );
    }

#if defined(SIP_PROTECTED_IS_PUBLIC) || (SIP_VERSION >= 0x050000 && !defined(_MSC_VER))
    sipCpp->initHistogram( *a0, a1, a2, minimum, maximum, *a5, a6, a7 );
#else
    sipCpp->sipProtect_initHistogram( *a0, a1, a2, minimum, maximum, *a5, a6, a7 );
#endif
    % End
// clang-format on
#endif
    // clang-format on

    /**
     * Fill in statistics defaults if not specified
     * \deprecated QGIS 3.40. Use Qgis::RasterBandStatistic instead of int for \a stats argument.
     */
    Q_DECL_DEPRECATED void initStatistics( QgsRasterBandStats &statistics, int bandNo, int stats, const QgsRectangle &boundingBox = QgsRectangle(), int binCount = 0 ) const SIP_DEPRECATED;

    //! Fill in statistics defaults if not specified
    void initStatistics(
      QgsRasterBandStats &statistics, int bandNo, Qgis::RasterBandStatistics stats = Qgis::RasterBandStatistic::All, const QgsRectangle &boundingBox = QgsRectangle(), int binCount = 0
    ) const;

  private:
#ifdef SIP_RUN
    QgsRasterInterface( const QgsRasterInterface & );
    QgsRasterInterface &operator=( const QgsRasterInterface & );
#endif

    Q_DISABLE_COPY( QgsRasterInterface ) // there is clone() for copying
};

#endif
