/******************************************************************************
 *
 * Project:  Generic Raw Binary Driver
 * Purpose:  Implementation of RawDataset and RawRasterBand classes.
 * Author:   Frank Warmerdam, warmerda@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 1999, Frank Warmerdam
 * Copyright (c) 2007-2014, Even Rouault <even dot rouault at mines-paris dot org>
 *
 * 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 "rawdataset.h"

#include <climits>
#include <cmath>
#include <cstddef>
#include <cstdlib>
#include <cstring>
#if HAVE_FCNTL_H
#  include <fcntl.h>
#endif
#include <algorithm>
#include <limits>

#include "cpl_conv.h"
#include "cpl_error.h"
#include "cpl_progress.h"
#include "cpl_string.h"
#include "cpl_virtualmem.h"
#include "cpl_vsi.h"
#include "gdal.h"
#include "gdal_priv.h"

CPL_CVSID("$Id: rawdataset.cpp 818f06b5bc60efa562497297e60d066a2ed9c0c3 2019-04-17 21:45:17 +0200 Even Rouault $")

/************************************************************************/
/*                           RawRasterBand()                            */
/************************************************************************/

RawRasterBand::RawRasterBand( GDALDataset *poDSIn, int nBandIn,
                              VSILFILE *fpRawLIn, vsi_l_offset nImgOffsetIn,
                              int nPixelOffsetIn, int nLineOffsetIn,
                              GDALDataType eDataTypeIn, int bNativeOrderIn,
                              OwnFP bOwnsFPIn ) :
    fpRawL(fpRawLIn),
    nImgOffset(nImgOffsetIn),
    nPixelOffset(nPixelOffsetIn),
    nLineOffset(nLineOffsetIn),
    bNativeOrder(bNativeOrderIn),
    bOwnsFP(bOwnsFPIn == OwnFP::YES)
{
    poDS = poDSIn;
    nBand = nBandIn;
    eDataType = eDataTypeIn;

    CPLDebug("GDALRaw",
             "RawRasterBand(%p,%d,%p,\n"
             "              Off=%d,PixOff=%d,LineOff=%d,%s,%d)",
             poDS, nBand, fpRawL,
             static_cast<unsigned int>(nImgOffset), nPixelOffset, nLineOffset,
             GDALGetDataTypeName(eDataType), bNativeOrder);

    // Treat one scanline as the block size.
    nBlockXSize = poDS->GetRasterXSize();
    nBlockYSize = 1;

    // Initialize other fields, and setup the line buffer.
    Initialize();
}

/************************************************************************/
/*                           RawRasterBand()                            */
/************************************************************************/

RawRasterBand::RawRasterBand( VSILFILE *fpRawLIn, vsi_l_offset nImgOffsetIn,
                              int nPixelOffsetIn, int nLineOffsetIn,
                              GDALDataType eDataTypeIn, int bNativeOrderIn,
                              int nXSize, int nYSize,
                              OwnFP bOwnsFPIn ) :
    fpRawL(fpRawLIn),
    nImgOffset(nImgOffsetIn),
    nPixelOffset(nPixelOffsetIn),
    nLineOffset(nLineOffsetIn),
    nLineSize(0),
    bNativeOrder(bNativeOrderIn),
    nLoadedScanline(0),
    pLineStart(nullptr),
    bDirty(FALSE),
    poCT(nullptr),
    eInterp(GCI_Undefined),
    papszCategoryNames(nullptr),
    bOwnsFP(bOwnsFPIn == OwnFP::YES)
{
    poDS = nullptr;
    nBand = 1;
    eDataType = eDataTypeIn;

    CPLDebug("GDALRaw",
             "RawRasterBand(floating,Off=%d,PixOff=%d,LineOff=%d,%s,%d)",
             static_cast<unsigned int>(nImgOffset),
             nPixelOffset, nLineOffset,
             GDALGetDataTypeName(eDataType), bNativeOrder);

    // Treat one scanline as the block size.
    nBlockXSize = nXSize;
    nBlockYSize = 1;
    nRasterXSize = nXSize;
    nRasterYSize = nYSize;
    if (!GDALCheckDatasetDimensions(nXSize, nYSize))
    {
        pLineBuffer = nullptr;
        return;
    }

    // Initialize other fields, and setup the line buffer.
    Initialize();
}

/************************************************************************/
/*                             Initialize()                             */
/************************************************************************/

void RawRasterBand::Initialize()

{
    poCT = nullptr;
    eInterp = GCI_Undefined;

    papszCategoryNames = nullptr;

    bDirty = FALSE;

    // Allocate working scanline.
    nLoadedScanline = -1;
    const int nDTSize = GDALGetDataTypeSizeBytes(GetRasterDataType());
    if (nBlockXSize <= 0 ||
        (nBlockXSize > 1 && std::abs(nPixelOffset) >
            std::numeric_limits<int>::max() / (nBlockXSize - 1)) ||
        std::abs(nPixelOffset) * (nBlockXSize - 1) >
            std::numeric_limits<int>::max() - nDTSize)
    {
        nLineSize = 0;
        pLineBuffer = nullptr;
    }
    else
    {
        nLineSize = std::abs(nPixelOffset) * (nBlockXSize - 1) + nDTSize;
        pLineBuffer = VSIMalloc(nLineSize);
    }
    if (pLineBuffer == nullptr)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Could not allocate line buffer: "
                 "nPixelOffset=%d, nBlockXSize=%d",
                 nPixelOffset, nBlockXSize);
    }

    if( nPixelOffset >= 0 )
        pLineStart = pLineBuffer;
    else
        pLineStart = static_cast<char *>(pLineBuffer) +
                     static_cast<std::ptrdiff_t>(std::abs(nPixelOffset)) *
                         (nBlockXSize - 1);
}

