/******************************************************************************
 * $Id$
 *
 * Project:  Arc/Info Binary Grid Translator
 * Purpose:  Grid file reading code.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 1999, Frank Warmerdam
 * Copyright (c) 2007-2010, Even Rouault <even dot rouault at spatialys.com>
 *
 * SPDX-License-Identifier: MIT
 ****************************************************************************/

#include "aigrid.h"

#ifndef CPL_IGNORE_RET_VAL_INT_defined
#define CPL_IGNORE_RET_VAL_INT_defined

CPL_INLINE static void CPL_IGNORE_RET_VAL_INT(CPL_UNUSED int unused)
{
}
#endif

/************************************************************************/
/*                    AIGProcessRaw32bitFloatBlock()                    */
/*                                                                      */
/*      Process a block using ``00'' (32 bit) raw format.               */
/************************************************************************/

static CPLErr AIGProcessRaw32BitFloatBlock(GByte *pabyCur, int nDataSize,
                                           int nMin, int nBlockXSize,
                                           int nBlockYSize, float *pafData)

{
    int i;

    (void)nMin;
    if (nDataSize < nBlockXSize * nBlockYSize * 4)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Block too small");
        return CE_Failure;
    }

    /* -------------------------------------------------------------------- */
    /*      Collect raw data.                                               */
    /* -------------------------------------------------------------------- */
    for (i = 0; i < nBlockXSize * nBlockYSize; i++)
    {
        float fWork;

#ifdef CPL_LSB
        ((GByte *)&fWork)[3] = *(pabyCur++);
        ((GByte *)&fWork)[2] = *(pabyCur++);
        ((GByte *)&fWork)[1] = *(pabyCur++);
        ((GByte *)&fWork)[0] = *(pabyCur++);
#else
        ((GByte *)&fWork)[0] = *(pabyCur++);
        ((GByte *)&fWork)[1] = *(pabyCur++);
        ((GByte *)&fWork)[2] = *(pabyCur++);
        ((GByte *)&fWork)[3] = *(pabyCur++);
#endif

        pafData[i] = fWork;
    }

    return (CE_None);
}

/************************************************************************/
/*                      AIGProcessIntConstBlock()                       */
/*                                                                      */
/*      Process a block using ``00'' constant 32bit integer format.     */
/************************************************************************/

static CPLErr AIGProcessIntConstBlock(GByte *pabyCur, int nDataSize, int nMin,
                                      int nBlockXSize, int nBlockYSize,
                                      GInt32 *panData)

{
    int i;

    (void)pabyCur;
    (void)nDataSize;

    /* -------------------------------------------------------------------- */
    /*	Apply constant min value.					*/
    /* -------------------------------------------------------------------- */
    for (i = 0; i < nBlockXSize * nBlockYSize; i++)
        panData[i] = nMin;

    return (CE_None);
}

/************************************************************************/
/*                         AIGRolloverSignedAdd()                       */
/************************************************************************/

CPL_NOSANITIZE_UNSIGNED_INT_OVERFLOW
static GInt32 AIGRolloverSignedAdd(GInt32 a, GInt32 b)
{
    // Not really portable as assumes complement to 2 representation
    // but AIG assumes typical unsigned rollover on signed
    // integer operations.
    GInt32 res;
    GUInt32 resUnsigned = (GUInt32)(a) + (GUInt32)(b);
    memcpy(&res, &resUnsigned, sizeof(res));
    return res;
}

/************************************************************************/
/*                         AIGProcess32bitRawBlock()                    */
/*                                                                      */
/*      Process a block using ``20'' (thirty two bit) raw format.        */
/************************************************************************/

static CPLErr AIGProcessRaw32BitBlock(GByte *pabyCur, int nDataSize, int nMin,
                                      int nBlockXSize, int nBlockYSize,
                                      GInt32 *panData)

{
    int i;

    if (nDataSize < nBlockXSize * nBlockYSize * 4)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Block too small");
        return CE_Failure;
    }

    /* -------------------------------------------------------------------- */
    /*      Collect raw data.                                               */
    /* -------------------------------------------------------------------- */
    for (i = 0; i < nBlockXSize * nBlockYSize; i++)
    {
        memcpy(panData + i, pabyCur, 4);
        panData[i] = CPL_MSBWORD32(panData[i]);
        panData[i] = AIGRolloverSignedAdd(panData[i], nMin);
        pabyCur += 4;
    }

    return (CE_None);
}

/************************************************************************/
/*                         AIGProcess16bitRawBlock()                    */
/*                                                                      */
/*      Process a block using ``10'' (sixteen bit) raw format.          */
/************************************************************************/

static CPLErr AIGProcessRaw16BitBlock(GByte *pabyCur, int nDataSize, int nMin,
                                      int nBlockXSize, int nBlockYSize,
                                      GInt32 *panData)

