/******************************************************************************
 *
 * Project:  GDAL Core
 * Purpose:  Implementation of a dataset overview warping class
 * Author:   Even Rouault, <even dot rouault at spatialys dot com>
 *
 ******************************************************************************
 * Copyright (c) 2014, Even Rouault, <even dot rouault at spatialys dot com>
 *
 * 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.
 ****************************************************************************/

#include "cpl_port.h"
#include "gdal_priv.h"

#include <cstring>

#include "cpl_conv.h"
#include "cpl_error.h"
#include "cpl_progress.h"
#include "cpl_string.h"
#include "gdal.h"
#include "gdal_mdreader.h"
#include "gdal_proxy.h"

/** In GDAL, GDALRasterBand::GetOverview() returns a stand-alone band, that may
    have no parent dataset. This can be inconvenient in certain contexts, where
    cross-band processing must be done, or when API expect a fully fledged
    dataset.  Furthermore even if overview band has a container dataset, that
    one often fails to declare its projection, geotransform, etc... which make
    it somehow useless. GDALOverviewDataset remedies to those deficiencies.
*/

class GDALOverviewBand;

/* ******************************************************************** */
/*                          GDALOverviewDataset                         */
/* ******************************************************************** */

class GDALOverviewDataset final : public GDALDataset
{
  private:
    friend class GDALOverviewBand;

    GDALDataset *poMainDS = nullptr;

    GDALDataset *poOvrDS = nullptr;  // Will be often NULL.
    int nOvrLevel = 0;
    bool bThisLevelOnly = false;

    int nGCPCount = 0;
    GDAL_GCP *pasGCPList = nullptr;
    char **papszMD_RPC = nullptr;
    char **papszMD_GEOLOCATION = nullptr;
    GDALOverviewBand *m_poMaskBand = nullptr;

    static void Rescale(char **&papszMD, const char *pszItem, double dfRatio,
                        double dfDefaultVal);

  protected:
    CPLErr IRasterIO(GDALRWFlag, int, int, int, int, void *, int, int,
                     GDALDataType, int, int *, GSpacing, GSpacing, GSpacing,
                     GDALRasterIOExtraArg *psExtraArg) override;

  public:
    GDALOverviewDataset(GDALDataset *poMainDS, int nOvrLevel,
                        bool bThisLevelOnly);
    ~GDALOverviewDataset() override;

    const OGRSpatialReference *GetSpatialRef() const override;
    CPLErr GetGeoTransform(double *) override;

    int GetGCPCount() override;
    const OGRSpatialReference *GetGCPSpatialRef() const override;
    const GDAL_GCP *GetGCPs() override;

    char **GetMetadata(const char *pszDomain = "") override;
    const char *GetMetadataItem(const char *pszName,
                                const char *pszDomain = "") override;

    int CloseDependentDatasets() override;

  private:
    CPL_DISALLOW_COPY_ASSIGN(GDALOverviewDataset)
};

/* ******************************************************************** */
/*                           GDALOverviewBand                           */
/* ******************************************************************** */

class GDALOverviewBand final : public GDALProxyRasterBand
{
  protected:
    friend class GDALOverviewDataset;

    GDALRasterBand *poUnderlyingBand = nullptr;
    GDALRasterBand *RefUnderlyingRasterBand() const override;

    CPLErr IRasterIO(GDALRWFlag, int, int, int, int, void *, int, int,
                     GDALDataType, GSpacing, GSpacing,
                     GDALRasterIOExtraArg *psExtraArg) override;

  public:
    GDALOverviewBand(GDALOverviewDataset *poDS, int nBand);
    ~GDALOverviewBand() override;

    CPLErr FlushCache(bool bAtClosing) override;

    int GetOverviewCount() override;
    GDALRasterBand *GetOverview(int) override;

    int GetMaskFlags() override;
    GDALRasterBand *GetMaskBand() override;

  private:
    CPL_DISALLOW_COPY_ASSIGN(GDALOverviewBand)
};

/************************************************************************/
/*                           GetOverviewEx()                            */
/************************************************************************/