/************************************************************************/
/*                           ~RawRasterBand()                           */
/************************************************************************/

RawRasterBand::~RawRasterBand()

{
    if( poCT )
        delete poCT;

    CSLDestroy(papszCategoryNames);

    RawRasterBand::FlushCache();

    if (bOwnsFP)
    {
        if( VSIFCloseL(fpRawL) != 0 )
        {
            CPLError(CE_Failure, CPLE_FileIO, "I/O error");
        }
    }

    CPLFree(pLineBuffer);
}

/************************************************************************/
/*                             SetAccess()                              */
/************************************************************************/

void RawRasterBand::SetAccess(GDALAccess eAccessIn) { eAccess = eAccessIn; }

/************************************************************************/
/*                             FlushCache()                             */
/*                                                                      */
/*      We override this so we have the opportunity to call             */
/*      fflush().  We don't want to do this all the time in the         */
/*      write block function as it is kind of expensive.                */
/************************************************************************/

CPLErr RawRasterBand::FlushCache()

{
    CPLErr eErr = GDALRasterBand::FlushCache();
    if( eErr != CE_None )
    {
        bDirty = FALSE;
        return eErr;
    }

    // If we have unflushed raw, flush it to disk now.
    if ( bDirty )
    {
        int nRet = VSIFFlushL(fpRawL);

        bDirty = FALSE;
        if( nRet < 0 )
            return CE_Failure;
    }

    return CE_None;
}

/************************************************************************/
/*                             AccessLine()                             */
/************************************************************************/

CPLErr RawRasterBand::AccessLine( int iLine )

{
    if (pLineBuffer == nullptr)
        return CE_Failure;

    if( nLoadedScanline == iLine )
        return CE_None;

    // Figure out where to start reading.
    // Write formulas such that unsigned int overflow doesn't occur
    const GUIntBig nPixelOffsetToSubtract =
        nPixelOffset >= 0
        ? 0 : static_cast<GUIntBig>(-static_cast<GIntBig>(nPixelOffset)) * (nBlockXSize - 1);
    const vsi_l_offset nReadStart = static_cast<vsi_l_offset>(
        (nLineOffset >= 0 ?
            nImgOffset + static_cast<GUIntBig>(nLineOffset) * iLine :
            nImgOffset - static_cast<GUIntBig>(-static_cast<GIntBig>(nLineOffset)) * iLine )
        - nPixelOffsetToSubtract);

    // Seek to the correct line.
    if( Seek(nReadStart, SEEK_SET) == -1 )
    {
        if (poDS != nullptr && poDS->GetAccess() == GA_ReadOnly)
        {
            CPLError(CE_Failure, CPLE_FileIO,
                     "Failed to seek to scanline %d @ " CPL_FRMT_GUIB ".",
                     iLine, nReadStart);
            return CE_Failure;
        }
        else
        {
            memset(pLineBuffer, 0, nLineSize);
            nLoadedScanline = iLine;
            return CE_None;
        }
    }

    // Read the line.  Take care not to request any more bytes than
    // are needed, and not to lose a partially successful scanline read.
    const size_t nBytesToRead = nLineSize;
    const size_t nBytesActuallyRead = Read(pLineBuffer, 1, nBytesToRead);
    if( nBytesActuallyRead < nBytesToRead )
    {
        if (poDS != nullptr && poDS->GetAccess() == GA_ReadOnly &&
            // ENVI datasets might be sparse (see #915)
            poDS->GetMetadata("ENVI") == nullptr)
        {
            CPLError(CE_Failure, CPLE_FileIO,
                     "Failed to read scanline %d.",
                     iLine);
            return CE_Failure;
        }
        else
        {
            memset(
                static_cast<GByte *>(pLineBuffer) + nBytesActuallyRead,
                0, nBytesToRead - nBytesActuallyRead);
        }
    }

    // Byte swap the interesting data, if required.
    if( !bNativeOrder && eDataType != GDT_Byte )
    {
        if( GDALDataTypeIsComplex(eDataType) )
        {
            const int nWordSize = GDALGetDataTypeSize(eDataType) / 16;
            GDALSwapWords(pLineBuffer, nWordSize, nBlockXSize,
                          std::abs(nPixelOffset));
            GDALSwapWords(
                static_cast<GByte *>(pLineBuffer) + nWordSize,
                nWordSize, nBlockXSize, std::abs(nPixelOffset));
        }
        else
        {
            GDALSwapWords(pLineBuffer, GDALGetDataTypeSizeBytes(eDataType),
                          nBlockXSize, std::abs(nPixelOffset));
        }
    }

    nLoadedScanline = iLine;

    return CE_None;
}