{
    int i;

    if (nDataSize < nBlockXSize * nBlockYSize * 2)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Block too small");
        return CE_Failure;
    }

    /* -------------------------------------------------------------------- */
    /*      Collect raw data.                                               */
    /* -------------------------------------------------------------------- */
    for (i = 0; i < nBlockXSize * nBlockYSize; i++)
    {
        panData[i] = AIGRolloverSignedAdd(pabyCur[0] * 256 + pabyCur[1], nMin);
        pabyCur += 2;
    }

    return (CE_None);
}

/************************************************************************/
/*                         AIGProcess4BitRawBlock()                     */
/*                                                                      */
/*      Process a block using ``08'' raw format.                        */
/************************************************************************/

static CPLErr AIGProcessRaw4BitBlock(GByte *pabyCur, int nDataSize, int nMin,
                                     int nBlockXSize, int nBlockYSize,
                                     GInt32 *panData)

{
    int i;

    if (nDataSize < (nBlockXSize * nBlockYSize + 1) / 2)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Block too small");
        return CE_Failure;
    }

    /* -------------------------------------------------------------------- */
    /*      Collect raw data.                                               */
    /* -------------------------------------------------------------------- */
    for (i = 0; i < nBlockXSize * nBlockYSize; i++)
    {
        if (i % 2 == 0)
            panData[i] = AIGRolloverSignedAdd((*(pabyCur)&0xf0) >> 4, nMin);
        else
            panData[i] = AIGRolloverSignedAdd(*(pabyCur++) & 0xf, nMin);
    }

    return (CE_None);
}

/************************************************************************/
/*                       AIGProcess1BitRawBlock()                       */
/*                                                                      */
/*      Process a block using ``0x01'' raw format.                      */
/************************************************************************/

static CPLErr AIGProcessRaw1BitBlock(GByte *pabyCur, int nDataSize, int nMin,
                                     int nBlockXSize, int nBlockYSize,
                                     GInt32 *panData)

{
    int i;

    if (nDataSize < (nBlockXSize * nBlockYSize + 7) / 8)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Block too small");
        return CE_Failure;
    }

    /* -------------------------------------------------------------------- */
    /*      Collect raw data.                                               */
    /* -------------------------------------------------------------------- */
    for (i = 0; i < nBlockXSize * nBlockYSize; i++)
    {
        if (pabyCur[i >> 3] & (0x80 >> (i & 0x7)))
            panData[i] = AIGRolloverSignedAdd(1, nMin);
        else
            panData[i] = 0 + nMin;
    }

    return (CE_None);
}

/************************************************************************/
/*                         AIGProcessRawBlock()                         */
/*                                                                      */
/*      Process a block using ``08'' raw format.                        */
/************************************************************************/

static CPLErr AIGProcessRawBlock(GByte *pabyCur, int nDataSize, int nMin,
                                 int nBlockXSize, int nBlockYSize,
                                 GInt32 *panData)

{
    int i;

    if (nDataSize < nBlockXSize * nBlockYSize)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Block too small");
        return CE_Failure;
    }

    /* -------------------------------------------------------------------- */
    /*      Collect raw data.                                               */
    /* -------------------------------------------------------------------- */
    for (i = 0; i < nBlockXSize * nBlockYSize; i++)
    {
        panData[i] = AIGRolloverSignedAdd(*(pabyCur++), nMin);
    }

    return (CE_None);
}

/************************************************************************/
/*                         AIGProcessFFBlock()                          */
/*                                                                      */
/*      Process a type 0xFF (CCITT RLE) compressed block.               */
/************************************************************************/

static CPLErr AIGProcessFFBlock(GByte *pabyCur, int nDataSize, int nMin,
                                int nBlockXSize, int nBlockYSize,
                                GInt32 *panData)

{
    /* -------------------------------------------------------------------- */
    /*      Convert CCITT compress bitstream into 1bit raw data.            */
    /* -------------------------------------------------------------------- */
    CPLErr eErr;
    int i, nDstBytes = (nBlockXSize * nBlockYSize + 7) / 8;
    unsigned char *pabyIntermediate;

    pabyIntermediate = (unsigned char *)VSI_MALLOC_VERBOSE(nDstBytes);
    if (pabyIntermediate == NULL)
    {
        return CE_Failure;
    }

    eErr = DecompressCCITTRLETile(pabyCur, nDataSize, pabyIntermediate,
                                  nDstBytes, nBlockXSize, nBlockYSize);
    if (eErr != CE_None)
    {
        CPLFree(pabyIntermediate);
        return eErr;
    }

    /* -------------------------------------------------------------------- */
    /*      Convert the bit buffer into 32bit integers and account for      */
    /*      nMin.                                                           */
    /* -------------------------------------------------------------------- */
    for (i = 0; i < nBlockXSize * nBlockYSize; i++)
    {
        if (pabyIntermediate[i >> 3] & (0x80 >> (i & 0x7)))
            panData[i] = AIGRolloverSignedAdd(nMin, 1);
        else
            panData[i] = nMin;
    }

    CPLFree(pabyIntermediate);

    return (CE_None);
}