static GDALRasterBand *GetOverviewEx(GDALRasterBand *poBand, int nLevel)
{
    if (nLevel == -1)
        return poBand;
    return poBand->GetOverview(nLevel);
}

/************************************************************************/
/*                       GDALCreateOverviewDataset()                    */
/************************************************************************/

// Takes a reference on poMainDS in case of success.
// nOvrLevel=-1 means the full resolution dataset (only useful if
// bThisLevelOnly = false to expose a dataset without its overviews)
GDALDataset *GDALCreateOverviewDataset(GDALDataset *poMainDS, int nOvrLevel,
                                       bool bThisLevelOnly)
{
    // Sanity checks.
    const int nBands = poMainDS->GetRasterCount();
    if (nBands == 0)
        return nullptr;

    auto poFirstBand = GetOverviewEx(poMainDS->GetRasterBand(1), nOvrLevel);
    for (int i = 1; i <= nBands; ++i)
    {
        auto poBand = GetOverviewEx(poMainDS->GetRasterBand(i), nOvrLevel);
        if (poBand == nullptr)
        {
            return nullptr;
        }
        if (poBand->GetXSize() != poFirstBand->GetXSize() ||
            poBand->GetYSize() != poFirstBand->GetYSize())
        {
            return nullptr;
        }
    }

    return new GDALOverviewDataset(poMainDS, nOvrLevel, bThisLevelOnly);
}

/************************************************************************/
/*                        GDALOverviewDataset()                         */
/************************************************************************/

GDALOverviewDataset::GDALOverviewDataset(GDALDataset *poMainDSIn,
                                         int nOvrLevelIn, bool bThisLevelOnlyIn)
    : poMainDS(poMainDSIn), nOvrLevel(nOvrLevelIn),
      bThisLevelOnly(bThisLevelOnlyIn)
{
    poMainDSIn->Reference();
    eAccess = poMainDS->GetAccess();
    auto poFirstBand = GetOverviewEx(poMainDS->GetRasterBand(1), nOvrLevel);
    nRasterXSize = poFirstBand->GetXSize();
    nRasterYSize = poFirstBand->GetYSize();
    poOvrDS = poFirstBand->GetDataset();
    if (nOvrLevel != -1 && poOvrDS != nullptr && poOvrDS == poMainDS)
    {
        CPLDebug("GDAL", "Dataset of overview is the same as the main band. "
                         "This is not expected");
        poOvrDS = nullptr;
    }
    nBands = poMainDS->GetRasterCount();
    for (int i = 0; i < nBands; ++i)
    {
        SetBand(i + 1, new GDALOverviewBand(this, i + 1));
    }

    if (poFirstBand->GetMaskFlags() == GMF_PER_DATASET)
    {
        auto poOvrMaskBand = poFirstBand->GetMaskBand();
        if (poOvrMaskBand && poOvrMaskBand->GetXSize() == nRasterXSize &&
            poOvrMaskBand->GetYSize() == nRasterYSize)
        {
            m_poMaskBand = new GDALOverviewBand(this, 0);
        }
    }

    // We create a fake driver that has the same name as the original
    // one, but we cannot use the real driver object, so that code
    // doesn't try to cast the GDALOverviewDataset* as a native dataset
    // object.
    if (poMainDS->GetDriver() != nullptr)
    {
        poDriver = new GDALDriver();
        poDriver->SetDescription(poMainDS->GetDriver()->GetDescription());
        poDriver->SetMetadata(poMainDS->GetDriver()->GetMetadata());
    }

    if (poOvrDS)
        poOvrDS->SetEnableOverviews(false);

    SetDescription(poMainDS->GetDescription());

    CPLDebug("GDAL", "GDALOverviewDataset(%s, this=%p) creation.",
             poMainDS->GetDescription(), this);

    papszOpenOptions = CSLDuplicate(poMainDS->GetOpenOptions());
    // Add OVERVIEW_LEVEL if not called from GDALOpenEx(), but directly.
    papszOpenOptions = CSLSetNameValue(
        papszOpenOptions, "OVERVIEW_LEVEL",
        nOvrLevel == -1
            ? "NONE"
            : CPLSPrintf("%d%s", nOvrLevel, bThisLevelOnly ? " only" : ""));
}