/************************************************************************/
/*                             IReadBlock()                             */
/************************************************************************/

CPLErr RawRasterBand::IReadBlock(CPL_UNUSED int nBlockXOff,
                                 int nBlockYOff,
                                 void *pImage)
{
    CPLAssert(nBlockXOff == 0);

    if (pLineBuffer == nullptr)
        return CE_Failure;

    const CPLErr eErr = AccessLine(nBlockYOff);
    if( eErr == CE_Failure )
        return eErr;

    // Copy data from disk buffer to user block buffer.
    GDALCopyWords(pLineStart, eDataType, nPixelOffset,
                  pImage, eDataType, GDALGetDataTypeSizeBytes(eDataType),
                  nBlockXSize);

    return eErr;
}

/************************************************************************/
/*                            IWriteBlock()                             */
/************************************************************************/

CPLErr RawRasterBand::IWriteBlock( CPL_UNUSED int nBlockXOff,
                                   int nBlockYOff,
                                   void *pImage )
{
    CPLAssert(nBlockXOff == 0);

    if (pLineBuffer == nullptr)
        return CE_Failure;

    // If the data for this band is completely contiguous, we don't
    // have to worry about pre-reading from disk.
    CPLErr eErr = CE_None;
    if( std::abs(nPixelOffset) > GDALGetDataTypeSizeBytes(eDataType) )
        eErr = AccessLine(nBlockYOff);

    // Copy data from user buffer into disk buffer.
    GDALCopyWords(pImage, eDataType, GDALGetDataTypeSizeBytes(eDataType),
                  pLineStart, eDataType, nPixelOffset, nBlockXSize);


    // Byte swap (if necessary) back into disk order before writing.
    if( !bNativeOrder && eDataType != GDT_Byte )
    {
        if( GDALDataTypeIsComplex(eDataType) )
        {
            const int nWordSize = GDALGetDataTypeSize(eDataType) / 16;
            GDALSwapWords(pLineBuffer, nWordSize, nBlockXSize,
                          std::abs(nPixelOffset));
            GDALSwapWords(static_cast<GByte *>(pLineBuffer) + nWordSize,
                          nWordSize, nBlockXSize, std::abs(nPixelOffset));
        }
        else
        {
            GDALSwapWords(pLineBuffer, GDALGetDataTypeSizeBytes(eDataType),
                          nBlockXSize, std::abs(nPixelOffset));
        }
    }

    // Figure out where to start writing.
    // Write formulas such that unsigned int overflow doesn't occur
    const GUIntBig nPixelOffsetToSubtract =
        nPixelOffset >= 0
        ? 0 : static_cast<GUIntBig>(-static_cast<GIntBig>(nPixelOffset)) * (nBlockXSize - 1);
    const vsi_l_offset nWriteStart = static_cast<vsi_l_offset>(
        (nLineOffset >= 0 ?
            nImgOffset + static_cast<GUIntBig>(nLineOffset) * nBlockYOff :
            nImgOffset - static_cast<GUIntBig>(-static_cast<GIntBig>(nLineOffset)) * nBlockYOff )
        - nPixelOffsetToSubtract);

    // Seek to correct location.
    if( Seek(nWriteStart, SEEK_SET) == -1 )
    {
        CPLError(CE_Failure, CPLE_FileIO,
                 "Failed to seek to scanline %d @ " CPL_FRMT_GUIB
                 " to write to file.",
                 nBlockYOff, nImgOffset + nBlockYOff * nLineOffset);

        eErr = CE_Failure;
    }

    // Write data buffer.
    const int nBytesToWrite = nLineSize;
    if( eErr == CE_None
        && Write(pLineBuffer, 1, nBytesToWrite)
        < static_cast<size_t>(nBytesToWrite) )
    {
        CPLError(CE_Failure, CPLE_FileIO,
                 "Failed to write scanline %d to file.",
                 nBlockYOff);

        eErr = CE_Failure;
    }

    // Byte swap (if necessary) back into machine order so the
    // buffer is still usable for reading purposes.
    if( !bNativeOrder && eDataType != GDT_Byte )
    {
        if( GDALDataTypeIsComplex(eDataType) )
        {
            const int nWordSize = GDALGetDataTypeSize(eDataType) / 16;
            GDALSwapWords(pLineBuffer, nWordSize, nBlockXSize,
                          std::abs(nPixelOffset));
            GDALSwapWords(static_cast<GByte *>(pLineBuffer) +
                          nWordSize, nWordSize, nBlockXSize,
                          std::abs(nPixelOffset));
        }
        else
        {
            GDALSwapWords(pLineBuffer, GDALGetDataTypeSizeBytes(eDataType),
                          nBlockXSize, std::abs(nPixelOffset));
        }
    }

    bDirty = TRUE;
    return eErr;
}

/************************************************************************/
/*                             AccessBlock()                            */
/************************************************************************/

