/******************************************************************************
 *
 * 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 spatialys.com>
 *
 * SPDX-License-Identifier: MIT
 ****************************************************************************/

#include "cpl_port.h"
#include "cpl_vax.h"
#include "rawdataset.h"

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

#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 "cpl_safemaths.hpp"
#include "gdal.h"
#include "gdal_priv.h"

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

RawRasterBand::RawRasterBand(GDALDataset *poDSIn, int nBandIn,
                             VSILFILE *fpRawLIn, vsi_l_offset nImgOffsetIn,
                             int nPixelOffsetIn, int nLineOffsetIn,
                             GDALDataType eDataTypeIn, int bNativeOrderIn,
                             OwnFP bOwnsFPIn)
    : RawRasterBand(poDSIn, nBandIn, fpRawLIn, nImgOffsetIn, nPixelOffsetIn,
                    nLineOffsetIn, eDataTypeIn,
#ifdef CPL_LSB
                    bNativeOrderIn ? ByteOrder::ORDER_LITTLE_ENDIAN
                                   : ByteOrder::ORDER_BIG_ENDIAN,
#else
                    bNativeOrderIn ? ByteOrder::ORDER_BIG_ENDIAN
                                   : ByteOrder::ORDER_LITTLE_ENDIAN,
#endif
                    bOwnsFPIn)
{
}

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

RawRasterBand::RawRasterBand(GDALDataset *poDSIn, int nBandIn,
                             VSILFILE *fpRawLIn, vsi_l_offset nImgOffsetIn,
                             int nPixelOffsetIn, int nLineOffsetIn,
                             GDALDataType eDataTypeIn, ByteOrder eByteOrderIn,
                             OwnFP bOwnsFPIn)
    : fpRawL(fpRawLIn), nImgOffset(nImgOffsetIn), nPixelOffset(nPixelOffsetIn),
      nLineOffset(nLineOffsetIn), eByteOrder(eByteOrderIn),
      bOwnsFP(bOwnsFPIn == OwnFP::YES)
{
    poDS = poDSIn;
    nBand = nBandIn;
    eDataType = eDataTypeIn;
    nRasterXSize = poDSIn->GetRasterXSize();
    nRasterYSize = poDSIn->GetRasterYSize();

    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),
             static_cast<int>(eByteOrder));

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

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

/************************************************************************/
/*                          RawRasterBand::Create()                     */
/************************************************************************/

std::unique_ptr<RawRasterBand>
RawRasterBand::Create(GDALDataset *poDSIn, int nBandIn, VSILFILE *fpRawLIn,
                      vsi_l_offset nImgOffsetIn, int nPixelOffsetIn,
                      int nLineOffsetIn, GDALDataType eDataTypeIn,
                      ByteOrder eByteOrderIn, OwnFP bOwnsFPIn)
{
    auto poBand = std::make_unique<RawRasterBand>(
        poDSIn, nBandIn, fpRawLIn, nImgOffsetIn, nPixelOffsetIn, nLineOffsetIn,
        eDataTypeIn, eByteOrderIn, bOwnsFPIn);
    if (!poBand->IsValid())
        return nullptr;
    return poBand;
}

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

RawRasterBand::RawRasterBand(VSILFILE *fpRawLIn, vsi_l_offset nImgOffsetIn,
                             int nPixelOffsetIn, int nLineOffsetIn,
                             GDALDataType eDataTypeIn, int bNativeOrderIn,
                             int nXSize, int nYSize, OwnFP bOwnsFPIn)
    : RawRasterBand(fpRawLIn, nImgOffsetIn, nPixelOffsetIn, nLineOffsetIn,
                    eDataTypeIn,
#ifdef CPL_LSB
                    bNativeOrderIn ? ByteOrder::ORDER_LITTLE_ENDIAN
                                   : ByteOrder::ORDER_BIG_ENDIAN,
#else
                    bNativeOrderIn ? ByteOrder::ORDER_BIG_ENDIAN
                                   : ByteOrder::ORDER_LITTLE_ENDIAN,
#endif
                    nXSize, nYSize, bOwnsFPIn)
{
}

/************************************************************************/
/*                          RawRasterBand::Create()                     */
/************************************************************************/

std::unique_ptr<RawRasterBand>
RawRasterBand::Create(VSILFILE *fpRawIn, vsi_l_offset nImgOffsetIn,
                      int nPixelOffsetIn, int nLineOffsetIn,
                      GDALDataType eDataTypeIn, ByteOrder eByteOrderIn,
                      int nXSizeIn, int nYSizeIn, OwnFP bOwnsFPIn)
{
    auto poBand = std::make_unique<RawRasterBand>(
        fpRawIn, nImgOffsetIn, nPixelOffsetIn, nLineOffsetIn, eDataTypeIn,
        eByteOrderIn, nXSizeIn, nYSizeIn, bOwnsFPIn);
    if (!poBand->IsValid())
        return nullptr;
    return poBand;
}

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