/************************************************************************/
/*                       ~GDALOverviewDataset()                         */
/************************************************************************/

GDALOverviewDataset::~GDALOverviewDataset()
{
    GDALOverviewDataset::FlushCache(true);

    GDALOverviewDataset::CloseDependentDatasets();

    if (nGCPCount > 0)
    {
        GDALDeinitGCPs(nGCPCount, pasGCPList);
        CPLFree(pasGCPList);
    }
    CSLDestroy(papszMD_RPC);

    CSLDestroy(papszMD_GEOLOCATION);

    delete poDriver;
}

/************************************************************************/
/*                      CloseDependentDatasets()                        */
/************************************************************************/

int GDALOverviewDataset::CloseDependentDatasets()
{
    bool bRet = false;

    if (poOvrDS)
        poOvrDS->SetEnableOverviews(true);

    if (poMainDS)
    {
        for (int i = 0; i < nBands; ++i)
        {
            GDALOverviewBand *const band =
                cpl::down_cast<GDALOverviewBand *>(papoBands[i]);
            band->poUnderlyingBand = nullptr;
        }
        if (poMainDS->ReleaseRef())
            bRet = true;
        poMainDS = nullptr;
    }

    if (m_poMaskBand)
    {
        m_poMaskBand->poUnderlyingBand = nullptr;
        delete m_poMaskBand;
        m_poMaskBand = nullptr;
    }

    return bRet;
}

/************************************************************************/
/*                             IRasterIO()                              */
/*                                                                      */
/*      The default implementation of IRasterIO() is to pass the        */
/*      request off to each band objects rasterio methods with          */
/*      appropriate arguments.                                          */
/************************************************************************/

CPLErr GDALOverviewDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
                                      int nXSize, int nYSize, void *pData,
                                      int nBufXSize, int nBufYSize,
                                      GDALDataType eBufType, int nBandCount,
                                      int *panBandMap, GSpacing nPixelSpace,
                                      GSpacing nLineSpace, GSpacing nBandSpace,
                                      GDALRasterIOExtraArg *psExtraArg)

{
    // Try to pass the request to the most appropriate overview dataset.
    if (nBufXSize < nXSize && nBufYSize < nYSize)
    {
        int bTried = FALSE;
        const CPLErr eErr = TryOverviewRasterIO(
            eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
            eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace,
            nBandSpace, psExtraArg, &bTried);
        if (bTried)
            return eErr;
    }

    // In case the overview bands are really linked to a dataset, then issue
    // the request to that dataset.
    if (nOvrLevel != -1 && poOvrDS != nullptr)
    {
        return poOvrDS->RasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
                                 nBufXSize, nBufYSize, eBufType, nBandCount,
                                 panBandMap, nPixelSpace, nLineSpace,
                                 nBandSpace, psExtraArg);
    }

    GDALProgressFunc pfnProgressGlobal = psExtraArg->pfnProgress;
    void *pProgressDataGlobal = psExtraArg->pProgressData;
    CPLErr eErr = CE_None;

    for (int iBandIndex = 0; iBandIndex < nBandCount && eErr == CE_None;
         ++iBandIndex)
    {
        GDALOverviewBand *poBand = cpl::down_cast<GDALOverviewBand *>(
            GetRasterBand(panBandMap[iBandIndex]));
        GByte *pabyBandData =
            static_cast<GByte *>(pData) + iBandIndex * nBandSpace;

        psExtraArg->pfnProgress = GDALScaledProgress;
        psExtraArg->pProgressData = GDALCreateScaledProgress(
            1.0 * iBandIndex / nBandCount, 1.0 * (iBandIndex + 1) / nBandCount,
            pfnProgressGlobal, pProgressDataGlobal);

        eErr = poBand->IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
                                 pabyBandData, nBufXSize, nBufYSize, eBufType,
                                 nPixelSpace, nLineSpace, psExtraArg);

        GDALDestroyScaledProgress(psExtraArg->pProgressData);
    }

    psExtraArg->pfnProgress = pfnProgressGlobal;
    psExtraArg->pProgressData = pProgressDataGlobal;

    return eErr;
}