CPLErr RawRasterBand::AccessBlock(vsi_l_offset nBlockOff, size_t nBlockSize,
                                  void *pData)
{
    // Seek to the correct block.
    if( Seek(nBlockOff, SEEK_SET) == -1 )
    {
        memset(pData, 0, nBlockSize);
        return CE_None;
    }

    // Read the block.
    const size_t nBytesActuallyRead = Read(pData, 1, nBlockSize);
    if( nBytesActuallyRead < nBlockSize )
    {

        memset(static_cast<GByte *>(pData) + nBytesActuallyRead,
               0, nBlockSize - nBytesActuallyRead);
        return CE_None;
    }

    // Byte swap the interesting data, if required.
    if( !bNativeOrder && eDataType != GDT_Byte )
    {
        if( GDALDataTypeIsComplex(eDataType) )
        {
            const int nWordSize = GDALGetDataTypeSize(eDataType) / 16;
            GDALSwapWordsEx(pData, nWordSize, nBlockSize / nPixelOffset,
                            nPixelOffset);
            GDALSwapWordsEx(static_cast<GByte *>(pData) + nWordSize,
                            nWordSize, nBlockSize / nPixelOffset, nPixelOffset);
        }
        else
        {
            GDALSwapWordsEx(pData, GDALGetDataTypeSizeBytes(eDataType),
                            nBlockSize / nPixelOffset, nPixelOffset);
        }
    }

    return CE_None;
}

/************************************************************************/
/*               IsSignificantNumberOfLinesLoaded()                     */
/*                                                                      */
/*  Check if there is a significant number of scanlines (>20%) from the */
/*  specified block of lines already cached.                            */
/************************************************************************/

int RawRasterBand::IsSignificantNumberOfLinesLoaded( int nLineOff, int nLines )
{
    int nCountLoaded = 0;

    for ( int iLine = nLineOff; iLine < nLineOff + nLines; iLine++ )
    {
        GDALRasterBlock *poBlock = TryGetLockedBlockRef(0, iLine);
        if( poBlock != nullptr )
        {
            poBlock->DropLock();
            nCountLoaded++;
            if( nCountLoaded > nLines / 20 )
            {
                return TRUE;
            }
        }
    }

    return FALSE;
}

/************************************************************************/
/*                           CanUseDirectIO()                           */
/************************************************************************/

int RawRasterBand::CanUseDirectIO(int /* nXOff */,
                                  int nYOff,
                                  int nXSize,
                                  int nYSize,
                                  GDALDataType /* eBufType*/,
                                  GDALRasterIOExtraArg* psExtraArg)
{

    // Use direct IO without caching if:
    //
    // GDAL_ONE_BIG_READ is enabled
    //
    // or
    //
    // the length of a scanline on disk is more than 50000 bytes, and the
    // width of the requested chunk is less than 40% of the whole scanline and
    // no significant number of requested scanlines are already in the cache.

    if( nPixelOffset < 0 ||
        psExtraArg->eResampleAlg != GRIORA_NearestNeighbour )
    {
        return FALSE;
    }

    const char *pszGDAL_ONE_BIG_READ =
        CPLGetConfigOption("GDAL_ONE_BIG_READ", nullptr);
    if ( pszGDAL_ONE_BIG_READ == nullptr )
    {
        if ( nLineSize < 50000
             || nXSize > nLineSize / nPixelOffset / 5 * 2
             || IsSignificantNumberOfLinesLoaded(nYOff, nYSize) )
        {
            return FALSE;
        }
        return TRUE;
    }

    return CPLTestBool(pszGDAL_ONE_BIG_READ);
}

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

CPLErr RawRasterBand::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 )