RawRasterBand::RawRasterBand(VSILFILE *fpRawLIn, vsi_l_offset nImgOffsetIn,
                             int nPixelOffsetIn, int nLineOffsetIn,
                             GDALDataType eDataTypeIn, ByteOrder eByteOrderIn,
                             int nXSize, int nYSize, OwnFP bOwnsFPIn)
    : fpRawL(fpRawLIn), nImgOffset(nImgOffsetIn), nPixelOffset(nPixelOffsetIn),
      nLineOffset(nLineOffsetIn), eByteOrder(eByteOrderIn),
      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), static_cast<int>(eByteOrder));

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

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

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

void RawRasterBand::Initialize()

{
    vsi_l_offset nSmallestOffset = nImgOffset;
    vsi_l_offset nLargestOffset = nImgOffset;
    if (nLineOffset < 0)
    {
        const auto nDelta =
            static_cast<vsi_l_offset>(-static_cast<GIntBig>(nLineOffset)) *
            (nRasterYSize - 1);
        if (nDelta > nImgOffset)
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "Inconsistent nLineOffset, nRasterYSize and nImgOffset");
            return;
        }
        nSmallestOffset -= nDelta;
    }
    else
    {
        if (nImgOffset >
            std::numeric_limits<vsi_l_offset>::max() -
                static_cast<vsi_l_offset>(nLineOffset) * (nRasterYSize - 1))
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "Inconsistent nLineOffset, nRasterYSize and nImgOffset");
            return;
        }
        nLargestOffset +=
            static_cast<vsi_l_offset>(nLineOffset) * (nRasterYSize - 1);
    }
    if (nPixelOffset < 0)
    {
        if (static_cast<vsi_l_offset>(-static_cast<GIntBig>(nPixelOffset)) *
                (nRasterXSize - 1) >
            nSmallestOffset)
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "Inconsistent nPixelOffset, nRasterXSize and nImgOffset");
            return;
        }
    }
    else
    {
        if (nLargestOffset >
            std::numeric_limits<vsi_l_offset>::max() -
                static_cast<vsi_l_offset>(nPixelOffset) * (nRasterXSize - 1))
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "Inconsistent nPixelOffset, nRasterXSize and nImgOffset");
            return;
        }
        nLargestOffset +=
            static_cast<vsi_l_offset>(nPixelOffset) * (nRasterXSize - 1);
    }
    if (nLargestOffset > static_cast<vsi_l_offset>(GINTBIG_MAX))
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Too big largest offset");
        return;
    }

    const int nDTSize = GDALGetDataTypeSizeBytes(GetRasterDataType());

    // Allocate working scanline.
    const bool bIsBIP = IsBIP();
    if (bIsBIP)
    {
        if (nBand == 1)
        {
            nLineSize = nPixelOffset * nBlockXSize;
            pLineBuffer = VSIMalloc(nLineSize);
        }
        else
        {
            // Band > 1 : share the same buffer as band 1
            pLineBuffer = nullptr;
            const auto poFirstBand =
                cpl::down_cast<RawRasterBand *>(poDS->GetRasterBand(1));
            if (poFirstBand->pLineBuffer != nullptr)
                pLineStart = static_cast<char *>(poFirstBand->pLineBuffer) +
                             (nBand - 1) * nDTSize;
            return;
        }
    }
    else 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)
    {
        nLineSize = 0;
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Could not allocate line buffer: "
                 "nPixelOffset=%d, nBlockXSize=%d",
                 nPixelOffset, nBlockXSize);
        return;
    }

    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(true);

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

    CPLFree(pLineBuffer);
}

/************************************************************************/
/*                              IsBIP()                                 */
/************************************************************************/

bool RawRasterBand::IsBIP() const
{
    const int nDTSize = GDALGetDataTypeSizeBytes(eDataType);
    const bool bIsRawDataset = dynamic_cast<RawDataset *>(poDS) != nullptr;
    if (bIsRawDataset && nPixelOffset > nDTSize &&
        nLineOffset == static_cast<int64_t>(nPixelOffset) * nRasterXSize)
    {
        if (nBand == 1)
        {
            return true;
        }
        const auto poFirstBand =
            dynamic_cast<RawRasterBand *>(poDS->GetRasterBand(1));
        if (poFirstBand && eDataType == poFirstBand->eDataType &&
            eByteOrder == poFirstBand->eByteOrder &&
            nPixelOffset == poFirstBand->nPixelOffset &&
            nLineOffset == poFirstBand->nLineOffset &&
            nImgOffset == poFirstBand->nImgOffset +
                              static_cast<vsi_l_offset>(nBand - 1) * nDTSize)
        {
            return true;
        }
    }
    return false;
}