/************************************************************************/
/*                          AIGProcessBlock()                           */
/*                                                                      */
/*      Process a block using ``D7'', ``E0'' or ``DF'' compression.     */
/************************************************************************/

static CPLErr AIGProcessBlock(GByte *pabyCur, int nDataSize, int nMin,
                              int nMagic, int nBlockXSize, int nBlockYSize,
                              GInt32 *panData)

{
    int nTotPixels, nPixels;
    int i;

    /* ==================================================================== */
    /*     Process runs till we are done.                                  */
    /* ==================================================================== */
    nTotPixels = nBlockXSize * nBlockYSize;
    nPixels = 0;

    while (nPixels < nTotPixels && nDataSize > 0)
    {
        int nMarker = *(pabyCur++);

        nDataSize--;

        /* --------------------------------------------------------------------
         */
        /*      Repeat data - four byte data block (0xE0) */
        /* --------------------------------------------------------------------
         */
        if (nMagic == 0xE0)
        {
            GInt32 nValue;

            if (nMarker + nPixels > nTotPixels)
            {
                CPLError(CE_Failure, CPLE_AppDefined,
                         "Run too long in AIGProcessBlock, needed %d values, "
                         "got %d.",
                         nTotPixels - nPixels, nMarker);
                return CE_Failure;
            }

            if (nDataSize < 4)
            {
                CPLError(CE_Failure, CPLE_AppDefined, "Block too small");
                return CE_Failure;
            }

            nValue = 0;
            memcpy(&nValue, pabyCur, 4);
            pabyCur += 4;
            nDataSize -= 4;

            nValue = CPL_MSBWORD32(nValue);
            nValue = AIGRolloverSignedAdd(nValue, nMin);
            for (i = 0; i < nMarker; i++)
                panData[nPixels++] = nValue;
        }

        /* --------------------------------------------------------------------
         */
        /*      Repeat data - two byte data block (0xF0) */
        /* --------------------------------------------------------------------
         */
        else if (nMagic == 0xF0)
        {
            GInt32 nValue;

            if (nMarker + nPixels > nTotPixels)
            {
                CPLError(CE_Failure, CPLE_AppDefined,
                         "Run too long in AIGProcessBlock, needed %d values, "
                         "got %d.",
                         nTotPixels - nPixels, nMarker);
                return CE_Failure;
            }

            if (nDataSize < 2)
            {
                CPLError(CE_Failure, CPLE_AppDefined, "Block too small");
                return CE_Failure;
            }

            nValue = AIGRolloverSignedAdd(pabyCur[0] * 256 + pabyCur[1], nMin);
            pabyCur += 2;
            nDataSize -= 2;

            for (i = 0; i < nMarker; i++)
                panData[nPixels++] = nValue;
        }

        /* --------------------------------------------------------------------
         */
        /*      Repeat data - one byte data block (0xFC) */
        /* --------------------------------------------------------------------
         */
        else if (nMagic == 0xFC || nMagic == 0xF8)
        {
            GInt32 nValue;

            if (nMarker + nPixels > nTotPixels)
            {
                CPLError(CE_Failure, CPLE_AppDefined,
                         "Run too long in AIGProcessBlock, needed %d values, "
                         "got %d.",
                         nTotPixels - nPixels, nMarker);
                return CE_Failure;
            }

            if (nDataSize < 1)
            {
                CPLError(CE_Failure, CPLE_AppDefined, "Block too small");
                return CE_Failure;
            }

            nValue = AIGRolloverSignedAdd(*(pabyCur++), nMin);
            nDataSize--;

            for (i = 0; i < nMarker; i++)
                panData[nPixels++] = nValue;
        }

        /* --------------------------------------------------------------------
         */
        /*      Repeat data - no actual data, just assign minimum (0xDF) */
        /* --------------------------------------------------------------------
         */
        else if (nMagic == 0xDF && nMarker < 128)
        {
            if (nMarker + nPixels > nTotPixels)
            {
                CPLError(CE_Failure, CPLE_AppDefined,
                         "Run too long in AIGProcessBlock, needed %d values, "
                         "got %d.",
                         nTotPixels - nPixels, nMarker);
                return CE_Failure;
            }

            for (i = 0; i < nMarker; i++)
                panData[nPixels++] = nMin;
        }

        /* --------------------------------------------------------------------
         */
        /*      Literal data (0xD7): 8bit values. */
        /* --------------------------------------------------------------------
         */
        else if (nMagic == 0xD7 && nMarker < 128)
        {
            if (nMarker + nPixels > nTotPixels)
            {
                CPLError(CE_Failure, CPLE_AppDefined,
                         "Run too long in AIGProcessBlock, needed %d values, "
                         "got %d.",
                         nTotPixels - nPixels, nMarker);
                return CE_Failure;
            }

            while (nMarker > 0 && nDataSize > 0)
            {
                panData[nPixels++] = AIGRolloverSignedAdd(*(pabyCur++), nMin);
                nMarker--;
                nDataSize--;
            }
        }

        /* --------------------------------------------------------------------
         */
        /*      Literal data (0xCF): 16 bit values. */
        /* --------------------------------------------------------------------
         */
        else if (nMagic == 0xCF && nMarker < 128)
        {
            GInt32 nValue;

            if (nMarker + nPixels > nTotPixels)
            {
                CPLError(CE_Failure, CPLE_AppDefined,
                         "Run too long in AIGProcessBlock, needed %d values, "
                         "got %d.",
                         nTotPixels - nPixels, nMarker);
                return CE_Failure;
            }

            while (nMarker > 0 && nDataSize >= 2)
            {
                nValue =
                    AIGRolloverSignedAdd(pabyCur[0] * 256 + pabyCur[1], nMin);
                panData[nPixels++] = nValue;
                pabyCur += 2;

                nMarker--;
                nDataSize -= 2;
            }
        }

        /* --------------------------------------------------------------------
         */
        /*      Nodata repeat */
        /* --------------------------------------------------------------------
         */
        else if (nMarker > 128)
        {
            nMarker = 256 - nMarker;

            if (nMarker + nPixels > nTotPixels)
            {
                CPLError(CE_Failure, CPLE_AppDefined,
                         "Run too long in AIGProcessBlock, needed %d values, "
                         "got %d.",
                         nTotPixels - nPixels, nMarker);
                return CE_Failure;
            }

            while (nMarker > 0)
            {
                panData[nPixels++] = ESRI_GRID_NO_DATA;
                nMarker--;
            }
        }

        else
        {
            return CE_Failure;
        }
    }

    if (nPixels < nTotPixels || nDataSize < 0)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Ran out of data processing block with nMagic=%d.", nMagic);
        return CE_Failure;
    }

    return CE_None;
}