/************************************************************************/
/*                           GetSpatialRef()                            */
/************************************************************************/

const OGRSpatialReference *GDALOverviewDataset::GetSpatialRef() const

{
    return poMainDS->GetSpatialRef();
}

/************************************************************************/
/*                          GetGeoTransform()                           */
/************************************************************************/

CPLErr GDALOverviewDataset::GetGeoTransform(double *padfTransform)

{
    double adfGeoTransform[6] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
    if (poMainDS->GetGeoTransform(adfGeoTransform) != CE_None)
        return CE_Failure;

    adfGeoTransform[1] *=
        static_cast<double>(poMainDS->GetRasterXSize()) / nRasterXSize;
    adfGeoTransform[2] *=
        static_cast<double>(poMainDS->GetRasterYSize()) / nRasterYSize;
    adfGeoTransform[4] *=
        static_cast<double>(poMainDS->GetRasterXSize()) / nRasterXSize;
    adfGeoTransform[5] *=
        static_cast<double>(poMainDS->GetRasterYSize()) / nRasterYSize;

    memcpy(padfTransform, adfGeoTransform, sizeof(double) * 6);

    return CE_None;
}

/************************************************************************/
/*                            GetGCPCount()                             */
/************************************************************************/

int GDALOverviewDataset::GetGCPCount()

{
    return poMainDS->GetGCPCount();
}

/************************************************************************/
/*                          GetGCPSpatialRef()                          */
/************************************************************************/

const OGRSpatialReference *GDALOverviewDataset::GetGCPSpatialRef() const

{
    return poMainDS->GetGCPSpatialRef();
}

/************************************************************************/
/*                               GetGCPs()                              */
/************************************************************************/

const GDAL_GCP *GDALOverviewDataset::GetGCPs()

{
    if (pasGCPList != nullptr)
        return pasGCPList;

    const GDAL_GCP *pasGCPsMain = poMainDS->GetGCPs();
    if (pasGCPsMain == nullptr)
        return nullptr;
    nGCPCount = poMainDS->GetGCPCount();

    pasGCPList = GDALDuplicateGCPs(nGCPCount, pasGCPsMain);
    for (int i = 0; i < nGCPCount; ++i)
    {
        pasGCPList[i].dfGCPPixel *=
            static_cast<double>(nRasterXSize) / poMainDS->GetRasterXSize();
        pasGCPList[i].dfGCPLine *=
            static_cast<double>(nRasterYSize) / poMainDS->GetRasterYSize();
    }
    return pasGCPList;
}

/************************************************************************/
/*                             Rescale()                                */
/************************************************************************/

void GDALOverviewDataset::Rescale(char **&papszMD, const char *pszItem,
                                  double dfRatio, double dfDefaultVal)
{
    double dfVal = CPLAtofM(CSLFetchNameValueDef(
        papszMD, pszItem, CPLSPrintf("%.18g", dfDefaultVal)));
    dfVal *= dfRatio;
    papszMD = CSLSetNameValue(papszMD, pszItem, CPLSPrintf("%.18g", dfVal));
}

/************************************************************************/
/*                            GetMetadata()                             */
/************************************************************************/