/************************************************************************/
/*                             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(bool bAtClosing)

{
    CPLErr eErr = GDALRasterBand::FlushCache(bAtClosing);
    if (eErr != CE_None)
    {
        bNeedFileFlush = false;
        return eErr;
    }

    RawRasterBand *masterBand = this;
    if (nBand > 1 && poDS != nullptr && poDS->GetRasterCount() > 1 && IsBIP())
    {
        // can't be null as IsBIP() checks that the first band is not null,
        // which could happen during dataset destruction.
        masterBand = cpl::down_cast<RawRasterBand *>(poDS->GetRasterBand(1));
    }

    if (!masterBand->FlushCurrentLine(false))
    {
        masterBand->bNeedFileFlush = false;
        bNeedFileFlush = false;
        return CE_Failure;
    }

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

        masterBand->bNeedFileFlush = false;
        bNeedFileFlush = false;
        if (nRet < 0)
            return CE_Failure;
    }

    bNeedFileFlush = false;

    return CE_None;
}

/************************************************************************/
/*                      NeedsByteOrderChange()                          */
/************************************************************************/

bool RawRasterBand::NeedsByteOrderChange() const
{
#ifdef CPL_LSB
    return eDataType != GDT_Byte &&
           eByteOrder != RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN;
#else
    return eDataType != GDT_Byte &&
           eByteOrder != RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN;
#endif
}

/************************************************************************/
/*                          DoByteSwap()                                */
/************************************************************************/

void RawRasterBand::DoByteSwap(void *pBuffer, size_t nValues, int nByteSkip,
                               bool bDiskToCPU) const
{
    if (eByteOrder != RawRasterBand::ByteOrder::ORDER_VAX)
    {
        if (GDALDataTypeIsComplex(eDataType))
        {
            const int nWordSize = GDALGetDataTypeSize(eDataType) / 16;
            GDALSwapWordsEx(pBuffer, nWordSize, nValues, nByteSkip);
            GDALSwapWordsEx(static_cast<GByte *>(pBuffer) + nWordSize,
                            nWordSize, nValues, nByteSkip);
        }
        else
        {
            GDALSwapWordsEx(pBuffer, GDALGetDataTypeSizeBytes(eDataType),
                            nValues, nByteSkip);
        }
    }
    else if (eDataType == GDT_Float32 || eDataType == GDT_CFloat32)
    {
        GByte *pPtr = static_cast<GByte *>(pBuffer);
        for (int k = 0; k < 2; k++)
        {
            if (bDiskToCPU)
            {
                for (size_t i = 0; i < nValues; i++, pPtr += nByteSkip)
                {
                    CPLVaxToIEEEFloat(pPtr);
                }
            }
            else
            {
                for (size_t i = 0; i < nValues; i++, pPtr += nByteSkip)
                {
                    CPLIEEEToVaxFloat(pPtr);
                }
            }
            if (k == 0 && eDataType == GDT_CFloat32)
                pPtr = static_cast<GByte *>(pBuffer) + sizeof(float);
            else
                break;
        }
    }
    else if (eDataType == GDT_Float64 || eDataType == GDT_CFloat64)
    {
        GByte *pPtr = static_cast<GByte *>(pBuffer);
        for (int k = 0; k < 2; k++)
        {
            if (bDiskToCPU)
            {
                for (size_t i = 0; i < nValues; i++, pPtr += nByteSkip)
                {
                    CPLVaxToIEEEDouble(pPtr);
                }
            }
            else
            {
                for (size_t i = 0; i < nValues; i++, pPtr += nByteSkip)
                {
                    CPLIEEEToVaxDouble(pPtr);
                }
            }
            if (k == 0 && eDataType == GDT_CFloat64)
                pPtr = static_cast<GByte *>(pBuffer) + sizeof(double);
            else
                break;
        }
    }
}

/************************************************************************/
/*                         ComputeFileOffset()                          */
/************************************************************************/

vsi_l_offset RawRasterBand::ComputeFileOffset(int iLine) const
{
    // Write formulas such that unsigned int overflow doesn't occur
    vsi_l_offset nOffset = nImgOffset;
    if (nLineOffset >= 0)
    {
        nOffset += static_cast<GUIntBig>(nLineOffset) * iLine;
    }
    else
    {
        nOffset -=
            static_cast<GUIntBig>(-static_cast<GIntBig>(nLineOffset)) * iLine;
    }
    if (nPixelOffset < 0)
    {
        const GUIntBig nPixelOffsetToSubtract =
            static_cast<GUIntBig>(-static_cast<GIntBig>(nPixelOffset)) *
            (nBlockXSize - 1);
        nOffset -= nPixelOffsetToSubtract;
    }
    return nOffset;
}

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

CPLErr RawRasterBand::AccessLine(int iLine)