{
    const int nBandDataSize = GDALGetDataTypeSizeBytes(eDataType);
#ifdef DEBUG
    // Otherwise Coverity thinks that a divide by zero is possible in
    // AccessBlock() in the complex data type wapping case.
    if( nBandDataSize == 0 )
        return CE_Failure;
#endif
    const int nBufDataSize = GDALGetDataTypeSizeBytes(eBufType);

    if( !CanUseDirectIO(nXOff, nYOff, nXSize, nYSize, eBufType, psExtraArg) )
    {
        return GDALRasterBand::IRasterIO(eRWFlag, nXOff, nYOff,
                                         nXSize, nYSize,
                                         pData, nBufXSize, nBufYSize,
                                         eBufType,
                                         nPixelSpace, nLineSpace, psExtraArg);
    }

    CPLDebug("RAW", "Using direct IO implementation");

    // Read data.
    if ( eRWFlag == GF_Read )
    {
        // Do we have overviews that are appropriate to satisfy this request?
        if( (nBufXSize < nXSize || nBufYSize < nYSize)
            && GetOverviewCount() > 0 )
        {
            if( OverviewRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
                                 pData, nBufXSize, nBufYSize,
                                 eBufType, nPixelSpace, nLineSpace,
                                 psExtraArg) == CE_None)
                return CE_None;
        }

        // 1. Simplest case when we should get contiguous block
        //    of uninterleaved pixels.
        if ( nXSize == GetXSize()
             && nXSize == nBufXSize
             && nYSize == nBufYSize
             && eBufType == eDataType
             && nPixelOffset == nBandDataSize
             && nPixelSpace == nBufDataSize
             && nLineSpace == nPixelSpace * nXSize )
        {
            const vsi_l_offset nOffset =
                nImgOffset + static_cast<vsi_l_offset>(nYOff) * nLineOffset +
                nXOff;
            const size_t nBytesToRead =
                static_cast<size_t>(nXSize) * nYSize * nBandDataSize;
            if ( AccessBlock(nOffset, nBytesToRead, pData) != CE_None )
            {
                CPLError(CE_Failure, CPLE_FileIO,
                         "Failed to read " CPL_FRMT_GUIB
                         " bytes at " CPL_FRMT_GUIB ".",
                         static_cast<GUIntBig>(nBytesToRead), nOffset);
                return CE_Failure;
            }
        }
        // 2. Case when we need deinterleave and/or subsample data.
        else
        {
            const double dfSrcXInc = static_cast<double>(nXSize) / nBufXSize;
            const double dfSrcYInc = static_cast<double>(nYSize) / nBufYSize;

            const size_t nBytesToRW =
                static_cast<size_t>(nPixelOffset) * nXSize;
            GByte *pabyData =
                static_cast<GByte *>(VSI_MALLOC_VERBOSE(nBytesToRW));
            if( pabyData == nullptr )
                return CE_Failure;

            for ( int iLine = 0; iLine < nBufYSize; iLine++ )
            {
                const vsi_l_offset nOffset =
                    nImgOffset +
                    ((static_cast<vsi_l_offset>(nYOff) +
                      static_cast<vsi_l_offset>(iLine * dfSrcYInc)) *
                     nLineOffset) +
                    nXOff * nPixelOffset;
                if ( AccessBlock(nOffset,
                                 nBytesToRW, pabyData) != CE_None )
                {
                    CPLError(CE_Failure, CPLE_FileIO,
                             "Failed to read " CPL_FRMT_GUIB
                             " bytes at " CPL_FRMT_GUIB ".",
                             static_cast<GUIntBig>(nBytesToRW), nOffset);
                    CPLFree(pabyData);
                    return CE_Failure;
                }
                // Copy data from disk buffer to user block buffer and
                // subsample, if needed.
                if ( nXSize == nBufXSize && nYSize == nBufYSize )
                {
                    GDALCopyWords(
                        pabyData, eDataType, nPixelOffset,
                        static_cast<GByte *>(pData) +
                            static_cast<vsi_l_offset>(iLine) * nLineSpace,
                        eBufType, static_cast<int>(nPixelSpace), nXSize);
                }
                else
                {
                    for ( int iPixel = 0; iPixel < nBufXSize; iPixel++ )
                    {
                        GDALCopyWords(
                            pabyData +
                                static_cast<vsi_l_offset>(iPixel * dfSrcXInc) *
                                    nPixelOffset,
                            eDataType, nPixelOffset,
                            static_cast<GByte *>(pData) +
                                static_cast<vsi_l_offset>(iLine) * nLineSpace +
                                static_cast<vsi_l_offset>(iPixel) * nPixelSpace,
                            eBufType, static_cast<int>(nPixelSpace), 1);
                    }
                }

                if( psExtraArg->pfnProgress != nullptr &&
                    !psExtraArg->pfnProgress(1.0 * (iLine + 1) / nBufYSize, "",
                                            psExtraArg->pProgressData) )
                {
                    CPLFree(pabyData);
                    return CE_Failure;
                }
            }

            CPLFree(pabyData);
        }
    }
    // Write data.
    else
    {
        // 1. Simplest case when we should write contiguous block of
        //    uninterleaved pixels.
        if ( nXSize == GetXSize()
             && nXSize == nBufXSize
             && nYSize == nBufYSize
             && eBufType == eDataType
             && nPixelOffset == nBandDataSize
             && nPixelSpace == nBufDataSize
             && nLineSpace == nPixelSpace * nXSize )
        {
            // Byte swap the data buffer, if required.
            if( !bNativeOrder && eDataType != GDT_Byte )
            {
                if( GDALDataTypeIsComplex(eDataType) )
                {
                    const int nWordSize = GDALGetDataTypeSize(eDataType) / 16;
                    GDALSwapWords(pData, nWordSize, nXSize, nPixelOffset);
                    GDALSwapWords(static_cast<GByte *>(pData) + nWordSize,
                                  nWordSize, nXSize, nPixelOffset);
                }
                else
                {
                    GDALSwapWords(pData, nBandDataSize, nXSize, nPixelOffset);
                }
            }

            // Seek to the correct block.
            const vsi_l_offset nOffset =
                nImgOffset + static_cast<vsi_l_offset>(nYOff) * nLineOffset +
                nXOff;
            if( Seek(nOffset, SEEK_SET) == -1 )
            {
                CPLError(CE_Failure, CPLE_FileIO,
                         "Failed to seek to " CPL_FRMT_GUIB " to write data.",
                         nOffset);

                return CE_Failure;
            }

            // Write the block.
            const size_t nBytesToRW =
                static_cast<size_t>(nXSize) * nYSize * nBandDataSize;

            const size_t nBytesActuallyWritten = Write(pData, 1, nBytesToRW);
            if( nBytesActuallyWritten < nBytesToRW )
            {
                CPLError(CE_Failure, CPLE_FileIO,
                         "Failed to write " CPL_FRMT_GUIB
                         " bytes to file. " CPL_FRMT_GUIB " bytes written",
                         static_cast<GUIntBig>(nBytesToRW),
                         static_cast<GUIntBig>(nBytesActuallyWritten));

                return CE_Failure;
            }

            // Byte swap (if necessary) back into machine order so the
            // buffer is still usable for reading purposes.
            if( !bNativeOrder  && eDataType != GDT_Byte )
            {
                if( GDALDataTypeIsComplex(eDataType) )
                {
                    const int nWordSize = GDALGetDataTypeSize(eDataType) / 16;
                    GDALSwapWords(pData, nWordSize, nXSize, nPixelOffset);
                    GDALSwapWords(static_cast<GByte *>(pData) + nWordSize,
                                  nWordSize, nXSize, nPixelOffset);
                }
                else
                {
                    GDALSwapWords(pData, nBandDataSize, nXSize, nPixelOffset);
                }
            }
        }
        // 2. Case when we need deinterleave and/or subsample data.
        else
        {
            const double dfSrcXInc = static_cast<double>(nXSize) / nBufXSize;
            const double dfSrcYInc = static_cast<double>(nYSize) / nBufYSize;

            const size_t nBytesToRW =
                static_cast<size_t>(nPixelOffset) * nXSize;
            GByte *pabyData =
                static_cast<GByte *>(VSI_MALLOC_VERBOSE(nBytesToRW));
            if( pabyData == nullptr )
                return CE_Failure;

            for ( int iLine = 0; iLine < nBufYSize; iLine++ )
            {
                const vsi_l_offset nBlockOff =
                    nImgOffset +
                    (static_cast<vsi_l_offset>(nYOff) +
                     static_cast<vsi_l_offset>(iLine * dfSrcYInc)) *
                        nLineOffset +
                    static_cast<vsi_l_offset>(nXOff) * nPixelOffset;

                // If the data for this band is completely contiguous we don't
                // have to worry about pre-reading from disk.
                if( nPixelOffset > nBandDataSize )
                    AccessBlock(nBlockOff, nBytesToRW, pabyData);

                // Copy data from user block buffer to disk buffer and
                // subsample, if needed.
                if ( nXSize == nBufXSize && nYSize == nBufYSize )
                {
                    GDALCopyWords(static_cast<GByte *>(pData) +
                                      static_cast<vsi_l_offset>(iLine) *
                                          nLineSpace,
                                  eBufType, static_cast<int>(nPixelSpace),
                                  pabyData, eDataType, nPixelOffset, nXSize);
                }
                else
                {
                    for ( int iPixel = 0; iPixel < nBufXSize; iPixel++ )
                    {
                        GDALCopyWords(
                            static_cast<GByte *>(pData) +
                                static_cast<vsi_l_offset>(iLine) * nLineSpace +
                                static_cast<vsi_l_offset>(iPixel) * nPixelSpace,
                            eBufType, static_cast<int>(nPixelSpace),
                            pabyData +
                                static_cast<vsi_l_offset>(iPixel * dfSrcXInc) *
                                    nPixelOffset,
                            eDataType, nPixelOffset, 1);
                    }
                }

                // Byte swap the data buffer, if required.
                if( !bNativeOrder && eDataType != GDT_Byte )
                {
                    if( GDALDataTypeIsComplex(eDataType) )
                    {
                        const int nWordSize =
                            GDALGetDataTypeSize(eDataType) / 16;
                        GDALSwapWords(pabyData, nWordSize, nXSize,
                                      nPixelOffset);
                        GDALSwapWords(static_cast<GByte *>(pabyData) +
                                          nWordSize,
                                      nWordSize, nXSize, nPixelOffset);
                    }
                    else
                    {
                        GDALSwapWords(pabyData, nBandDataSize, nXSize,
                                      nPixelOffset);
                    }
                }

                // Seek to the right line in block.
                if( Seek(nBlockOff, SEEK_SET) == -1 )
                {
                    CPLError(CE_Failure, CPLE_FileIO,
                             "Failed to seek to " CPL_FRMT_GUIB " to read.",
                             nBlockOff);
                    CPLFree(pabyData);
                    return CE_Failure;
                }

                // Write the line of block.
                const size_t nBytesActuallyWritten =
                    Write(pabyData, 1, nBytesToRW);
                if( nBytesActuallyWritten < nBytesToRW )
                {
                    CPLError(CE_Failure, CPLE_FileIO,
                             "Failed to write " CPL_FRMT_GUIB
                             " bytes to file. " CPL_FRMT_GUIB " bytes written",
                             static_cast<GUIntBig>(nBytesToRW),
                             static_cast<GUIntBig>(nBytesActuallyWritten));
                    CPLFree(pabyData);
                    return CE_Failure;
                }


                // Byte swap (if necessary) back into machine order so the
                // buffer is still usable for reading purposes.
                if( !bNativeOrder && eDataType != GDT_Byte )
                {
                    if( GDALDataTypeIsComplex(eDataType) )
                    {
                        const int nWordSize =
                            GDALGetDataTypeSize(eDataType) / 16;
                        GDALSwapWords(pabyData, nWordSize, nXSize,
                                      nPixelOffset);
                        GDALSwapWords(static_cast<GByte *>(pabyData) +
                                          nWordSize,
                                      nWordSize, nXSize, nPixelOffset);
                    }
                    else
                    {
                        GDALSwapWords(pabyData, nBandDataSize, nXSize,
                                      nPixelOffset);
                    }
                }
            }

            bDirty = TRUE;
            CPLFree(pabyData);
        }
    }

    return CE_None;
}