char **GDALOverviewDataset::GetMetadata(const char *pszDomain)
{
    if (poOvrDS != nullptr)
    {
        char **papszMD = poOvrDS->GetMetadata(pszDomain);
        if (papszMD != nullptr)
            return papszMD;
    }

    char **papszMD = poMainDS->GetMetadata(pszDomain);

    // We may need to rescale some values from the RPC metadata domain.
    if (pszDomain != nullptr && EQUAL(pszDomain, MD_DOMAIN_RPC) &&
        papszMD != nullptr)
    {
        if (papszMD_RPC)
            return papszMD_RPC;
        papszMD_RPC = CSLDuplicate(papszMD);

        Rescale(papszMD_RPC, RPC_LINE_OFF,
                static_cast<double>(nRasterYSize) / poMainDS->GetRasterYSize(),
                0.0);
        Rescale(papszMD_RPC, RPC_LINE_SCALE,
                static_cast<double>(nRasterYSize) / poMainDS->GetRasterYSize(),
                1.0);
        Rescale(papszMD_RPC, RPC_SAMP_OFF,
                static_cast<double>(nRasterXSize) / poMainDS->GetRasterXSize(),
                0.0);
        Rescale(papszMD_RPC, RPC_SAMP_SCALE,
                static_cast<double>(nRasterXSize) / poMainDS->GetRasterXSize(),
                1.0);

        papszMD = papszMD_RPC;
    }

    // We may need to rescale some values from the GEOLOCATION metadata domain.
    if (pszDomain != nullptr && EQUAL(pszDomain, "GEOLOCATION") &&
        papszMD != nullptr)
    {
        if (papszMD_GEOLOCATION)
            return papszMD_GEOLOCATION;
        papszMD_GEOLOCATION = CSLDuplicate(papszMD);

        Rescale(papszMD_GEOLOCATION, "PIXEL_OFFSET",
                static_cast<double>(poMainDS->GetRasterXSize()) / nRasterXSize,
                0.0);
        Rescale(papszMD_GEOLOCATION, "LINE_OFFSET",
                static_cast<double>(poMainDS->GetRasterYSize()) / nRasterYSize,
                0.0);

        Rescale(papszMD_GEOLOCATION, "PIXEL_STEP",
                static_cast<double>(nRasterXSize) / poMainDS->GetRasterXSize(),
                1.0);
        Rescale(papszMD_GEOLOCATION, "LINE_STEP",
                static_cast<double>(nRasterYSize) / poMainDS->GetRasterYSize(),
                1.0);

        papszMD = papszMD_GEOLOCATION;
    }

    return papszMD;
}

/************************************************************************/
/*                          GetMetadataItem()                           */
/************************************************************************/

const char *GDALOverviewDataset::GetMetadataItem(const char *pszName,
                                                 const char *pszDomain)
{
    if (poOvrDS != nullptr)
    {
        const char *pszValue = poOvrDS->GetMetadataItem(pszName, pszDomain);
        if (pszValue != nullptr)
            return pszValue;
    }

    if (pszDomain != nullptr &&
        (EQUAL(pszDomain, "RPC") || EQUAL(pszDomain, "GEOLOCATION")))
    {
        char **papszMD = GetMetadata(pszDomain);
        return CSLFetchNameValue(papszMD, pszName);
    }

    return poMainDS->GetMetadataItem(pszName, pszDomain);
}

/************************************************************************/
/*                          GDALOverviewBand()                          */
/************************************************************************/

GDALOverviewBand::GDALOverviewBand(GDALOverviewDataset *poDSIn, int nBandIn)
{
    poDS = poDSIn;
    nBand = nBandIn;
    nRasterXSize = poDSIn->nRasterXSize;
    nRasterYSize = poDSIn->nRasterYSize;
    if (nBandIn == 0)
    {
        poUnderlyingBand =
            GetOverviewEx(poDSIn->poMainDS->GetRasterBand(1), poDSIn->nOvrLevel)
                ->GetMaskBand();
    }
    else
    {
        poUnderlyingBand = GetOverviewEx(
            poDSIn->poMainDS->GetRasterBand(nBandIn), poDSIn->nOvrLevel);
    }
    eDataType = poUnderlyingBand->GetRasterDataType();
    poUnderlyingBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
}

/************************************************************************/
/*                         ~GDALOverviewBand()                          */
/************************************************************************/

GDALOverviewBand::~GDALOverviewBand()
{
    GDALOverviewBand::FlushCache(true);
}

/************************************************************************/
/*                              FlushCache()                            */
/************************************************************************/

CPLErr GDALOverviewBand::FlushCache(bool bAtClosing)
{
    if (poUnderlyingBand)
        return poUnderlyingBand->FlushCache(bAtClosing);
    return CE_None;
}

/************************************************************************/
/*                        RefUnderlyingRasterBand()                     */
/************************************************************************/