{
    if (pLineBuffer == nullptr)
    {
        if (nBand > 1 && pLineStart != nullptr)
        {
            // BIP interleaved
            auto poFirstBand =
                cpl::down_cast<RawRasterBand *>(poDS->GetRasterBand(1));
            CPLAssert(poFirstBand);
            return poFirstBand->AccessLine(iLine);
        }
        return CE_Failure;
    }

    if (nLoadedScanline == iLine)
    {
        return CE_None;
    }

    if (!FlushCurrentLine(false))
    {
        return CE_Failure;
    }

    // Figure out where to start reading.
    const vsi_l_offset nReadStart = ComputeFileOffset(iLine);

    // 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 (NeedsByteOrderChange())
    {
        if (poDS != nullptr && poDS->GetRasterCount() > 1 && IsBIP())
        {
            const int nDTSize = GDALGetDataTypeSizeBytes(eDataType);
            DoByteSwap(pLineBuffer,
                       static_cast<size_t>(nBlockXSize) *
                           poDS->GetRasterCount(),
                       nDTSize, true);
        }
        else
            DoByteSwap(pLineBuffer, nBlockXSize, std::abs(nPixelOffset), true);
    }

    nLoadedScanline = iLine;

    return CE_None;
}

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

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

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

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

    // Pre-cache block cache of other bands
    if (poDS != nullptr && poDS->GetRasterCount() > 1 && IsBIP())
    {
        for (int iBand = 1; iBand <= poDS->GetRasterCount(); iBand++)
        {
            if (iBand != nBand)
            {
                auto poOtherBand =
                    cpl::down_cast<RawRasterBand *>(poDS->GetRasterBand(iBand));
                GDALRasterBlock *poBlock =
                    poOtherBand->TryGetLockedBlockRef(0, nBlockYOff);
                if (poBlock != nullptr)
                {
                    poBlock->DropLock();
                    continue;
                }
                poBlock = poOtherBand->GetLockedBlockRef(0, nBlockYOff, true);
                if (poBlock != nullptr)
                {
                    GDALCopyWords(poOtherBand->pLineStart, eDataType,
                                  nPixelOffset, poBlock->GetDataRef(),
                                  eDataType, nDTSize, nBlockXSize);
                    poBlock->DropLock();
                }
            }
        }
    }

    return eErr;
}

/************************************************************************/
/*                           BIPWriteBlock()                            */
/************************************************************************/

CPLErr RawRasterBand::BIPWriteBlock(int nBlockYOff, int nCallingBand,
                                    const void *pImage)
{
    if (nLoadedScanline != nBlockYOff)
    {
        if (!FlushCurrentLine(false))
            return CE_Failure;
    }

    const int nBands = poDS->GetRasterCount();
    std::vector<GDALRasterBlock *> apoBlocks(nBands);
    bool bAllBlocksDirty = true;
    const int nDTSize = GDALGetDataTypeSizeBytes(eDataType);

    /* -------------------------------------------------------------------- */
    /*     If all blocks are cached and dirty then we do not need to reload */
    /*     the scanline from disk                                           */
    /* -------------------------------------------------------------------- */
    for (int iBand = 0; iBand < nBands; ++iBand)
    {
        if (iBand + 1 != nCallingBand)
        {
            apoBlocks[iBand] =
                cpl::down_cast<RawRasterBand *>(poDS->GetRasterBand(iBand + 1))
                    ->TryGetLockedBlockRef(0, nBlockYOff);

            if (apoBlocks[iBand] == nullptr)
            {
                bAllBlocksDirty = false;
            }
            else if (!apoBlocks[iBand]->GetDirty())
            {
                apoBlocks[iBand]->DropLock();
                apoBlocks[iBand] = nullptr;
                bAllBlocksDirty = false;
            }
        }
        else
            apoBlocks[iBand] = nullptr;
    }

    if (!bAllBlocksDirty)
    {
        // We only to read the scanline if we don't have data for all bands.
        if (AccessLine(nBlockYOff) != CE_None)
        {
            for (int iBand = 0; iBand < nBands; ++iBand)
            {
                if (apoBlocks[iBand] != nullptr)
                    apoBlocks[iBand]->DropLock();
            }
            return CE_Failure;
        }
    }

    for (int iBand = 0; iBand < nBands; ++iBand)
    {
        const GByte *pabyThisImage = nullptr;
        GDALRasterBlock *poBlock = nullptr;

        if (iBand + 1 == nCallingBand)
        {
            pabyThisImage = static_cast<const GByte *>(pImage);
        }
        else
        {
            poBlock = apoBlocks[iBand];
            if (poBlock == nullptr)
                continue;

            if (!poBlock->GetDirty())
            {
                poBlock->DropLock();
                continue;
            }

            pabyThisImage = static_cast<const GByte *>(poBlock->GetDataRef());
        }

        GByte *pabyOut = static_cast<GByte *>(pLineStart) + iBand * nDTSize;

        GDALCopyWords(pabyThisImage, eDataType, nDTSize, pabyOut, eDataType,
                      nPixelOffset, nBlockXSize);

        if (poBlock != nullptr)
        {
            poBlock->MarkClean();
            poBlock->DropLock();
        }
    }

    nLoadedScanline = nBlockYOff;
    bLoadedScanlineDirty = true;

    if (bAllBlocksDirty)
    {
        return FlushCurrentLine(true) ? CE_None : CE_Failure;
    }

    bNeedFileFlush = true;
    return CE_None;
}

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

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

    if (pLineBuffer == nullptr)
    {
        if (poDS != nullptr && poDS->GetRasterCount() > 1 && IsBIP())
        {
            auto poFirstBand =
                (nBand == 1)
                    ? this
                    : cpl::down_cast<RawRasterBand *>(poDS->GetRasterBand(1));
            CPLAssert(poFirstBand);
            return poFirstBand->BIPWriteBlock(nBlockYOff, nBand, pImage);
        }

        return CE_Failure;
    }

    if (nLoadedScanline != nBlockYOff)
    {
        if (!FlushCurrentLine(false))
            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;
    const int nDTSize = GDALGetDataTypeSizeBytes(eDataType);
    if (std::abs(nPixelOffset) > nDTSize)
        eErr = AccessLine(nBlockYOff);

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

    nLoadedScanline = nBlockYOff;
    bLoadedScanlineDirty = true;

    return eErr == CE_None && FlushCurrentLine(true) ? CE_None : CE_Failure;
}