/************************************************************************/
/*                            AIGReadBlock()                            */
/*                                                                      */
/*      Read a single block of integer grid data.                       */
/************************************************************************/

CPLErr AIGReadBlock(VSILFILE *fp, GUInt32 nBlockOffset, int nBlockSize,
                    int nBlockXSize, int nBlockYSize, GInt32 *panData,
                    int nCellType, int bCompressed)

{
    GByte *pabyRaw, *pabyCur;
    CPLErr eErr;
    int i, nMagic, nMinSize = 0, nDataSize;
    GInt32 nMin = 0;

    /* -------------------------------------------------------------------- */
    /*      If the block has zero size it is all dummies.                   */
    /* -------------------------------------------------------------------- */
    if (nBlockSize == 0)
    {
        for (i = 0; i < nBlockXSize * nBlockYSize; i++)
            panData[i] = ESRI_GRID_NO_DATA;

        return (CE_None);
    }

    /* -------------------------------------------------------------------- */
    /*      Read the block into memory.                                     */
    /* -------------------------------------------------------------------- */
    if (nBlockSize <= 0 || nBlockSize > 65535 * 2)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid block size : %d",
                 nBlockSize);
        return CE_Failure;
    }

    pabyRaw = (GByte *)VSIMalloc(nBlockSize + 2);
    if (pabyRaw == NULL)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Cannot allocate memory for block");
        return CE_Failure;
    }

    if (VSIFSeekL(fp, nBlockOffset, SEEK_SET) != 0 ||
        VSIFReadL(pabyRaw, nBlockSize + 2, 1, fp) != 1)
    {
        memset(panData, 0, sizeof(int32_t) * nBlockXSize * nBlockYSize);
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Read of %d bytes from offset %d for grid block failed.",
                 nBlockSize + 2, nBlockOffset);
        CPLFree(pabyRaw);
        return CE_Failure;
    }

    /* -------------------------------------------------------------------- */
    /*      Verify the block size.                                          */
    /* -------------------------------------------------------------------- */
    if (nBlockSize != (pabyRaw[0] * 256 + pabyRaw[1]) * 2)
    {
        memset(panData, 0, sizeof(int32_t) * nBlockXSize * nBlockYSize);
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Block is corrupt, block size was %d, but expected to be %d.",
                 (pabyRaw[0] * 256 + pabyRaw[1]) * 2, nBlockSize);
        CPLFree(pabyRaw);
        return CE_Failure;
    }

    nDataSize = nBlockSize;

    /* -------------------------------------------------------------------- */
    /*      Handle float files and uncompressed integer files directly.     */
    /* -------------------------------------------------------------------- */
    if (nCellType == AIG_CELLTYPE_FLOAT)
    {
        AIGProcessRaw32BitFloatBlock(pabyRaw + 2, nDataSize, 0, nBlockXSize,
                                     nBlockYSize, (float *)panData);
        CPLFree(pabyRaw);

        return CE_None;
    }

    if (nCellType == AIG_CELLTYPE_INT && !bCompressed)
    {
        AIGProcessRaw32BitBlock(pabyRaw + 2, nDataSize, nMin, nBlockXSize,
                                nBlockYSize, panData);
        CPLFree(pabyRaw);
        return CE_None;
    }

    /* -------------------------------------------------------------------- */
    /*      Collect minimum value.                                          */
    /* -------------------------------------------------------------------- */

    /* The first 2 bytes that give the block size are not included in nDataSize
     */
    /* and have already been safely read */
    pabyCur = pabyRaw + 2;

    /* Need at least 2 byte to read the nMinSize and the nMagic */
    if (nDataSize < 2)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Corrupt block. Need 2 bytes to read nMagic and nMinSize, "
                 "only %d available",
                 nDataSize);
        CPLFree(pabyRaw);
        return CE_Failure;
    }
    nMagic = pabyCur[0];
    nMinSize = pabyCur[1];
    pabyCur += 2;
    nDataSize -= 2;

    /* Need at least nMinSize bytes to read the nMin value */
    if (nDataSize < nMinSize)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Corrupt block. Need %d bytes to read nMin. Only %d available",
                 nMinSize, nDataSize);
        CPLFree(pabyRaw);
        return CE_Failure;
    }

    if (nMinSize > 4)
    {
        memset(panData, 0, sizeof(int32_t) * nBlockXSize * nBlockYSize);
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Corrupt 'minsize' of %d in block header.  Read aborted.",
                 nMinSize);
        CPLFree(pabyRaw);
        return CE_Failure;
    }

    if (nMinSize == 4)
    {
        memcpy(&nMin, pabyCur, 4);
        nMin = CPL_MSBWORD32(nMin);
        pabyCur += 4;
    }
    else
    {
        nMin = 0;
        for (i = 0; i < nMinSize; i++)
        {
            nMin = nMin * 256 + *pabyCur;
            pabyCur++;
        }

        /* If nMinSize = 0, then we might have only 4 bytes in pabyRaw */
        /* don't try to read the 5th one then */
        if (nMinSize != 0 && pabyRaw[4] > 127)
        {
            if (nMinSize == 2)
                nMin = nMin - 65536;
            else if (nMinSize == 1)
                nMin = nMin - 256;
            else if (nMinSize == 3)
                nMin = nMin - 256 * 256 * 256;
        }
    }

    nDataSize -= nMinSize;

    /* -------------------------------------------------------------------- */
    /*	Call an appropriate handler depending on magic code.		*/
    /* -------------------------------------------------------------------- */
    eErr = CE_None;
    if (nMagic == 0x08)
    {
        AIGProcessRawBlock(pabyCur, nDataSize, nMin, nBlockXSize, nBlockYSize,
                           panData);
    }
    else if (nMagic == 0x04)
    {
        AIGProcessRaw4BitBlock(pabyCur, nDataSize, nMin, nBlockXSize,
                               nBlockYSize, panData);
    }
    else if (nMagic == 0x01)
    {
        AIGProcessRaw1BitBlock(pabyCur, nDataSize, nMin, nBlockXSize,
                               nBlockYSize, panData);
    }
    else if (nMagic == 0x00)
    {
        AIGProcessIntConstBlock(pabyCur, nDataSize, nMin, nBlockXSize,
                                nBlockYSize, panData);
    }
    else if (nMagic == 0x10)
    {
        AIGProcessRaw16BitBlock(pabyCur, nDataSize, nMin, nBlockXSize,
                                nBlockYSize, panData);
    }
    else if (nMagic == 0x20)
    {
        AIGProcessRaw32BitBlock(pabyCur, nDataSize, nMin, nBlockXSize,
                                nBlockYSize, panData);
    }
    else if (nMagic == 0xFF)
    {
        eErr = AIGProcessFFBlock(pabyCur, nDataSize, nMin, nBlockXSize,
                                 nBlockYSize, panData);
    }
    else
    {
        eErr = AIGProcessBlock(pabyCur, nDataSize, nMin, nMagic, nBlockXSize,
                               nBlockYSize, panData);

        if (eErr == CE_Failure)
        {
            static int bHasWarned = FALSE;

            for (i = 0; i < nBlockXSize * nBlockYSize; i++)
                panData[i] = ESRI_GRID_NO_DATA;

            if (!bHasWarned)
            {
                CPLError(CE_Warning, CPLE_AppDefined,
                         "Unsupported Arc/Info Binary Grid tile of type 0x%X"
                         " encountered.\n"
                         "This and subsequent unsupported tile types set to"
                         " no data value.\n",
                         nMagic);
                bHasWarned = TRUE;
            }
        }
    }

    CPLFree(pabyRaw);

    return eErr;
}