/************************************************************************/
/*                                Seek()                                */
/************************************************************************/

int RawRasterBand::Seek( vsi_l_offset nOffset, int nSeekMode )

{
    return VSIFSeekL(fpRawL, nOffset, nSeekMode);
}

/************************************************************************/
/*                                Read()                                */
/************************************************************************/

size_t RawRasterBand::Read( void *pBuffer, size_t nSize, size_t nCount )

{
    return VSIFReadL(pBuffer, nSize, nCount, fpRawL);
}

/************************************************************************/
/*                               Write()                                */
/************************************************************************/

size_t RawRasterBand::Write( void *pBuffer, size_t nSize, size_t nCount )

{
    return VSIFWriteL(pBuffer, nSize, nCount, fpRawL);
}

/************************************************************************/
/*                          StoreNoDataValue()                          */
/*                                                                      */
/*      This is a helper function for datasets to associate a no        */
/*      data value with this band, it isn't intended to be called by    */
/*      applications.                                                   */
/************************************************************************/

void RawRasterBand::StoreNoDataValue( double dfValue )

{
    SetNoDataValue(dfValue);
}

/************************************************************************/
/*                          GetCategoryNames()                          */
/************************************************************************/

char **RawRasterBand::GetCategoryNames() { return papszCategoryNames; }