/************************************************************************/
/*                         FlushCurrentLine()                           */
/************************************************************************/

bool RawRasterBand::FlushCurrentLine(bool bNeedUsableBufferAfter)
{
    if (!bLoadedScanlineDirty)
        return true;

    bLoadedScanlineDirty = false;

    bool ok = true;

    // Byte swap (if necessary) back into disk order before writing.
    if (NeedsByteOrderChange())
    {
        if (poDS != nullptr && poDS->GetRasterCount() > 1 && IsBIP())
        {
            const int nDTSize = GDALGetDataTypeSizeBytes(eDataType);
            DoByteSwap(pLineBuffer,
                       static_cast<size_t>(nBlockXSize) *
                           poDS->GetRasterCount(),
                       nDTSize, false);
        }
        else
            DoByteSwap(pLineBuffer, nBlockXSize, std::abs(nPixelOffset), false);
    }

    // Figure out where to start reading.
    const vsi_l_offset nWriteStart = ComputeFileOffset(nLoadedScanline);

    // 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.",
                 nLoadedScanline, nWriteStart);

        ok = false;
    }

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

        ok = false;
    }

    // Byte swap (if necessary) back into machine order so the
    // buffer is still usable for reading purposes, unless this is not needed.
    if (bNeedUsableBufferAfter && NeedsByteOrderChange())
    {
        if (poDS != nullptr && poDS->GetRasterCount() > 1 && IsBIP())
        {
            const int nDTSize = GDALGetDataTypeSizeBytes(eDataType);
            DoByteSwap(pLineBuffer,
                       static_cast<size_t>(nBlockXSize) *
                           poDS->GetRasterCount(),
                       nDTSize, true);
        }
        else
            DoByteSwap(pLineBuffer, nBlockXSize, std::abs(nPixelOffset), true);
    }

    bNeedFileFlush = true;

    return ok;
}

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

CPLErr RawRasterBand::AccessBlock(vsi_l_offset nBlockOff, size_t nBlockSize,
                                  void *pData, size_t nValues)
{
    // 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);
    }

    // Byte swap the interesting data, if required.
    if (NeedsByteOrderChange())
    {
        DoByteSwap(pData, nValues, std::abs(nPixelOffset), true);
    }

    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)
{
    bool result = FALSE;

    // Use direct IO without caching if:
    //
    // GDAL_ONE_BIG_READ is enabled
    //
    // or
    //
    // the raster width is so small that the cost of a GDALRasterBlock is
    // significant
    //
    // 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;
    }

    RawDataset *rawDataset = dynamic_cast<RawDataset *>(this->GetDataset());
    int oldCachedCPLOneBigReadOption = 0;
    if (rawDataset != nullptr)
    {
        oldCachedCPLOneBigReadOption = rawDataset->cachedCPLOneBigReadOption;
    }

    const char *pszGDAL_ONE_BIG_READ =
        !(oldCachedCPLOneBigReadOption & 0xff)  // Test valid
            ? CPLGetConfigOption("GDAL_ONE_BIG_READ", nullptr)
            : (((oldCachedCPLOneBigReadOption >> 8) & 0xff) == 0)   ? "0"
              : (((oldCachedCPLOneBigReadOption >> 8) & 0xff) == 1) ? "1"
                                                                    : nullptr;
    if (pszGDAL_ONE_BIG_READ == nullptr)
    {
        const int newCachedCPLOneBigReadOption = (0xff << 8) | 1;
        if (rawDataset != nullptr)
        {
            rawDataset->cachedCPLOneBigReadOption.compare_exchange_strong(
                oldCachedCPLOneBigReadOption, newCachedCPLOneBigReadOption);
        }

        if (nRasterXSize <= 64)
        {
            return TRUE;
        }

        if (nLineSize < 50000 || nXSize > nLineSize / nPixelOffset / 5 * 2 ||
            IsSignificantNumberOfLinesLoaded(nYOff, nYSize))
        {
            return FALSE;
        }
        return TRUE;
    }

    result = CPLTestBool(pszGDAL_ONE_BIG_READ);

    const int newCachedCPLOneBigReadOption = (result ? 1 : 0) << 8 | 1;
    if (rawDataset != nullptr)
    {
        rawDataset->cachedCPLOneBigReadOption.compare_exchange_strong(
            oldCachedCPLOneBigReadOption, newCachedCPLOneBigReadOption);
    }

    return result;
}