/************************************************************************/
/*                           AIGReadHeader()                            */
/*                                                                      */
/*      Read the hdr.adf file, and populate the given info structure    */
/*      appropriately.                                                  */
/************************************************************************/

CPLErr AIGReadHeader(const char *pszCoverName, AIGInfo_t *psInfo)

{
    char *pszHDRFilename;
    VSILFILE *fp;
    GByte abyData[308];
    const size_t nHDRFilenameLen = strlen(pszCoverName) + 30;

    /* -------------------------------------------------------------------- */
    /*      Open the file hdr.adf file.                                     */
    /* -------------------------------------------------------------------- */
    pszHDRFilename = (char *)CPLMalloc(nHDRFilenameLen);
    snprintf(pszHDRFilename, nHDRFilenameLen, "%s/hdr.adf", pszCoverName);

    fp = AIGLLOpen(pszHDRFilename, "rb");

    if (fp == NULL)
    {
        CPLError(CE_Failure, CPLE_OpenFailed,
                 "Failed to open grid header file:\n%s\n", pszHDRFilename);

        CPLFree(pszHDRFilename);
        return (CE_Failure);
    }

    CPLFree(pszHDRFilename);

    /* -------------------------------------------------------------------- */
    /*      Read the whole file (we expect it to always be 308 bytes        */
    /*      long.                                                           */
    /* -------------------------------------------------------------------- */

    if (VSIFReadL(abyData, 1, 308, fp) != 308)
    {
        CPL_IGNORE_RET_VAL_INT(VSIFCloseL(fp));
        return (CE_Failure);
    }

    CPL_IGNORE_RET_VAL_INT(VSIFCloseL(fp));

    /* -------------------------------------------------------------------- */
    /*      Read the block size information.                                */
    /* -------------------------------------------------------------------- */
    memcpy(&(psInfo->nCellType), abyData + 16, 4);
    memcpy(&(psInfo->bCompressed), abyData + 20, 4);
    memcpy(&(psInfo->nBlocksPerRow), abyData + 288, 4);
    memcpy(&(psInfo->nBlocksPerColumn), abyData + 292, 4);
    memcpy(&(psInfo->nBlockXSize), abyData + 296, 4);
    memcpy(&(psInfo->nBlockYSize), abyData + 304, 4);
    memcpy(&(psInfo->dfCellSizeX), abyData + 256, 8);
    memcpy(&(psInfo->dfCellSizeY), abyData + 264, 8);

#ifdef CPL_LSB
    psInfo->nCellType = CPL_SWAP32(psInfo->nCellType);
    psInfo->bCompressed = CPL_SWAP32(psInfo->bCompressed);
    psInfo->nBlocksPerRow = CPL_SWAP32(psInfo->nBlocksPerRow);
    psInfo->nBlocksPerColumn = CPL_SWAP32(psInfo->nBlocksPerColumn);
    psInfo->nBlockXSize = CPL_SWAP32(psInfo->nBlockXSize);
    psInfo->nBlockYSize = CPL_SWAP32(psInfo->nBlockYSize);
    CPL_SWAPDOUBLE(&(psInfo->dfCellSizeX));
    CPL_SWAPDOUBLE(&(psInfo->dfCellSizeY));
#endif

    psInfo->bCompressed = !psInfo->bCompressed;

    return (CE_None);
}