GDALRasterBand *GDALOverviewBand::RefUnderlyingRasterBand() const
{
    if (poUnderlyingBand)
        return poUnderlyingBand;

    return nullptr;
}

/************************************************************************/
/*                         GetOverviewCount()                           */
/************************************************************************/

int GDALOverviewBand::GetOverviewCount()
{
    GDALOverviewDataset *const poOvrDS =
        cpl::down_cast<GDALOverviewDataset *>(poDS);
    if (poOvrDS->bThisLevelOnly)
        return 0;
    GDALDataset *const poMainDS = poOvrDS->poMainDS;
    GDALRasterBand *poMainBand = (nBand == 0)
                                     ? poMainDS->GetRasterBand(1)->GetMaskBand()
                                     : poMainDS->GetRasterBand(nBand);
    auto poUnderlyingDS =
        poUnderlyingBand ? poUnderlyingBand->GetDataset() : nullptr;
    if (poUnderlyingDS)
        poUnderlyingDS->SetEnableOverviews(true);
    const int nRet = poMainBand->GetOverviewCount() - poOvrDS->nOvrLevel - 1;
    if (poUnderlyingDS)
        poUnderlyingDS->SetEnableOverviews(false);
    return nRet;
}

/************************************************************************/
/*                           GetOverview()                              */
/************************************************************************/

GDALRasterBand *GDALOverviewBand::GetOverview(int iOvr)
{
    if (iOvr < 0 || iOvr >= GetOverviewCount())
        return nullptr;
    GDALOverviewDataset *const poOvrDS =
        cpl::down_cast<GDALOverviewDataset *>(poDS);
    GDALDataset *const poMainDS = poOvrDS->poMainDS;
    GDALRasterBand *poMainBand = (nBand == 0)
                                     ? poMainDS->GetRasterBand(1)->GetMaskBand()
                                     : poMainDS->GetRasterBand(nBand);
    auto poUnderlyingDS =
        poUnderlyingBand ? poUnderlyingBand->GetDataset() : nullptr;
    if (poUnderlyingDS)
        poUnderlyingDS->SetEnableOverviews(true);
    auto poRet = poMainBand->GetOverview(iOvr + poOvrDS->nOvrLevel + 1);
    if (poUnderlyingDS)
        poUnderlyingDS->SetEnableOverviews(false);
    return poRet;
}

/************************************************************************/
/*                           GetMaskFlags()                             */
/************************************************************************/

int GDALOverviewBand::GetMaskFlags()
{
    GDALOverviewDataset *const poOvrDS =
        cpl::down_cast<GDALOverviewDataset *>(poDS);
    if (nBand != 0 && poOvrDS->m_poMaskBand)
        return GMF_PER_DATASET;
    return GDALProxyRasterBand::GetMaskFlags();
}

/************************************************************************/
/*                           GetMaskBand()                              */
/************************************************************************/

GDALRasterBand *GDALOverviewBand::GetMaskBand()
{
    GDALOverviewDataset *const poOvrDS =
        cpl::down_cast<GDALOverviewDataset *>(poDS);
    if (nBand != 0 && poOvrDS->m_poMaskBand)
        return poOvrDS->m_poMaskBand;
    return GDALProxyRasterBand::GetMaskBand();
}

/************************************************************************/
/*                            IRasterIO()                               */
/************************************************************************/

CPLErr GDALOverviewBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
                                   int nXSize, int nYSize, void *pData,
                                   int nBufXSize, int nBufYSize,
                                   GDALDataType eBufType, GSpacing nPixelSpace,
                                   GSpacing nLineSpace,
                                   GDALRasterIOExtraArg *psExtraArg)
{
    // Try to pass the request to the most appropriate overview.
    if (nBufXSize < nXSize && nBufYSize < nYSize)
    {
        int bTried = FALSE;
        const CPLErr eErr = TryOverviewRasterIO(
            eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
            eBufType, nPixelSpace, nLineSpace, psExtraArg, &bTried);
        if (bTried)
            return eErr;
    }

    return GDALProxyRasterBand::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
                                          pData, nBufXSize, nBufYSize, eBufType,
                                          nPixelSpace, nLineSpace, psExtraArg);
}