/************************************************************************/
/*                             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");

    if (pLineBuffer == nullptr)
    {
        if (poDS != nullptr && poDS->GetRasterCount() > 1 && IsBIP())
        {
            auto poFirstBand =
                (nBand == 1)
                    ? this
                    : cpl::down_cast<RawRasterBand *>(poDS->GetRasterBand(1));
            CPLAssert(poFirstBand);
            if (poFirstBand->bNeedFileFlush)
                RawRasterBand::FlushCache(false);
        }
    }
    if (bNeedFileFlush)
        RawRasterBand::FlushCache(false);

    // Needed for ICC fast math approximations
    constexpr double EPS = 1e-10;

    // 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 &&
            nLineOffset == nPixelOffset * nXSize)
        {
            vsi_l_offset nOffset = nImgOffset;
            if (nLineOffset >= 0)
                nOffset += nYOff * static_cast<vsi_l_offset>(nLineOffset);
            else
                nOffset -= nYOff * static_cast<vsi_l_offset>(-nLineOffset);

            const size_t nValues = static_cast<size_t>(nXSize) * nYSize;
            const size_t nBytesToRead = nValues * nBandDataSize;
            AccessBlock(nOffset, nBytesToRead, pData, nValues);
        }
        // 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 - 1) +
                GDALGetDataTypeSizeBytes(eDataType);
            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 nLine =
                    static_cast<vsi_l_offset>(nYOff) +
                    static_cast<vsi_l_offset>(iLine * dfSrcYInc + EPS);
                vsi_l_offset nOffset = nImgOffset;
                if (nLineOffset >= 0)
                    nOffset += nLine * nLineOffset;
                else
                    nOffset -= nLine * static_cast<vsi_l_offset>(-nLineOffset);
                if (nPixelOffset >= 0)
                    nOffset += nXOff * static_cast<vsi_l_offset>(nPixelOffset);
                else
                    nOffset -= nXOff * static_cast<vsi_l_offset>(-nPixelOffset);
                AccessBlock(nOffset, nBytesToRW, pabyData, nXSize);
                // 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) + 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 + EPS) *
                                           nPixelOffset,
                            eDataType, nPixelOffset,
                            static_cast<GByte *>(pData) + iLine * nLineSpace +
                                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 &&
            nLineOffset == nPixelOffset * nXSize)
        {
            const size_t nValues = static_cast<size_t>(nXSize) * nYSize;

            // Byte swap the data buffer, if required.
            if (NeedsByteOrderChange())
            {
                DoByteSwap(pData, nValues, std::abs(nPixelOffset), false);
            }

            // Seek to the correct block.
            vsi_l_offset nOffset = nImgOffset;
            if (nLineOffset >= 0)
                nOffset += nYOff * static_cast<vsi_l_offset>(nLineOffset);
            else
                nOffset -= nYOff * static_cast<vsi_l_offset>(-nLineOffset);

            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 = nValues * 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 (NeedsByteOrderChange())
            {
                DoByteSwap(pData, nValues, std::abs(nPixelOffset), true);
            }
        }
        // 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 - 1) +
                GDALGetDataTypeSizeBytes(eDataType);
            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 nLine =
                    static_cast<vsi_l_offset>(nYOff) +
                    static_cast<vsi_l_offset>(iLine * dfSrcYInc + EPS);
                vsi_l_offset nOffset = nImgOffset;
                if (nLineOffset >= 0)
                    nOffset += nLine * static_cast<vsi_l_offset>(nLineOffset);
                else
                    nOffset -= nLine * static_cast<vsi_l_offset>(-nLineOffset);
                if (nPixelOffset >= 0)
                    nOffset += nXOff * static_cast<vsi_l_offset>(nPixelOffset);
                else
                    nOffset -= nXOff * static_cast<vsi_l_offset>(-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(nOffset, nBytesToRW, pabyData, nXSize);

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

                // Byte swap the data buffer, if required.
                if (NeedsByteOrderChange())
                {
                    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(nOffset, SEEK_SET) == -1)
                {
                    CPLError(CE_Failure, CPLE_FileIO,
                             "Failed to seek to " CPL_FRMT_GUIB " to read.",
                             nOffset);
                    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 (NeedsByteOrderChange())
                {
                    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);
                    }
                }
            }

            bNeedFileFlush = 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 +
        static_cast<vsi_l_offset>(nRasterXSize - 1) * nPixelOffset +
        GDALGetDataTypeSizeBytes(eDataType);

    const char *pszImpl = CSLFetchNameValueDef(
        papszOptions, "USE_DEFAULT_IMPLEMENTATION", "AUTO");
    if (VSIFGetNativeFileDescriptorL(fpRawL) == nullptr ||
        !CPLIsVirtualMemFileMapAvailable() || NeedsByteOrderChange() ||
        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(false);

    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, BANDMAP_TYPE panBandMap,
                             GSpacing nPixelSpace, GSpacing nLineSpace,
                             GSpacing nBandSpace,
                             GDALRasterIOExtraArg *psExtraArg)

{
    const char *pszInterleave = nullptr;

    this->ClearCachedConfigOption();

    // 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"))
    {
        RawRasterBand *poFirstBand = nullptr;
        bool bCanDirectAccessToBIPDataset =
            eRWFlag == GF_Read && nBandCount == nBands;
        bool bCanUseDirectIO = true;
        for (int iBandIndex = 0; iBandIndex < nBandCount; iBandIndex++)
        {
            RawRasterBand *poBand = dynamic_cast<RawRasterBand *>(
                GetRasterBand(panBandMap[iBandIndex]));
            if (poBand == nullptr)
            {
                bCanDirectAccessToBIPDataset = false;
                bCanUseDirectIO = false;
                break;
            }
            else if (!poBand->CanUseDirectIO(nXOff, nYOff, nXSize, nYSize,
                                             eBufType, psExtraArg))
            {
                bCanUseDirectIO = false;
                if (!bCanDirectAccessToBIPDataset)
                    break;
            }
            if (bCanDirectAccessToBIPDataset)
            {
                const auto eDT = poBand->GetRasterDataType();
                const int nDTSize = GDALGetDataTypeSizeBytes(eDT);
                if (poBand->bNeedFileFlush || poBand->bLoadedScanlineDirty ||
                    poBand->HasDirtyBlocks() ||
                    panBandMap[iBandIndex] != iBandIndex + 1 ||
                    nPixelSpace != poBand->nPixelOffset)
                {
                    bCanDirectAccessToBIPDataset = false;
                }
                else
                {
                    if (poFirstBand == nullptr)
                    {
                        poFirstBand = poBand;
                        bCanDirectAccessToBIPDataset =
                            eDT == eBufType && nBandSpace == nDTSize &&
                            poFirstBand->nPixelOffset ==
                                cpl::fits_on<int>(nBands * nDTSize);
                    }
                    else
                    {
                        bCanDirectAccessToBIPDataset =
                            eDT == poFirstBand->GetRasterDataType() &&
                            poBand->fpRawL == poFirstBand->fpRawL &&
                            poBand->nImgOffset ==
                                poFirstBand->nImgOffset +
                                    cpl::fits_on<int>(iBandIndex * nDTSize) &&
                            poBand->nPixelOffset == poFirstBand->nPixelOffset &&
                            poBand->nLineOffset == poFirstBand->nLineOffset &&
                            poBand->eByteOrder == poFirstBand->eByteOrder;
                    }
                }
            }
        }
        if (bCanDirectAccessToBIPDataset)
        {
            CPLDebugOnly("GDALRaw", "Direct access to BIP dataset");
            const auto eDT = poFirstBand->GetRasterDataType();
            const int nDTSize = GDALGetDataTypeSizeBytes(eDT);
            const bool bNeedsByteOrderChange =
                poFirstBand->NeedsByteOrderChange();
            for (int iY = 0; iY < nYSize; ++iY)
            {
                GByte *pabyOut = static_cast<GByte *>(pData) + iY * nLineSpace;
                VSIFSeekL(poFirstBand->fpRawL,
                          poFirstBand->nImgOffset +
                              static_cast<vsi_l_offset>(nYOff + iY) *
                                  poFirstBand->nLineOffset +
                              static_cast<vsi_l_offset>(nXOff) *
                                  poFirstBand->nPixelOffset,
                          SEEK_SET);
                if (VSIFReadL(pabyOut,
                              static_cast<size_t>(nXSize * nPixelSpace), 1,
                              poFirstBand->fpRawL) != 1)
                {
                    return CE_Failure;
                }
                if (bNeedsByteOrderChange)
                {
                    poFirstBand->DoByteSwap(
                        pabyOut, static_cast<size_t>(nXSize) * nBands, nDTSize,
                        true);
                }
            }
            return CE_None;
        }
        else if (bCanUseDirectIO)
        {
            GDALProgressFunc pfnProgressGlobal = psExtraArg->pfnProgress;
            void *pProgressDataGlobal = psExtraArg->pProgressData;

            CPLErr eErr = CE_None;
            for (int 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)
{
    const GIntBig nTotalBufferSize =
        nPixelOffset == static_cast<GIntBig>(nDTSize) * nBands
            ?  // BIP ?
            static_cast<GIntBig>(nPixelOffset) * nXSize
            : static_cast<GIntBig>(std::abs(nPixelOffset)) * nXSize * nBands;

    // 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 || nTotalBufferSize > 20000 ||
         (pszCheck && CPLTestBool(pszCheck))) &&
        !(pszCheck && !CPLTestBool(pszCheck)))
    {
        vsi_l_offset nExpectedFileSize;
        try
        {
            nExpectedFileSize =
                (CPLSM(static_cast<GUInt64>(nHeaderSize)) +
                 CPLSM(static_cast<GUInt64>(nBandOffset)) *
                     CPLSM(static_cast<GUInt64>(nBands - 1)) +
                 (nLineOffset >= 0
                      ? CPLSM(static_cast<GUInt64>(nYSize - 1)) *
                            CPLSM(static_cast<GUInt64>(nLineOffset))
                      : CPLSM(static_cast<GUInt64>(0))) +
                 (nPixelOffset >= 0
                      ? CPLSM(static_cast<GUInt64>(nXSize - 1)) *
                            CPLSM(static_cast<GUInt64>(nPixelOffset))
                      : CPLSM(static_cast<GUInt64>(0))))
                    .v();
        }
        catch (...)
        {
            CPLError(CE_Failure, CPLE_AppDefined, "Image file is too small");
            return false;
        }
        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;
        }
    }

#if SIZEOF_VOIDP == 8
    const char *pszDefault = "1024";
#else
    const char *pszDefault = "512";
#endif
    constexpr int MB_IN_BYTES = 1024 * 1024;
    const GIntBig nMAX_BUFFER_MEM =
        static_cast<GIntBig>(
            atoi(CPLGetConfigOption("RAW_MEM_ALLOC_LIMIT_MB", pszDefault))) *
        MB_IN_BYTES;
    if (nTotalBufferSize > nMAX_BUFFER_MEM)
    {
        CPLError(
            CE_Failure, CPLE_OutOfMemory,
            CPL_FRMT_GIB
            " MB of RAM would be needed to open the dataset. If you are "
            "comfortable with this, you can set the RAW_MEM_ALLOC_LIMIT_MB "
            "configuration option to that value or above",
            (nTotalBufferSize + MB_IN_BYTES - 1) / MB_IN_BYTES);
        return false;
    }

    return true;
}

/************************************************************************/
/*                        GetRawBinaryLayout()                          */
/************************************************************************/