/************************************************************************/
/*                         AIGReadBlockIndex()                          */
/*                                                                      */
/*      Read the w001001x.adf file, and populate the given info         */
/*      structure with the block offsets, and sizes.                    */
/************************************************************************/

CPLErr AIGReadBlockIndex(AIGInfo_t *psInfo, AIGTileInfo *psTInfo,
                         const char *pszBasename)

{
    char *pszHDRFilename;
    VSILFILE *fp;
    int i;
    GUInt32 nValue, nLength;
    GUInt32 *panIndex;
    GByte abyHeader[8];
    const size_t nHDRFilenameLen = strlen(psInfo->pszCoverName) + 40;

    /* -------------------------------------------------------------------- */
    /*      Open the file hdr.adf file.                                     */
    /* -------------------------------------------------------------------- */
    pszHDRFilename = (char *)CPLMalloc(nHDRFilenameLen);
    snprintf(pszHDRFilename, nHDRFilenameLen, "%s/%sx.adf",
             psInfo->pszCoverName, pszBasename);

    fp = AIGLLOpen(pszHDRFilename, "rb");

    if (fp == NULL)
    {
        CPLError(CE_Failure, CPLE_OpenFailed,
                 "Failed to open grid block index file:\n%s\n", pszHDRFilename);

        CPLFree(pszHDRFilename);
        return (CE_Failure);
    }

    CPLFree(pszHDRFilename);

    /* -------------------------------------------------------------------- */
    /*      Verify the magic number.  This is often corrupted by CR/LF      */
    /*      translation.                                                    */
    /* -------------------------------------------------------------------- */
    if (VSIFReadL(abyHeader, 1, 8, fp) != 8)
    {
        CPL_IGNORE_RET_VAL_INT(VSIFCloseL(fp));
        return CE_Failure;
    }
    if (abyHeader[3] == 0x0D && abyHeader[4] == 0x0A)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "w001001x.adf file header has been corrupted by unix to dos "
                 "text conversion.");
        CPL_IGNORE_RET_VAL_INT(VSIFCloseL(fp));
        return CE_Failure;
    }

    if (abyHeader[0] != 0x00 || abyHeader[1] != 0x00 || abyHeader[2] != 0x27 ||
        abyHeader[3] != 0x0A || abyHeader[4] != 0xFF || abyHeader[5] != 0xFF)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "w001001x.adf file header magic number is corrupt.");
        CPL_IGNORE_RET_VAL_INT(VSIFCloseL(fp));
        return CE_Failure;
    }

    /* -------------------------------------------------------------------- */
    /*      Get the file length (in 2 byte shorts)                          */
    /* -------------------------------------------------------------------- */
    if (VSIFSeekL(fp, 24, SEEK_SET) != 0 || VSIFReadL(&nValue, 1, 4, fp) != 4)
    {
        CPL_IGNORE_RET_VAL_INT(VSIFCloseL(fp));
        return CE_Failure;
    }

    nValue = CPL_MSBWORD32(nValue);
    if (nValue > INT_MAX)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "AIGReadBlockIndex: Bad length");
        CPL_IGNORE_RET_VAL_INT(VSIFCloseL(fp));
        return CE_Failure;
    }
    nLength = nValue * 2;
    if (nLength <= 100)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "AIGReadBlockIndex: Bad length");
        CPL_IGNORE_RET_VAL_INT(VSIFCloseL(fp));
        return CE_Failure;
    }

    /* -------------------------------------------------------------------- */
    /*      Allocate buffer, and read the file (from beyond the header)     */
    /*      into the buffer.                                                */
    /* -------------------------------------------------------------------- */
    psTInfo->nBlocks = (nLength - 100) / 8;
    if (psTInfo->nBlocks >= 1000000)
    {
        // Avoid excessive memory consumption.
        vsi_l_offset nFileSize;
        VSIFSeekL(fp, 0, SEEK_END);
        nFileSize = VSIFTellL(fp);
        if (nFileSize < 100 ||
            (vsi_l_offset)psTInfo->nBlocks > (nFileSize - 100) / 8)
        {
            CPL_IGNORE_RET_VAL_INT(VSIFCloseL(fp));
            return CE_Failure;
        }
    }
    panIndex = (GUInt32 *)VSI_MALLOC2_VERBOSE(psTInfo->nBlocks, 8);
    if (panIndex == NULL)
    {
        CPL_IGNORE_RET_VAL_INT(VSIFCloseL(fp));
        return CE_Failure;
    }
    if (VSIFSeekL(fp, 100, SEEK_SET) != 0 ||
        (int)VSIFReadL(panIndex, 8, psTInfo->nBlocks, fp) != psTInfo->nBlocks)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "AIGReadBlockIndex: Cannot read block info");
        CPL_IGNORE_RET_VAL_INT(VSIFCloseL(fp));
        CPLFree(panIndex);
        return CE_Failure;
    }

    CPL_IGNORE_RET_VAL_INT(VSIFCloseL(fp));

    /* -------------------------------------------------------------------- */
    /*	Allocate AIGInfo block info arrays.				*/
    /* -------------------------------------------------------------------- */
    psTInfo->panBlockOffset =
        (GUInt32 *)VSI_MALLOC2_VERBOSE(4, psTInfo->nBlocks);
    psTInfo->panBlockSize = (int *)VSI_MALLOC2_VERBOSE(4, psTInfo->nBlocks);
    if (psTInfo->panBlockOffset == NULL || psTInfo->panBlockSize == NULL)
    {
        CPLFree(psTInfo->panBlockOffset);
        CPLFree(psTInfo->panBlockSize);
        psTInfo->panBlockOffset = NULL;
        psTInfo->panBlockSize = NULL;
        CPLFree(panIndex);
        return CE_Failure;
    }

    /* -------------------------------------------------------------------- */
    /*      Populate the block information.                                 */
    /* -------------------------------------------------------------------- */
    for (i = 0; i < psTInfo->nBlocks; i++)
    {
        GUInt32 nVal;

        nVal = CPL_MSBWORD32(panIndex[i * 2]);
        if (nVal >= INT_MAX)
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "AIGReadBlockIndex: Bad offset for block %d", i);
            CPLFree(psTInfo->panBlockOffset);
            CPLFree(psTInfo->panBlockSize);
            psTInfo->panBlockOffset = NULL;
            psTInfo->panBlockSize = NULL;
            CPLFree(panIndex);
            return CE_Failure;
        }
        psTInfo->panBlockOffset[i] = nVal * 2;

        nVal = CPL_MSBWORD32(panIndex[i * 2 + 1]);
        if (nVal >= INT_MAX / 2)
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "AIGReadBlockIndex: Bad size for block %d", i);
            CPLFree(psTInfo->panBlockOffset);
            CPLFree(psTInfo->panBlockSize);
            psTInfo->panBlockOffset = NULL;
            psTInfo->panBlockSize = NULL;
            CPLFree(panIndex);
            return CE_Failure;
        }
        psTInfo->panBlockSize[i] = nVal * 2;
    }

    CPLFree(panIndex);

    return (CE_None);
}