/************************************************************************/
/*                          SetCategoryNames()                          */
/************************************************************************/

CPLErr RawRasterBand::SetCategoryNames( char **papszNewNames )

{
    CSLDestroy(papszCategoryNames);
    papszCategoryNames = CSLDuplicate(papszNewNames);

    return CE_None;
}

/************************************************************************/
/*                           SetColorTable()                            */
/************************************************************************/

CPLErr RawRasterBand::SetColorTable( GDALColorTable *poNewCT )

{
    if( poCT )
        delete poCT;
    if( poNewCT == nullptr )
        poCT = nullptr;
    else
        poCT = poNewCT->Clone();

    return CE_None;
}

/************************************************************************/
/*                           GetColorTable()                            */
/************************************************************************/

GDALColorTable *RawRasterBand::GetColorTable() { return poCT; }

/************************************************************************/
/*                       SetColorInterpretation()                       */
/************************************************************************/

CPLErr RawRasterBand::SetColorInterpretation( GDALColorInterp eNewInterp )

{
    eInterp = eNewInterp;

    return CE_None;
}

/************************************************************************/
/*                       GetColorInterpretation()                       */
/************************************************************************/

GDALColorInterp RawRasterBand::GetColorInterpretation() { return eInterp; }

/************************************************************************/
/*                           GetVirtualMemAuto()                        */
/************************************************************************/

CPLVirtualMem  *RawRasterBand::GetVirtualMemAuto( GDALRWFlag eRWFlag,
                                                  int *pnPixelSpace,
                                                  GIntBig *pnLineSpace,
                                                  char **papszOptions )
{
    CPLAssert(pnPixelSpace);
    CPLAssert(pnLineSpace);

    const vsi_l_offset nSize =
        static_cast<vsi_l_offset>(nRasterYSize - 1) * nLineOffset +
        (nRasterXSize - 1) * nPixelOffset + GDALGetDataTypeSizeBytes(eDataType);

    const char *pszImpl = CSLFetchNameValueDef(
            papszOptions, "USE_DEFAULT_IMPLEMENTATION", "AUTO");
    if( VSIFGetNativeFileDescriptorL(fpRawL) == nullptr ||
        !CPLIsVirtualMemFileMapAvailable() ||
        (eDataType != GDT_Byte && !bNativeOrder) ||
        static_cast<size_t>(nSize) != nSize ||
        nPixelOffset < 0 ||
        nLineOffset < 0 ||
        EQUAL(pszImpl, "YES") || EQUAL(pszImpl, "ON") ||
        EQUAL(pszImpl, "1") || EQUAL(pszImpl, "TRUE") )
    {
        return GDALRasterBand::GetVirtualMemAuto(eRWFlag, pnPixelSpace,
                                                 pnLineSpace, papszOptions);
    }

    FlushCache();

    CPLVirtualMem *pVMem = CPLVirtualMemFileMapNew(
        fpRawL, nImgOffset, nSize,
        (eRWFlag == GF_Write) ? VIRTUALMEM_READWRITE : VIRTUALMEM_READONLY,
        nullptr, nullptr);
    if( pVMem == nullptr )
    {
        if( EQUAL(pszImpl, "NO") || EQUAL(pszImpl, "OFF") ||
            EQUAL(pszImpl, "0") || EQUAL(pszImpl, "FALSE") )
        {
            return nullptr;
        }
        return GDALRasterBand::GetVirtualMemAuto(eRWFlag, pnPixelSpace,
                                                 pnLineSpace, papszOptions);
    }

    *pnPixelSpace = nPixelOffset;
    *pnLineSpace = nLineOffset;
    return pVMem;
}