bool RawDataset::GetRawBinaryLayout(GDALDataset::RawBinaryLayout &sLayout)
{
    vsi_l_offset nImgOffset = 0;
    GIntBig nBandOffset = 0;
    int nPixelOffset = 0;
    int nLineOffset = 0;
    RawRasterBand::ByteOrder eByteOrder =
        RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN;
    GDALDataType eDT = GDT_Unknown;
    for (int i = 1; i <= nBands; i++)
    {
        auto poBand = dynamic_cast<RawRasterBand *>(GetRasterBand(i));
        if (poBand == nullptr)
            return false;
        if (i == 1)
        {
            nImgOffset = poBand->nImgOffset;
            nPixelOffset = poBand->nPixelOffset;
            nLineOffset = poBand->nLineOffset;
            eByteOrder = poBand->eByteOrder;
            if (eByteOrder == RawRasterBand::ByteOrder::ORDER_VAX)
                return false;
            eDT = poBand->GetRasterDataType();
        }
        else if (nPixelOffset != poBand->nPixelOffset ||
                 nLineOffset != poBand->nLineOffset ||
                 eByteOrder != poBand->eByteOrder ||
                 eDT != poBand->GetRasterDataType())
        {
            return false;
        }
        else if (i == 2)
        {
            nBandOffset = static_cast<GIntBig>(poBand->nImgOffset) -
                          static_cast<GIntBig>(nImgOffset);
        }
        else if (nBandOffset * (i - 1) !=
                 static_cast<GIntBig>(poBand->nImgOffset) -
                     static_cast<GIntBig>(nImgOffset))
        {
            return false;
        }
    }

    sLayout.eInterleaving = RawBinaryLayout::Interleaving::UNKNOWN;
    const int nDTSize = GDALGetDataTypeSizeBytes(eDT);
    if (nBands > 1)
    {
        if (nPixelOffset == nBands * nDTSize &&
            nLineOffset == nPixelOffset * nRasterXSize &&
            nBandOffset == nDTSize)
        {
            sLayout.eInterleaving = RawBinaryLayout::Interleaving::BIP;
        }
        else if (nPixelOffset == nDTSize &&
                 nLineOffset == nDTSize * nBands * nRasterXSize &&
                 nBandOffset == static_cast<GIntBig>(nDTSize) * nRasterXSize)
        {
            sLayout.eInterleaving = RawBinaryLayout::Interleaving::BIL;
        }
        else if (nPixelOffset == nDTSize &&
                 nLineOffset == nDTSize * nRasterXSize &&
                 nBandOffset ==
                     static_cast<GIntBig>(nLineOffset) * nRasterYSize)
        {
            sLayout.eInterleaving = RawBinaryLayout::Interleaving::BSQ;
        }
    }

    sLayout.eDataType = eDT;
    sLayout.bLittleEndianOrder =
        eByteOrder == RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN;
    sLayout.nImageOffset = nImgOffset;
    sLayout.nPixelOffset = nPixelOffset;
    sLayout.nLineOffset = nLineOffset;
    sLayout.nBandOffset = nBandOffset;

    return true;
}

/************************************************************************/
/*                        ClearCachedConfigOption()                     */
/************************************************************************/

void RawDataset::ClearCachedConfigOption(void)
{
    cachedCPLOneBigReadOption = 0;
}