/************************************************************************/
/*                           AIGReadBounds()                            */
/*                                                                      */
/*      Read the dblbnd.adf file for the georeferenced bounds.          */
/************************************************************************/

CPLErr AIGReadBounds(const char *pszCoverName, AIGInfo_t *psInfo)

{
    char *pszHDRFilename;
    VSILFILE *fp;
    double adfBound[4];
    const size_t nHDRFilenameLen = strlen(pszCoverName) + 40;

    /* -------------------------------------------------------------------- */
    /*      Open the file dblbnd.adf file.                                  */
    /* -------------------------------------------------------------------- */
    pszHDRFilename = (char *)CPLMalloc(nHDRFilenameLen);
    snprintf(pszHDRFilename, nHDRFilenameLen, "%s/dblbnd.adf", pszCoverName);

    fp = AIGLLOpen(pszHDRFilename, "rb");

    if (fp == NULL)
    {
        CPLError(CE_Failure, CPLE_OpenFailed,
                 "Failed to open grid bounds file:\n%s\n", pszHDRFilename);

        CPLFree(pszHDRFilename);
        return (CE_Failure);
    }

    CPLFree(pszHDRFilename);

    /* -------------------------------------------------------------------- */
    /*      Get the contents - four doubles.                                */
    /* -------------------------------------------------------------------- */
    if (VSIFReadL(adfBound, 1, 32, fp) != 32)
    {
        CPL_IGNORE_RET_VAL_INT(VSIFCloseL(fp));
        return CE_Failure;
    }

    CPL_IGNORE_RET_VAL_INT(VSIFCloseL(fp));

#ifdef CPL_LSB
    CPL_SWAPDOUBLE(adfBound + 0);
    CPL_SWAPDOUBLE(adfBound + 1);
    CPL_SWAPDOUBLE(adfBound + 2);
    CPL_SWAPDOUBLE(adfBound + 3);
#endif

    psInfo->dfLLX = adfBound[0];
    psInfo->dfLLY = adfBound[1];
    psInfo->dfURX = adfBound[2];
    psInfo->dfURY = adfBound[3];

    return (CE_None);
}