/************************************************************************/
/* ==================================================================== */
/*      RawDataset                                                      */
/* ==================================================================== */
/************************************************************************/

/************************************************************************/
/*                            RawDataset()                              */
/************************************************************************/

RawDataset::RawDataset() {}

/************************************************************************/
/*                           ~RawDataset()                              */
/************************************************************************/

// It's pure virtual function but must be defined, even if empty.
RawDataset::~RawDataset() {}

/************************************************************************/
/*                             IRasterIO()                              */
/*                                                                      */
/*      Multi-band raster io handler.                                   */
/************************************************************************/

CPLErr RawDataset::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 )

{
    const char* pszInterleave = nullptr;

    // The default GDALDataset::IRasterIO() implementation would go to
    // BlockBasedRasterIO if the dataset is interleaved. However if the
    // access pattern is compatible with DirectIO() we don't want to go
    // BlockBasedRasterIO, but rather used our optimized path in
    // RawRasterBand::IRasterIO().
    if (nXSize == nBufXSize && nYSize == nBufYSize && nBandCount > 1 &&
        (pszInterleave = GetMetadataItem("INTERLEAVE",
                                         "IMAGE_STRUCTURE")) != nullptr &&
        EQUAL(pszInterleave, "PIXEL"))
    {
        int iBandIndex = 0;
        for( ; iBandIndex < nBandCount; iBandIndex++ )
        {
            RawRasterBand *poBand = dynamic_cast<RawRasterBand *>(
                GetRasterBand(panBandMap[iBandIndex]));
            if( poBand == nullptr ||
                !poBand->CanUseDirectIO(nXOff, nYOff,
                                        nXSize, nYSize, eBufType, psExtraArg) )
            {
                break;
            }
        }
        if( iBandIndex == nBandCount )
        {
            GDALProgressFunc pfnProgressGlobal = psExtraArg->pfnProgress;
            void *pProgressDataGlobal = psExtraArg->pProgressData;

            CPLErr eErr = CE_None;
            for( iBandIndex = 0;
                 iBandIndex < nBandCount && eErr == CE_None;
                 iBandIndex++ )
            {
                GDALRasterBand *poBand = GetRasterBand(panBandMap[iBandIndex]);

                if (poBand == nullptr)
                {
                    eErr = CE_Failure;
                    break;
                }

                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->RasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
                                        static_cast<void *>(pabyBandData),
                                        nBufXSize, nBufYSize, eBufType,
                                        nPixelSpace, nLineSpace, psExtraArg);

                GDALDestroyScaledProgress(psExtraArg->pProgressData);
            }

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

            return eErr;
        }
    }

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


/************************************************************************/
/*                  RAWDatasetCheckMemoryUsage()                        */
/************************************************************************/

bool RAWDatasetCheckMemoryUsage(int nXSize, int nYSize, int nBands,
                                int nDTSize,
                                int nPixelOffset,
                                int nLineOffset,
                                vsi_l_offset nHeaderSize,
                                vsi_l_offset nBandOffset,
                                VSILFILE* fp)
{

    // Currently each RawRasterBand allocates nPixelOffset * nRasterXSize bytes
    // so for a pixel interleaved scheme, this will allocate lots of memory!
    // Actually this is quadratic in the number of bands!
    // Do a few sanity checks to avoid excessive memory allocation on
    // small files.
    // But ultimately we should fix RawRasterBand to have a shared buffer
    // among bands.
    const char* pszCheck = CPLGetConfigOption("RAW_CHECK_FILE_SIZE", nullptr);
    if( (nBands > 10 ||
         static_cast<vsi_l_offset>(nPixelOffset) * nXSize > 20000 ||
         (pszCheck && CPLTestBool(pszCheck))) &&
        !(pszCheck && !CPLTestBool(pszCheck)) )
    {
        vsi_l_offset nExpectedFileSize =
            nHeaderSize + nBandOffset * (nBands - 1) +
            (nYSize-1) * static_cast<vsi_l_offset>(nLineOffset) +
            (nXSize-1) * static_cast<vsi_l_offset>(nPixelOffset);
        CPL_IGNORE_RET_VAL( VSIFSeekL(fp, 0, SEEK_END) );
        vsi_l_offset nFileSize = VSIFTellL(fp);
        // Do not strictly compare against nExpectedFileSize, but use an arbitrary
        // 50% margin, since some raw formats such as ENVI
        // allow for sparse files (see https://github.com/OSGeo/gdal/issues/915)
        if( nFileSize < nExpectedFileSize / 2 )
        {
            CPLError(CE_Failure, CPLE_AppDefined, "Image file is too small");
            return false;
        }
    }

    // Currently each RawRasterBand need to allocate nLineSize
    GIntBig nLineSize =
        static_cast<GIntBig>(std::abs(nPixelOffset)) * (nXSize - 1) + nDTSize;
    constexpr int knMAX_BUFFER_MEM = INT_MAX / 4;
    if( nBands > 0 && nLineSize > knMAX_BUFFER_MEM / nBands )
    {
        CPLError(CE_Failure, CPLE_OutOfMemory, "Too much memory needed");
        return false;
    }

    return true;
}