/************************************************************************/
/*                         AIGReadStatistics()                          */
/*                                                                      */
/*      Read the sta.adf file for the layer statistics.                 */
/************************************************************************/

CPLErr AIGReadStatistics(const char *pszCoverName, AIGInfo_t *psInfo)

{
    char *pszHDRFilename;
    VSILFILE *fp;
    double adfStats[4];
    const size_t nHDRFilenameLen = strlen(pszCoverName) + 40;
    size_t nRead;

    psInfo->dfMin = 0.0;
    psInfo->dfMax = 0.0;
    psInfo->dfMean = 0.0;
    psInfo->dfStdDev = -1.0;

    /* -------------------------------------------------------------------- */
    /*      Open the file sta.adf file.                                     */
    /* -------------------------------------------------------------------- */
    pszHDRFilename = (char *)CPLMalloc(nHDRFilenameLen);
    snprintf(pszHDRFilename, nHDRFilenameLen, "%s/sta.adf", pszCoverName);

    fp = AIGLLOpen(pszHDRFilename, "rb");

    if (fp == NULL)
    {
        CPLError(CE_Failure, CPLE_OpenFailed,
                 "Failed to open grid statistics file:\n%s\n", pszHDRFilename);

        CPLFree(pszHDRFilename);
        return (CE_Failure);
    }

    /* -------------------------------------------------------------------- */
    /*      Get the contents - 3 or 4 doubles.                              */
    /* -------------------------------------------------------------------- */
    nRead = VSIFReadL(adfStats, 1, 32, fp);

    CPL_IGNORE_RET_VAL_INT(VSIFCloseL(fp));

    if (nRead == 32)
    {
#ifdef CPL_LSB
        CPL_SWAPDOUBLE(adfStats + 0);
        CPL_SWAPDOUBLE(adfStats + 1);
        CPL_SWAPDOUBLE(adfStats + 2);
        CPL_SWAPDOUBLE(adfStats + 3);
#endif

        psInfo->dfMin = adfStats[0];
        psInfo->dfMax = adfStats[1];
        psInfo->dfMean = adfStats[2];
        psInfo->dfStdDev = adfStats[3];
    }
    else if (nRead == 24)
    {
        /* See dataset at https://trac.osgeo.org/gdal/ticket/6633 */
        /* In that case, we have only min, max and mean, in LSB ordering */
        CPL_LSBPTR64(adfStats + 0);
        CPL_LSBPTR64(adfStats + 1);
        CPL_LSBPTR64(adfStats + 2);

        psInfo->dfMin = adfStats[0];
        psInfo->dfMax = adfStats[1];
        psInfo->dfMean = adfStats[2];
    }
    else
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Wrong content for %s",
                 pszHDRFilename);
        CPLFree(pszHDRFilename);
        return CE_Failure;
    }

    CPLFree(pszHDRFilename);
    return CE_None;
}
