/******************************************************************************
 *
 * Purpose:  ADRG reader
 * Author:   Even Rouault, even.rouault at spatialys.com
 *
 ******************************************************************************
 * Copyright (c) 2007-2013, Even Rouault <even dot rouault at spatialys.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#include "cpl_string.h"
#include "gdal_pam.h"
#include "gdal_frmts.h"
#include "iso8211.h"
#include "ogr_spatialref.h"

#include <limits>
#include <new>

#define N_ELEMENTS(x) (sizeof(x) / sizeof(x[0]))

#define DIGIT_ZERO '0'

class ADRGDataset final : public GDALPamDataset
{
    friend class ADRGRasterBand;

    CPLString osGENFileName;
    CPLString osIMGFileName;
    OGRSpatialReference m_oSRS{};

    VSILFILE *fdIMG;
    int *TILEINDEX;
    int offsetInIMG;
    int NFC;
    int NFL;
    double LSO;
    double PSO;
    int ARV;
    int BRV;

    char **papszSubDatasets;

    ADRGDataset *poOverviewDS;

    /* For creation */
    int bCreation;
    VSILFILE *fdGEN;
    VSILFILE *fdTHF;
    int bGeoTransformValid;
    double adfGeoTransform[6];
    int nNextAvailableBlock;
    CPLString osBaseFileName;

    static char **GetGENListFromTHF(const char *pszFileName);
    static char **GetIMGListFromGEN(const char *pszFileName,
                                    int *pnRecordIndex = nullptr);
    static ADRGDataset *OpenDataset(const char *pszGENFileName,
                                    const char *pszIMGFileName,
                                    DDFRecord *record = nullptr);
    static DDFRecord *FindRecordInGENForIMG(DDFModule &module,
                                            const char *pszGENFileName,
                                            const char *pszIMGFileName);

  public:
    ADRGDataset();
    ~ADRGDataset() override;

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

    char **GetMetadataDomainList() override;
    char **GetMetadata(const char *pszDomain = "") override;

    char **GetFileList() override;

    void AddSubDataset(const char *pszGENFileName, const char *pszIMGFileName);

    static GDALDataset *Open(GDALOpenInfo *);
    static GDALDataset *Create(const char *pszFilename, int nXSize, int nYSize,
                               int nBandsIn, GDALDataType eType,
                               char **papszOptions);

    static double GetLongitudeFromString(const char *str);
    static double GetLatitudeFromString(const char *str);

    void WriteGENFile();
    void WriteTHFFile();
};

/************************************************************************/
/* ==================================================================== */
/*                            ADRGRasterBand                             */
/* ==================================================================== */
/************************************************************************/

class ADRGRasterBand final : public GDALPamRasterBand
{
    friend class ADRGDataset;

  public:
    ADRGRasterBand(ADRGDataset *, int);

    GDALColorInterp GetColorInterpretation() override;
    CPLErr IReadBlock(int, int, void *) override;
    CPLErr IWriteBlock(int, int, void *) override;

    double GetNoDataValue(int *pbSuccess = nullptr) override;

    // virtual int GetOverviewCount();
    // virtual GDALRasterBand* GetOverview(int i);
};

/************************************************************************/
/*                           ADRGRasterBand()                            */
/************************************************************************/

ADRGRasterBand::ADRGRasterBand(ADRGDataset *poDSIn, int nBandIn)

{
    poDS = poDSIn;
    nBand = nBandIn;

    eDataType = GDT_Byte;

    nBlockXSize = 128;
    nBlockYSize = 128;
}

#if 0

/* We have a problem with the overview. Its geo bounding box doesn't match */
/* exactly the one of the main image. We should handle the shift between */
/* the two top level corners... */

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

int ADRGRasterBand::GetOverviewCount()

{
    ADRGDataset* poDS = (ADRGDataset*)this->poDS;
    if( poDS->poOverviewDS )
        return 1;

    return GDALRasterBand::GetOverviewCount();
}

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

GDALRasterBand *ADRGRasterBand::GetOverview( int i )

{
    ADRGDataset* poDS = (ADRGDataset*)this->poDS;
    if( poDS->poOverviewDS )
    {
        if( i < 0 || i >= 1 )
            return NULL;

        return poDS->poOverviewDS->GetRasterBand(nBand);
    }

    return GDALRasterBand::GetOverview( i );
}
#endif

/************************************************************************/
/*                            GetNoDataValue()                          */
/************************************************************************/

double ADRGRasterBand::GetNoDataValue(int *pbSuccess)
{
    if (pbSuccess)
        *pbSuccess = TRUE;

    return 0.0;
}

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

GDALColorInterp ADRGRasterBand::GetColorInterpretation()

{
    if (nBand == 1)
        return GCI_RedBand;

    else if (nBand == 2)
        return GCI_GreenBand;

    return GCI_BlueBand;
}

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

CPLErr ADRGRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage)

{
    ADRGDataset *l_poDS = (ADRGDataset *)this->poDS;
    int nBlock = nBlockYOff * l_poDS->NFC + nBlockXOff;
    if (nBlockXOff >= l_poDS->NFC || nBlockYOff >= l_poDS->NFL)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "nBlockXOff=%d, NFC=%d, nBlockYOff=%d, NFL=%d", nBlockXOff,
                 l_poDS->NFC, nBlockYOff, l_poDS->NFL);
        return CE_Failure;
    }
    CPLDebug("ADRG", "(%d,%d) -> nBlock = %d", nBlockXOff, nBlockYOff, nBlock);

    vsi_l_offset offset;
    if (l_poDS->TILEINDEX)
    {
        if (l_poDS->TILEINDEX[nBlock] <= 0)
        {
            memset(pImage, 0, 128 * 128);
            return CE_None;
        }
        offset = l_poDS->offsetInIMG +
                 static_cast<vsi_l_offset>(l_poDS->TILEINDEX[nBlock] - 1) *
                     128 * 128 * 3 +
                 (nBand - 1) * 128 * 128;
    }
    else
        offset = l_poDS->offsetInIMG +
                 static_cast<vsi_l_offset>(nBlock) * 128 * 128 * 3 +
                 (nBand - 1) * 128 * 128;

    if (VSIFSeekL(l_poDS->fdIMG, offset, SEEK_SET) != 0)
    {
        CPLError(CE_Failure, CPLE_FileIO,
                 "Cannot seek to offset " CPL_FRMT_GUIB, offset);
        return CE_Failure;
    }
    if (VSIFReadL(pImage, 1, 128 * 128, l_poDS->fdIMG) != 128 * 128)
    {
        CPLError(CE_Failure, CPLE_FileIO,
                 "Cannot read data at offset " CPL_FRMT_GUIB, offset);
        return CE_Failure;
    }

    return CE_None;
}

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

CPLErr ADRGRasterBand::IWriteBlock(int nBlockXOff, int nBlockYOff, void *pImage)

{
    ADRGDataset *l_poDS = (ADRGDataset *)this->poDS;
    int nBlock = nBlockYOff * l_poDS->NFC + nBlockXOff;
    if (l_poDS->eAccess != GA_Update)
    {
        return CE_Failure;
    }
    if (nBlockXOff >= l_poDS->NFC || nBlockYOff >= l_poDS->NFL)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "nBlockXOff=%d, NFC=%d, nBlockYOff=%d, NFL=%d", nBlockXOff,
                 l_poDS->NFC, nBlockYOff, l_poDS->NFL);
        return CE_Failure;
    }
    CPLDebug("ADRG", "(%d,%d) -> nBlock = %d", nBlockXOff, nBlockYOff, nBlock);

    if (l_poDS->TILEINDEX[nBlock] == 0)
    {
        unsigned int i;
        int *pi = (int *)pImage;
        for (i = 0; i < 128 * 128 / sizeof(int); i++)
        {
            if (pi[i])
                break;
        }
        if (i == 128 * 128 / sizeof(int))
        {
            return CE_None;
        }

        l_poDS->TILEINDEX[nBlock] = l_poDS->nNextAvailableBlock++;
    }

    const int offset = l_poDS->offsetInIMG +
                       (l_poDS->TILEINDEX[nBlock] - 1) * 128 * 128 * 3 +
                       (nBand - 1) * 128 * 128;

    if (VSIFSeekL(l_poDS->fdIMG, offset, SEEK_SET) != 0)
    {
        CPLError(CE_Failure, CPLE_FileIO, "Cannot seek to offset %d", offset);
        return CE_Failure;
    }
    if (VSIFWriteL(pImage, 1, 128 * 128, l_poDS->fdIMG) != 128 * 128)
    {
        CPLError(CE_Failure, CPLE_FileIO, "Cannot read data at offset %d",
                 offset);
        return CE_Failure;
    }

    return CE_None;
}

static unsigned int WriteSubFieldStr(VSILFILE *fd, const char *pszStr,
                                     unsigned int size)
{
    char *str = (char *)CPLMalloc(size + 1);
    memset(str, ' ', size);
    str[size] = 0;
    const size_t nStrLen = strlen(pszStr);
    if (nStrLen > size)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "strlen(pszStr) > size");
        CPLFree(str);
        return size;
    }
    memcpy(str, pszStr, nStrLen);
    VSIFWriteL(str, 1, size, fd);
    CPLFree(str);
    return size;
}

static unsigned int WriteSubFieldInt(VSILFILE *fd, int val, unsigned int size)
{
    char *str = (char *)CPLMalloc(size + 1);
    char formatStr[32];
    snprintf(formatStr, sizeof(formatStr), "%%0%ud", size);
    snprintf(str, size + 1, formatStr, val);
    VSIFWriteL(str, 1, size, fd);
    CPLFree(str);
    return size;
}

static unsigned int WriteFieldTerminator(VSILFILE *fd)
{
    char fieldTerminator = 30;
    VSIFWriteL(&fieldTerminator, 1, 1, fd);
    return 1;
}

static unsigned int WriteUnitTerminator(VSILFILE *fd)
{
    char fieldTerminator = 31;
    VSIFWriteL(&fieldTerminator, 1, 1, fd);
    return 1;
}

static unsigned int WriteLongitude(VSILFILE *fd, double val)
{
    char str[11 + 1];
    const char sign = (val >= 0) ? '+' : '-';
    if (val < 0)
        val = -val;
    const int ddd = (int)val;
    const int mm = (int)((val - ddd) * 60);
    const double ssdotss = ((val - ddd) * 60 - mm) * 60;
    snprintf(str, sizeof(str), "%c%03d%02d%05.2f", sign, ddd, mm, ssdotss);
    CPLAssert((int)strlen(str) == 11);
    VSIFWriteL(str, 1, 11, fd);
    return 11;
}

static unsigned int WriteLatitude(VSILFILE *fd, double val)
{
    char str[10 + 1];
    const char sign = (val >= 0) ? '+' : '-';
    if (val < 0)
        val = -val;
    const int dd = (int)val;
    const int mm = (int)((val - dd) * 60);
    const double ssdotss = ((val - dd) * 60 - mm) * 60;
    snprintf(str, sizeof(str), "%c%02d%02d%05.2f", sign, dd, mm, ssdotss);
    CPLAssert((int)strlen(str) == 10);
    VSIFWriteL(str, 1, 10, fd);
    return 10;
}

static int BeginLeader(VSILFILE *fd, int sizeFieldLength, int sizeFieldPos,
                       int sizeFieldTag, int nFields)
{
    int pos = (int)VSIFTellL(fd);
    VSIFSeekL(fd,
              24 +
                  (sizeFieldLength + sizeFieldPos + sizeFieldTag) *
                      (vsi_l_offset)nFields +
                  1,
              SEEK_CUR);
    return pos;
}

static void FinishWriteLeader(VSILFILE *fd, int beginPos, int sizeFieldLength,
                              int sizeFieldPos, int sizeFieldTag, int nFields,
                              int *sizeOfFields, const char **nameOfFields)
{
    const int endPos = (int)VSIFTellL(fd);
    VSIFSeekL(fd, beginPos, SEEK_SET);

    int nLeaderSize = 24;
    char szLeader[24 + 1];
    memset(szLeader, ' ', nLeaderSize);

    int nDataSize = 0;
    int nFieldOffset = 0;
    for (int i = 0; i < nFields; i++)
        nDataSize += sizeOfFields[i];
    nFieldOffset =
        (sizeFieldLength + sizeFieldPos + sizeFieldTag) * nFields + 1;
    nDataSize += nFieldOffset;

    snprintf(szLeader + 0, sizeof(szLeader) - 0, "%05d",
             (int)(nDataSize + nLeaderSize));
    szLeader[5] = ' ';
    szLeader[6] = 'D';

    snprintf(szLeader + 12, sizeof(szLeader) - 12, "%05d",
             (int)(nFieldOffset + nLeaderSize));
    szLeader[17] = ' ';

    szLeader[20] = (char)('0' + sizeFieldLength);
    szLeader[21] = (char)('0' + sizeFieldPos);
    szLeader[22] = '0';
    szLeader[23] = (char)('0' + sizeFieldTag);

    VSIFWriteL(szLeader, 1, nLeaderSize, fd);

    int acc = 0;
    for (int i = 0; i < nFields; i++)
    {
        VSIFWriteL(nameOfFields[i], 1, sizeFieldTag, fd);
        WriteSubFieldInt(fd, sizeOfFields[i], sizeFieldLength);
        WriteSubFieldInt(fd, acc, sizeFieldPos);
        acc += sizeOfFields[i];
    }
    WriteFieldTerminator(fd);

    VSIFSeekL(fd, endPos, SEEK_SET);
}

static int BeginHeader(VSILFILE *fd, int sizeFieldLength, int sizeFieldPos,
                       int sizeFieldTag, int nFields)
{
    int pos = (int)VSIFTellL(fd);
    VSIFSeekL(
        fd, 24 + (sizeFieldLength + sizeFieldPos + sizeFieldTag) * nFields + 1,
        SEEK_CUR);
    return pos;
}

static void FinishWriteHeader(VSILFILE *fd, int beginPos, int sizeFieldLength,
                              int sizeFieldPos, int sizeFieldTag, int nFields,
                              int *sizeOfFields, const char **nameOfFields)
{
    int endPos = (int)VSIFTellL(fd);
    VSIFSeekL(fd, beginPos, SEEK_SET);

    int nLeaderSize = 24;
    char szLeader[24 + 1];
    memset(szLeader, ' ', nLeaderSize);

    int nDataSize = 0;
    int nFieldOffset = 0;
    for (int i = 0; i < nFields; i++)
        nDataSize += sizeOfFields[i];
    nFieldOffset =
        (sizeFieldLength + sizeFieldPos + sizeFieldTag) * nFields + 1;
    nDataSize += nFieldOffset;

    snprintf(szLeader + 0, sizeof(szLeader) - 0, "%05d",
             (int)(nDataSize + nLeaderSize));
    szLeader[5] = '2';
    szLeader[6] = 'L';

    szLeader[10] = '0';
    szLeader[11] = '6';
    snprintf(szLeader + 12, sizeof(szLeader) - 12, "%05d",
             (int)(nFieldOffset + nLeaderSize));
    szLeader[17] = ' ';

    szLeader[20] = (char)('0' + sizeFieldLength);
    szLeader[21] = (char)('0' + sizeFieldPos);
    szLeader[22] = '0';
    szLeader[23] = (char)('0' + sizeFieldTag);

    VSIFWriteL(szLeader, 1, nLeaderSize, fd);

    int acc = 0;
    for (int i = 0; i < nFields; i++)
    {
        VSIFWriteL(nameOfFields[i], 1, sizeFieldTag, fd);
        WriteSubFieldInt(fd, sizeOfFields[i], sizeFieldLength);
        WriteSubFieldInt(fd, acc, sizeFieldPos);
        acc += sizeOfFields[i];
    }
    WriteFieldTerminator(fd);

    VSIFSeekL(fd, endPos, SEEK_SET);
}

static int WriteFieldDecl(VSILFILE *fd, char _data_struct_code,
                          char _data_type_code, const char *_fieldName,
                          const char *_arrayDescr, const char *_formatControls)
{
    VSIFWriteL(&_data_struct_code, 1, 1, fd);
    VSIFWriteL(&_data_type_code, 1, 1, fd);
    if (_data_struct_code == ' ')
    {
        VSIFWriteL("    ", 1, 4, fd);
    }
    else
    {
        VSIFWriteL("00;&", 1, 4, fd);
    }
    int len = 6;
    VSIFWriteL(_fieldName, 1, strlen(_fieldName), fd);
    len += static_cast<int>(strlen(_fieldName));
    if (_arrayDescr[0])
    {
        len += WriteUnitTerminator(fd);
        VSIFWriteL(_arrayDescr, 1, strlen(_arrayDescr), fd);
        len += static_cast<int>(strlen(_arrayDescr));

        len += WriteUnitTerminator(fd);
        VSIFWriteL(_formatControls, 1, strlen(_formatControls), fd);
        len += static_cast<int>(strlen(_formatControls));
    }
    len += WriteFieldTerminator(fd);
    return len;
}

/************************************************************************/
/*                          ADRGDataset()                               */
/************************************************************************/

ADRGDataset::ADRGDataset()
    : fdIMG(nullptr), TILEINDEX(nullptr), offsetInIMG(0), NFC(0), NFL(0),
      LSO(0.0), PSO(0.0), ARV(0), BRV(0), papszSubDatasets(nullptr),
      poOverviewDS(nullptr), bCreation(FALSE), fdGEN(nullptr), fdTHF(nullptr),
      bGeoTransformValid(0), nNextAvailableBlock(0)
{
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    memset(adfGeoTransform, 0, sizeof(adfGeoTransform));
}

/************************************************************************/
/*                          ~ADRGDataset()                              */
/************************************************************************/

ADRGDataset::~ADRGDataset()
{
    if (poOverviewDS)
    {
        delete poOverviewDS;
    }

    CSLDestroy(papszSubDatasets);

    if (bCreation)
    {
        GDALPamDataset::FlushCache(true);

        /* Write header and padding of image */
        VSIFSeekL(fdIMG, 0, SEEK_SET);
        {
            VSILFILE *fd = fdIMG;
            {
                int nFields = 0;
                int sizeOfFields[] = {0, 0, 0, 0};
                const char *nameOfFields[] = {"000", "001", "PAD", "SCN"};
                int pos = BeginHeader(fd, 3, 4, 3, N_ELEMENTS(sizeOfFields));

                sizeOfFields[nFields++] += WriteFieldDecl(
                    fd, ' ', ' ', "GEO_DATA_FILE", "", ""); /* 000 */
                sizeOfFields[nFields++] +=
                    WriteFieldDecl(fd, '1', '0', "RECORD_ID_FIELD", /* 001 */
                                   "RTY!RID", "(A(3),A(2))");
                sizeOfFields[nFields++] +=
                    WriteFieldDecl(fd, '1', '0', "PADDING_FIELD", /* PAD */
                                   "PAD", "(A)");
                sizeOfFields[nFields++] +=
                    WriteFieldDecl(fd, '2', '0', "PIXEL_FIELD", /* SCN */
                                   "*PIX", "(A(1))");

                FinishWriteHeader(fd, pos, 3, 4, 3, N_ELEMENTS(sizeOfFields),
                                  sizeOfFields, nameOfFields);
            }

            /* Write IMAGE_RECORD */
            {
                int nFields = 0;
                int sizeOfFields[] = {0, 0, 0};
                const char *nameOfFields[] = {"001", "PAD", "SCN"};
                int pos = BeginLeader(fd, 9, 9, 3, N_ELEMENTS(sizeOfFields));

                /* Field 001 */
                sizeOfFields[nFields] +=
                    WriteSubFieldStr(fd, "IMG", 3); /* RTY */
                sizeOfFields[nFields] +=
                    WriteSubFieldStr(fd, "01", 2); /* RID */
                sizeOfFields[nFields] += WriteFieldTerminator(fd);
                nFields++;

                /* Field PAD */
                int endPos = (int)VSIFTellL(fd);
                char *pad = (char *)CPLMalloc(2047 - endPos);
                memset(pad, ' ', 2047 - endPos);
                VSIFWriteL(pad, 1, 2047 - endPos, fd);
                CPLFree(pad);
                WriteFieldTerminator(fd);
                sizeOfFields[nFields] += 2047 - endPos + 1;
                nFields++;

                /* Field SCN */
                sizeOfFields[nFields] =
                    (nNextAvailableBlock - 1) * 128 * 128 * 3;
                // nFields++;

                FinishWriteLeader(fd, pos, 9, 9, 3, N_ELEMENTS(sizeOfFields),
                                  sizeOfFields, nameOfFields);
            }
        }

        /* Write terminal field terminator */
        int offset = offsetInIMG + (nNextAvailableBlock - 1) * 128 * 128 * 3;
        VSIFSeekL(fdIMG, offset, SEEK_SET);
        WriteFieldTerminator(fdIMG);

        WriteGENFile();
        WriteTHFFile();
    }

    if (fdIMG)
    {
        VSIFCloseL(fdIMG);
    }

    if (fdGEN)
    {
        VSIFCloseL(fdGEN);
    }
    if (fdTHF)
    {
        VSIFCloseL(fdTHF);
    }

    if (TILEINDEX)
    {
        delete[] TILEINDEX;
    }
}

/************************************************************************/
/*                            GetFileList()                             */
/************************************************************************/

char **ADRGDataset::GetFileList()
{
    char **papszFileList = GDALPamDataset::GetFileList();

    if (!osGENFileName.empty() && !osIMGFileName.empty())
    {
        CPLString osMainFilename = GetDescription();
        VSIStatBufL sStat;

        const bool bMainFileReal = VSIStatL(osMainFilename, &sStat) == 0;
        if (bMainFileReal)
        {
            CPLString osShortMainFilename = CPLGetFilename(osMainFilename);
            CPLString osShortGENFileName = CPLGetFilename(osGENFileName);
            if (!EQUAL(osShortMainFilename.c_str(), osShortGENFileName.c_str()))
                papszFileList =
                    CSLAddString(papszFileList, osGENFileName.c_str());
        }
        else
            papszFileList = CSLAddString(papszFileList, osGENFileName.c_str());

        papszFileList = CSLAddString(papszFileList, osIMGFileName.c_str());
    }

    return papszFileList;
}

/************************************************************************/
/*                           AddSubDataset()                            */
/************************************************************************/

void ADRGDataset::AddSubDataset(const char *pszGENFileName,
                                const char *pszIMGFileName)
{
    char szName[80];
    int nCount = CSLCount(papszSubDatasets) / 2;

    CPLString osSubDatasetName;
    osSubDatasetName = "ADRG:";
    osSubDatasetName += pszGENFileName;
    osSubDatasetName += ",";
    osSubDatasetName += pszIMGFileName;

    snprintf(szName, sizeof(szName), "SUBDATASET_%d_NAME", nCount + 1);
    papszSubDatasets =
        CSLSetNameValue(papszSubDatasets, szName, osSubDatasetName);

    snprintf(szName, sizeof(szName), "SUBDATASET_%d_DESC", nCount + 1);
    papszSubDatasets =
        CSLSetNameValue(papszSubDatasets, szName, osSubDatasetName);
}

/************************************************************************/
/*                      GetMetadataDomainList()                         */
/************************************************************************/

char **ADRGDataset::GetMetadataDomainList()
{
    return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
                                   TRUE, "SUBDATASETS", nullptr);
}

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

char **ADRGDataset::GetMetadata(const char *pszDomain)

{
    if (pszDomain != nullptr && EQUAL(pszDomain, "SUBDATASETS"))
        return papszSubDatasets;

    return GDALPamDataset::GetMetadata(pszDomain);
}

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

const OGRSpatialReference *ADRGDataset::GetSpatialRef() const
{
    return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
}

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

CPLErr ADRGDataset::GetGeoTransform(double *padfGeoTransform)
{
    if (papszSubDatasets != nullptr)
        return CE_Failure;

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

    return CE_None;
}

/************************************************************************/
/*                          SetGeoTransform()                           */
/************************************************************************/

CPLErr ADRGDataset::SetGeoTransform(double *padfGeoTransform)

{
    memcpy(adfGeoTransform, padfGeoTransform, sizeof(double) * 6);
    bGeoTransformValid = TRUE;
    return CE_None;
}

/************************************************************************/
/*                     GetLongitudeFromString()                         */
/************************************************************************/

double ADRGDataset::GetLongitudeFromString(const char *str)
{
    char ddd[3 + 1] = {0};
    char mm[2 + 1] = {0};
    char ssdotss[5 + 1] = {0};
    int sign = (str[0] == '+') ? 1 : -1;
    str++;
    strncpy(ddd, str, 3);
    str += 3;
    strncpy(mm, str, 2);
    str += 2;
    strncpy(ssdotss, str, 5);
    return sign * (CPLAtof(ddd) + CPLAtof(mm) / 60 + CPLAtof(ssdotss) / 3600);
}

/************************************************************************/
/*                      GetLatitudeFromString()                         */
/************************************************************************/

double ADRGDataset::GetLatitudeFromString(const char *str)
{
    char ddd[2 + 1] = {0};
    char mm[2 + 1] = {0};
    char ssdotss[5 + 1] = {0};
    int sign = (str[0] == '+') ? 1 : -1;
    str++;
    strncpy(ddd, str, 2);
    str += 2;
    strncpy(mm, str, 2);
    str += 2;
    strncpy(ssdotss, str, 5);
    return sign * (CPLAtof(ddd) + CPLAtof(mm) / 60 + CPLAtof(ssdotss) / 3600);
}

/************************************************************************/
/*                      FindRecordInGENForIMG()                         */
/************************************************************************/

DDFRecord *ADRGDataset::FindRecordInGENForIMG(DDFModule &module,
                                              const char *pszGENFileName,
                                              const char *pszIMGFileName)
{
    /* Finds the GEN file corresponding to the IMG file */
    if (!module.Open(pszGENFileName, TRUE))
        return nullptr;

    CPLString osShortIMGFilename = CPLGetFilename(pszIMGFileName);

    /* Now finds the record */
    while (true)
    {
        CPLPushErrorHandler(CPLQuietErrorHandler);
        DDFRecord *record = module.ReadRecord();
        CPLPopErrorHandler();
        CPLErrorReset();
        if (record == nullptr)
            return nullptr;

        if (record->GetFieldCount() >= 5)
        {
            DDFField *field = record->GetField(0);
            DDFFieldDefn *fieldDefn = field->GetFieldDefn();
            if (!(strcmp(fieldDefn->GetName(), "001") == 0 &&
                  fieldDefn->GetSubfieldCount() == 2))
            {
                continue;
            }

            const char *RTY = record->GetStringSubfield("001", 0, "RTY", 0);
            if (RTY == nullptr)
                continue;
            /* Ignore overviews */
            if (strcmp(RTY, "OVV") == 0)
                continue;

            if (strcmp(RTY, "GIN") != 0)
                continue;

            field = record->GetField(3);
            fieldDefn = field->GetFieldDefn();

            if (!(strcmp(fieldDefn->GetName(), "SPR") == 0 &&
                  fieldDefn->GetSubfieldCount() == 15))
            {
                continue;
            }

            const char *pszBAD = record->GetStringSubfield("SPR", 0, "BAD", 0);
            if (pszBAD == nullptr || strlen(pszBAD) != 12)
                continue;
            CPLString osBAD = pszBAD;
            {
                char *c = (char *)strchr(osBAD.c_str(), ' ');
                if (c)
                    *c = 0;
            }

            if (EQUAL(osShortIMGFilename.c_str(), osBAD.c_str()))
            {
                return record;
            }
        }
    }
}

/************************************************************************/
/*                           OpenDataset()                              */
/************************************************************************/

ADRGDataset *ADRGDataset::OpenDataset(const char *pszGENFileName,
                                      const char *pszIMGFileName,
                                      DDFRecord *record)
{
    DDFModule module;

    int SCA = 0;
    int ZNA = 0;
    double PSP;
    int ARV;
    int BRV;
    double LSO;
    double PSO;
    int NFL;
    int NFC;
    CPLString osBAD;
    int TIF;
    int *TILEINDEX = nullptr;

    if (record == nullptr)
    {
        record = FindRecordInGENForIMG(module, pszGENFileName, pszIMGFileName);
        if (record == nullptr)
            return nullptr;
    }

    DDFField *field = record->GetField(1);
    if (field == nullptr)
        return nullptr;
    DDFFieldDefn *fieldDefn = field->GetFieldDefn();

    if (!(strcmp(fieldDefn->GetName(), "DSI") == 0 &&
          fieldDefn->GetSubfieldCount() == 2))
    {
        return nullptr;
    }

    const char *pszPTR = record->GetStringSubfield("DSI", 0, "PRT", 0);
    if (pszPTR == nullptr || !EQUAL(pszPTR, "ADRG"))
        return nullptr;

    const char *pszNAM = record->GetStringSubfield("DSI", 0, "NAM", 0);
    if (pszNAM == nullptr || strlen(pszNAM) != 8)
        return nullptr;
    CPLString osNAM = pszNAM;

    field = record->GetField(2);
    if (field == nullptr)
        return nullptr;
    fieldDefn = field->GetFieldDefn();

    // TODO: Support on GIN things.  And what is GIN?
    // GIN might mean general information and might be a typo of GEN.
    // if( isGIN )
    {
        if (!(strcmp(fieldDefn->GetName(), "GEN") == 0 &&
              fieldDefn->GetSubfieldCount() == 21))
        {
            return nullptr;
        }

        if (record->GetIntSubfield("GEN", 0, "STR", 0) != 3)
            return nullptr;

        SCA = record->GetIntSubfield("GEN", 0, "SCA", 0);
        CPLDebug("ADRG", "SCA=%d", SCA);

        ZNA = record->GetIntSubfield("GEN", 0, "ZNA", 0);
        CPLDebug("ADRG", "ZNA=%d", ZNA);

        PSP = record->GetFloatSubfield("GEN", 0, "PSP", 0);
        CPLDebug("ADRG", "PSP=%f", PSP);

        ARV = record->GetIntSubfield("GEN", 0, "ARV", 0);
        CPLDebug("ADRG", "ARV=%d", ARV);

        BRV = record->GetIntSubfield("GEN", 0, "BRV", 0);
        CPLDebug("ADRG", "BRV=%d", BRV);
        if (ARV <= 0 || (ZNA != 9 && ZNA != 18 && BRV <= 0))
            return nullptr;

        const char *pszLSO = record->GetStringSubfield("GEN", 0, "LSO", 0);
        if (pszLSO == nullptr || strlen(pszLSO) != 11)
            return nullptr;
        LSO = GetLongitudeFromString(pszLSO);
        CPLDebug("ADRG", "LSO=%f", LSO);

        const char *pszPSO = record->GetStringSubfield("GEN", 0, "PSO", 0);
        if (pszPSO == nullptr || strlen(pszPSO) != 10)
            return nullptr;
        PSO = GetLatitudeFromString(pszPSO);
        CPLDebug("ADRG", "PSO=%f", PSO);
    }
#if 0
    else
    {
        if( !(strcmp(fieldDefn->GetName(), "OVI") == 0 &&
                fieldDefn->GetSubfieldCount() == 5) )
        {
            return NULL;
        }

        if( record->GetIntSubfield("OVI", 0, "STR", 0) != 3 )
            return NULL;

        ARV = record->GetIntSubfield("OVI", 0, "ARV", 0);
        CPLDebug("ADRG", "ARV=%d", ARV);

        BRV = record->GetIntSubfield("OVI", 0, "BRV", 0);
        CPLDebug("ADRG", "BRV=%d", BRV);

        const char* pszLSO = record->GetStringSubfield("OVI", 0, "LSO", 0);
        if( pszLSO == NULL || strlen(pszLSO) != 11 )
            return NULL;
        LSO = GetLongitudeFromString(pszLSO);
        CPLDebug("ADRG", "LSO=%f", LSO);

        const char* pszPSO = record->GetStringSubfield("OVI", 0, "PSO", 0);
        if( pszPSO == NULL || strlen(pszPSO) != 10 )
            return NULL;
        PSO = GetLatitudeFromString(pszPSO);
        CPLDebug("ADRG", "PSO=%f", PSO);
    }
#endif

    field = record->GetField(3);
    if (field == nullptr)
        return nullptr;
    fieldDefn = field->GetFieldDefn();

    if (!(strcmp(fieldDefn->GetName(), "SPR") == 0 &&
          fieldDefn->GetSubfieldCount() == 15))
    {
        return nullptr;
    }

    NFL = record->GetIntSubfield("SPR", 0, "NFL", 0);
    CPLDebug("ADRG", "NFL=%d", NFL);

    NFC = record->GetIntSubfield("SPR", 0, "NFC", 0);
    CPLDebug("ADRG", "NFC=%d", NFC);

    const auto knIntMax = std::numeric_limits<int>::max();
    if (NFL <= 0 || NFC <= 0 || NFL > knIntMax / 128 || NFC > knIntMax / 128 ||
        NFL > (knIntMax - 1) / (NFC * 5))
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid NFL / NFC values");
        return nullptr;
    }

    int PNC = record->GetIntSubfield("SPR", 0, "PNC", 0);
    CPLDebug("ADRG", "PNC=%d", PNC);
    if (PNC != 128)
    {
        return nullptr;
    }

    int PNL = record->GetIntSubfield("SPR", 0, "PNL", 0);
    CPLDebug("ADRG", "PNL=%d", PNL);
    if (PNL != 128)
    {
        return nullptr;
    }

    const char *pszBAD = record->GetStringSubfield("SPR", 0, "BAD", 0);
    if (pszBAD == nullptr || strlen(pszBAD) != 12)
        return nullptr;
    osBAD = pszBAD;
    {
        char *c = (char *)strchr(osBAD.c_str(), ' ');
        if (c)
            *c = 0;
    }
    CPLDebug("ADRG", "BAD=%s", osBAD.c_str());

    DDFSubfieldDefn *subfieldDefn = fieldDefn->GetSubfield(14);
    if (!(strcmp(subfieldDefn->GetName(), "TIF") == 0 &&
          (subfieldDefn->GetFormat())[0] == 'A'))
    {
        return nullptr;
    }

    const char *pszTIF = record->GetStringSubfield("SPR", 0, "TIF", 0);
    if (pszTIF == nullptr)
        return nullptr;
    TIF = pszTIF[0] == 'Y';
    CPLDebug("ADRG", "TIF=%d", TIF);

    if (TIF)
    {
        if (record->GetFieldCount() != 6)
        {
            return nullptr;
        }

        field = record->GetField(5);
        if (field == nullptr)
            return nullptr;
        fieldDefn = field->GetFieldDefn();

        if (!(strcmp(fieldDefn->GetName(), "TIM") == 0))
        {
            return nullptr;
        }

        if (field->GetDataSize() != 5 * NFL * NFC + 1)
        {
            return nullptr;
        }

        try
        {
            TILEINDEX = new int[NFL * NFC];
        }
        catch (const std::exception &)
        {
            return nullptr;
        }
        const char *ptr = field->GetData();
        char offset[5 + 1] = {0};
        for (int i = 0; i < NFL * NFC; i++)
        {
            strncpy(offset, ptr, 5);
            ptr += 5;
            TILEINDEX[i] = atoi(offset);
            // CPLDebug("ADRG", "TSI[%d]=%d", i, TILEINDEX[i]);
        }
    }

    VSILFILE *fdIMG = VSIFOpenL(pszIMGFileName, "rb");
    if (fdIMG == nullptr)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot open %s\n",
                 pszIMGFileName);
        delete[] TILEINDEX;
        return nullptr;
    }

    /* Skip ISO8211 header of IMG file */
    int offsetInIMG = 0;
    char c;
    char recordName[3];
    if (VSIFReadL(&c, 1, 1, fdIMG) != 1)
    {
        VSIFCloseL(fdIMG);
        delete[] TILEINDEX;
        return nullptr;
    }
    while (!VSIFEofL(fdIMG))
    {
        if (c == 30)
        {
            if (VSIFReadL(recordName, 1, 3, fdIMG) != 3)
            {
                VSIFCloseL(fdIMG);
                delete[] TILEINDEX;
                return nullptr;
            }
            offsetInIMG += 3;
            if (STARTS_WITH(recordName, "IMG"))
            {
                offsetInIMG += 4;
                if (VSIFSeekL(fdIMG, 3, SEEK_CUR) != 0 ||
                    VSIFReadL(&c, 1, 1, fdIMG) != 1)
                {
                    VSIFCloseL(fdIMG);
                    delete[] TILEINDEX;
                    return nullptr;
                }
                while (c == ' ')
                {
                    offsetInIMG++;
                    if (VSIFReadL(&c, 1, 1, fdIMG) != 1)
                    {
                        VSIFCloseL(fdIMG);
                        delete[] TILEINDEX;
                        return nullptr;
                    }
                }
                offsetInIMG++;
                break;
            }
        }

        offsetInIMG++;
        if (VSIFReadL(&c, 1, 1, fdIMG) != 1)
        {
            VSIFCloseL(fdIMG);
            delete[] TILEINDEX;
            return nullptr;
        }
    }

    if (VSIFEofL(fdIMG))
    {
        VSIFCloseL(fdIMG);
        delete[] TILEINDEX;
        return nullptr;
    }

    CPLDebug("ADRG", "Img offset data = %d", offsetInIMG);

    ADRGDataset *poDS = new ADRGDataset();

    poDS->osGENFileName = pszGENFileName;
    poDS->osIMGFileName = pszIMGFileName;
    poDS->NFC = NFC;
    poDS->NFL = NFL;
    poDS->nRasterXSize = NFC * 128;
    poDS->nRasterYSize = NFL * 128;
    poDS->LSO = LSO;
    poDS->PSO = PSO;
    poDS->ARV = ARV;
    poDS->BRV = BRV;
    poDS->TILEINDEX = TILEINDEX;
    poDS->fdIMG = fdIMG;
    poDS->offsetInIMG = offsetInIMG;
    poDS->poOverviewDS = nullptr;

    if (ZNA == 9)
    {
        // North Polar Case
        poDS->adfGeoTransform[0] =
            111319.4907933 * (90.0 - PSO) * sin(LSO * M_PI / 180.0);
        poDS->adfGeoTransform[1] = 40075016.68558 / ARV;
        poDS->adfGeoTransform[2] = 0.0;
        poDS->adfGeoTransform[3] =
            -111319.4907933 * (90.0 - PSO) * cos(LSO * M_PI / 180.0);
        poDS->adfGeoTransform[4] = 0.0;
        poDS->adfGeoTransform[5] = -40075016.68558 / ARV;
        poDS->m_oSRS.importFromWkt(
            "PROJCS[\"ARC_System_Zone_09\",GEOGCS[\"GCS_Sphere\","
            "DATUM[\"D_Sphere\",SPHEROID[\"Sphere\",6378137.0,0.0]],"
            "PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433]],"
            "PROJECTION[\"Azimuthal_Equidistant\"],"
            "PARAMETER[\"latitude_of_center\",90],"
            "PARAMETER[\"longitude_of_center\",0],"
            "PARAMETER[\"false_easting\",0],"
            "PARAMETER[\"false_northing\",0],"
            "UNIT[\"metre\",1]]");
    }
    else if (ZNA == 18)
    {
        // South Polar Case
        poDS->adfGeoTransform[0] =
            111319.4907933 * (90.0 + PSO) * sin(LSO * M_PI / 180.0);
        poDS->adfGeoTransform[1] = 40075016.68558 / ARV;
        poDS->adfGeoTransform[2] = 0.0;
        poDS->adfGeoTransform[3] =
            111319.4907933 * (90.0 + PSO) * cos(LSO * M_PI / 180.0);
        poDS->adfGeoTransform[4] = 0.0;
        poDS->adfGeoTransform[5] = -40075016.68558 / ARV;
        poDS->m_oSRS.importFromWkt(
            "PROJCS[\"ARC_System_Zone_18\",GEOGCS[\"GCS_Sphere\","
            "DATUM[\"D_Sphere\",SPHEROID[\"Sphere\",6378137.0,0.0]],"
            "PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433]],"
            "PROJECTION[\"Azimuthal_Equidistant\"],"
            "PARAMETER[\"latitude_of_center\",-90],"
            "PARAMETER[\"longitude_of_center\",0],"
            "PARAMETER[\"false_easting\",0],"
            "PARAMETER[\"false_northing\",0],"
            "UNIT[\"metre\",1]]");
    }
    else
    {
        poDS->adfGeoTransform[0] = LSO;
        poDS->adfGeoTransform[1] = 360. / ARV;
        poDS->adfGeoTransform[2] = 0.0;
        poDS->adfGeoTransform[3] = PSO;
        poDS->adfGeoTransform[4] = 0.0;
        poDS->adfGeoTransform[5] = -360. / BRV;
        poDS->m_oSRS.importFromWkt(SRS_WKT_WGS84_LAT_LONG);
    }

    // if( isGIN )
    {
        char szValue[32];
        snprintf(szValue, sizeof(szValue), "%d", SCA);
        poDS->SetMetadataItem("ADRG_SCA", szValue);
        snprintf(szValue, sizeof(szValue), "%d", ZNA);
        poDS->SetMetadataItem("ADRG_ZNA", szValue);
    }

    poDS->SetMetadataItem("ADRG_NAM", osNAM.c_str());

    poDS->nBands = 3;
    for (int i = 0; i < poDS->nBands; i++)
        poDS->SetBand(i + 1, new ADRGRasterBand(poDS, i + 1));

    return poDS;
}

/************************************************************************/
/*                          GetGENListFromTHF()                         */
/************************************************************************/

char **ADRGDataset::GetGENListFromTHF(const char *pszFileName)
{
    DDFModule module;
    DDFRecord *record = nullptr;
    int nFilenames = 0;
    char **papszFileNames = nullptr;

    if (!module.Open(pszFileName, TRUE))
        return papszFileNames;

    while (true)
    {
        CPLPushErrorHandler(CPLQuietErrorHandler);
        record = module.ReadRecord();
        CPLPopErrorHandler();
        CPLErrorReset();
        if (record == nullptr)
            break;

        if (record->GetFieldCount() >= 2)
        {
            DDFField *field = record->GetField(0);
            DDFFieldDefn *fieldDefn = field->GetFieldDefn();
            if (!(strcmp(fieldDefn->GetName(), "001") == 0 &&
                  fieldDefn->GetSubfieldCount() == 2))
            {
                continue;
            }

            const char *RTY = record->GetStringSubfield("001", 0, "RTY", 0);
            if (RTY == nullptr || !(strcmp(RTY, "TFN") == 0))
            {
                continue;
            }

            int iVFFFieldInstance = 0;
            for (int i = 1; i < record->GetFieldCount(); i++)
            {
                field = record->GetField(i);
                fieldDefn = field->GetFieldDefn();

                if (!(strcmp(fieldDefn->GetName(), "VFF") == 0 &&
                      fieldDefn->GetSubfieldCount() == 1))
                {
                    continue;
                }

                const char *pszVFF = record->GetStringSubfield(
                    "VFF", iVFFFieldInstance++, "VFF", 0);
                if (pszVFF == nullptr)
                    continue;
                CPLString osSubFileName(pszVFF);
                char *c = (char *)strchr(osSubFileName.c_str(), ' ');
                if (c)
                    *c = 0;
                if (EQUAL(CPLGetExtension(osSubFileName.c_str()), "GEN"))
                {
                    CPLDebug("ADRG", "Found GEN file in THF : %s",
                             osSubFileName.c_str());
                    CPLString osGENFileName(CPLGetDirname(pszFileName));
                    char **tokens =
                        CSLTokenizeString2(osSubFileName.c_str(), "/\"", 0);
                    char **ptr = tokens;
                    if (ptr == nullptr)
                        continue;
                    while (*ptr)
                    {
                        char **papszDirContent =
                            VSIReadDir(osGENFileName.c_str());
                        char **ptrDir = papszDirContent;
                        if (ptrDir)
                        {
                            while (*ptrDir)
                            {
                                if (EQUAL(*ptrDir, *ptr))
                                {
                                    osGENFileName =
                                        CPLFormFilename(osGENFileName.c_str(),
                                                        *ptrDir, nullptr);
                                    CPLDebug("ADRG",
                                             "Building GEN full file name : %s",
                                             osGENFileName.c_str());
                                    break;
                                }
                                ptrDir++;
                            }
                        }
                        if (ptrDir == nullptr)
                            break;
                        CSLDestroy(papszDirContent);
                        ptr++;
                    }
                    int isNameValid = *ptr == nullptr;
                    CSLDestroy(tokens);
                    if (isNameValid)
                    {
                        papszFileNames = (char **)CPLRealloc(
                            papszFileNames, sizeof(char *) * (nFilenames + 2));
                        papszFileNames[nFilenames] =
                            CPLStrdup(osGENFileName.c_str());
                        papszFileNames[nFilenames + 1] = nullptr;
                        nFilenames++;
                    }
                }
            }
        }
    }
    return papszFileNames;
}

/************************************************************************/
/*                          GetIMGListFromGEN()                         */
/************************************************************************/

char **ADRGDataset::GetIMGListFromGEN(const char *pszFileName,
                                      int *pnRecordIndex)
{
    DDFRecord *record = nullptr;
    int nFilenames = 0;
    char **papszFileNames = nullptr;
    int nRecordIndex = -1;

    if (pnRecordIndex)
        *pnRecordIndex = -1;

    DDFModule module;
    if (!module.Open(pszFileName, TRUE))
        return nullptr;

    while (true)
    {
        nRecordIndex++;

        CPLPushErrorHandler(CPLQuietErrorHandler);
        record = module.ReadRecord();
        CPLPopErrorHandler();
        CPLErrorReset();
        if (record == nullptr)
            break;

        if (record->GetFieldCount() >= 5)
        {
            DDFField *field = record->GetField(0);
            DDFFieldDefn *fieldDefn = field->GetFieldDefn();
            if (!(strcmp(fieldDefn->GetName(), "001") == 0 &&
                  fieldDefn->GetSubfieldCount() == 2))
            {
                continue;
            }

            const char *RTY = record->GetStringSubfield("001", 0, "RTY", 0);
            if (RTY == nullptr)
                continue;
            /* Ignore overviews */
            if (strcmp(RTY, "OVV") == 0)
                continue;

            // TODO: Fix the non-GIN section or remove it.
            if (strcmp(RTY, "GIN") != 0)
                continue;

            /* make sure that the GEN file is part of an ADRG dataset, not a SRP
             * dataset, by checking that the GEN field contains a NWO subfield
             */
            const char *NWO = record->GetStringSubfield("GEN", 0, "NWO", 0);
            if (NWO == nullptr)
            {
                CSLDestroy(papszFileNames);
                return nullptr;
            }

            field = record->GetField(3);
            if (field == nullptr)
                continue;
            fieldDefn = field->GetFieldDefn();

            if (!(strcmp(fieldDefn->GetName(), "SPR") == 0 &&
                  fieldDefn->GetSubfieldCount() == 15))
            {
                continue;
            }

            const char *pszBAD = record->GetStringSubfield("SPR", 0, "BAD", 0);
            if (pszBAD == nullptr || strlen(pszBAD) != 12)
                continue;
            CPLString osBAD = pszBAD;
            {
                char *c = (char *)strchr(osBAD.c_str(), ' ');
                if (c)
                    *c = 0;
            }
            CPLDebug("ADRG", "BAD=%s", osBAD.c_str());

            /* Build full IMG file name from BAD value */
            CPLString osGENDir(CPLGetDirname(pszFileName));

            const CPLString osFileName =
                CPLFormFilename(osGENDir.c_str(), osBAD.c_str(), nullptr);
            VSIStatBufL sStatBuf;
            if (VSIStatL(osFileName, &sStatBuf) == 0)
            {
                osBAD = osFileName;
                CPLDebug("ADRG", "Building IMG full file name : %s",
                         osBAD.c_str());
            }
            else
            {
                char **papszDirContent = nullptr;
                if (strcmp(osGENDir.c_str(), "/vsimem") == 0)
                {
                    CPLString osTmp = osGENDir + "/";
                    papszDirContent = VSIReadDir(osTmp);
                }
                else
                    papszDirContent = VSIReadDir(osGENDir);
                char **ptrDir = papszDirContent;
                while (ptrDir && *ptrDir)
                {
                    if (EQUAL(*ptrDir, osBAD.c_str()))
                    {
                        osBAD =
                            CPLFormFilename(osGENDir.c_str(), *ptrDir, nullptr);
                        CPLDebug("ADRG", "Building IMG full file name : %s",
                                 osBAD.c_str());
                        break;
                    }
                    ptrDir++;
                }
                CSLDestroy(papszDirContent);
            }

            if (nFilenames == 0 && pnRecordIndex)
                *pnRecordIndex = nRecordIndex;

            papszFileNames = (char **)CPLRealloc(
                papszFileNames, sizeof(char *) * (nFilenames + 2));
            papszFileNames[nFilenames] = CPLStrdup(osBAD.c_str());
            papszFileNames[nFilenames + 1] = nullptr;
            nFilenames++;
        }
    }

    return papszFileNames;
}

/************************************************************************/
/*                                Open()                                */
/************************************************************************/

GDALDataset *ADRGDataset::Open(GDALOpenInfo *poOpenInfo)
{
    int nRecordIndex = -1;
    CPLString osGENFileName;
    CPLString osIMGFileName;
    bool bFromSubdataset = false;

    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "ADRG:"))
    {
        char **papszTokens =
            CSLTokenizeString2(poOpenInfo->pszFilename + 5, ",", 0);
        if (CSLCount(papszTokens) == 2)
        {
            osGENFileName = papszTokens[0];
            osIMGFileName = papszTokens[1];
            bFromSubdataset = true;
        }
        CSLDestroy(papszTokens);
    }
    else
    {
        if (poOpenInfo->nHeaderBytes < 500)
            return nullptr;

        CPLString osFileName(poOpenInfo->pszFilename);
        if (EQUAL(CPLGetExtension(osFileName.c_str()), "THF"))
        {
            char **papszFileNames = GetGENListFromTHF(osFileName.c_str());
            if (papszFileNames == nullptr)
                return nullptr;
            if (papszFileNames[1] == nullptr)
            {
                osFileName = papszFileNames[0];
                CSLDestroy(papszFileNames);
            }
            else
            {
                char **ptr = papszFileNames;
                ADRGDataset *poDS = new ADRGDataset();
                while (*ptr)
                {
                    char **papszIMGFileNames = GetIMGListFromGEN(*ptr);
                    char **papszIMGIter = papszIMGFileNames;
                    while (papszIMGIter && *papszIMGIter)
                    {
                        poDS->AddSubDataset(*ptr, *papszIMGIter);
                        papszIMGIter++;
                    }
                    CSLDestroy(papszIMGFileNames);

                    ptr++;
                }
                CSLDestroy(papszFileNames);
                return poDS;
            }
        }

        if (EQUAL(CPLGetExtension(osFileName.c_str()), "GEN"))
        {
            osGENFileName = osFileName;

            char **papszFileNames =
                GetIMGListFromGEN(osFileName.c_str(), &nRecordIndex);
            if (papszFileNames == nullptr)
                return nullptr;
            if (papszFileNames[1] == nullptr)
            {
                osIMGFileName = papszFileNames[0];
                CSLDestroy(papszFileNames);
            }
            else
            {
                char **ptr = papszFileNames;
                ADRGDataset *poDS = new ADRGDataset();
                while (*ptr)
                {
                    poDS->AddSubDataset(osFileName.c_str(), *ptr);
                    ptr++;
                }
                CSLDestroy(papszFileNames);
                return poDS;
            }
        }
    }

    if (!osGENFileName.empty() && !osIMGFileName.empty())
    {
        if (poOpenInfo->eAccess == GA_Update)
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                     "The ADRG driver does not support update access to "
                     "existing datasets.");
            return nullptr;
        }

        DDFModule module;
        DDFRecord *record = nullptr;
        if (nRecordIndex >= 0 && module.Open(osGENFileName.c_str(), TRUE))
        {
            for (int i = 0; i <= nRecordIndex; i++)
            {
                CPLPushErrorHandler(CPLQuietErrorHandler);
                record = module.ReadRecord();
                CPLPopErrorHandler();
                CPLErrorReset();
                if (record == nullptr)
                    break;
            }
        }

        ADRGDataset *poDS =
            OpenDataset(osGENFileName.c_str(), osIMGFileName.c_str(), record);

        if (poDS)
        {
            /* -------------------------------------------------------------- */
            /*      Initialize any PAM information.                           */
            /* -------------------------------------------------------------- */
            poDS->SetDescription(poOpenInfo->pszFilename);
            poDS->TryLoadXML();

            /* -------------------------------------------------------------- */
            /*      Check for external overviews.                             */
            /* -------------------------------------------------------------- */
            if (bFromSubdataset)
                poDS->oOvManager.Initialize(poDS, osIMGFileName.c_str());
            else
                poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename);

            return poDS;
        }
    }

    return nullptr;
}

/************************************************************************/
/*                               Create()                               */
/************************************************************************/

GDALDataset *ADRGDataset::Create(const char *pszFilename, int nXSize,
                                 int nYSize, int nBandsIn, GDALDataType eType,
                                 CPL_UNUSED char **papszOptions)
{
    if (eType != GDT_Byte)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Attempt to create ADRG dataset with an illegal "
                 "data type (%s), only Byte supported by the format.",
                 GDALGetDataTypeName(eType));

        return nullptr;
    }

    if (nBandsIn != 3)
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "ADRG driver doesn't support %d bands. "
                 "Must be 3 (rgb) bands.",
                 nBandsIn);
        return nullptr;
    }

    if (nXSize < 1 || nYSize < 1)
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "Specified pixel dimensions (% d x %d) are bad.", nXSize,
                 nYSize);
    }

    if (!EQUAL(CPLGetExtension(pszFilename), "gen"))
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "Invalid filename. Must be ABCDEF01.GEN");
        return nullptr;
    }

    CPLString osBaseFileName(CPLGetBasename(pszFilename));
    if (osBaseFileName.size() != 8 || osBaseFileName[6] != DIGIT_ZERO ||
        osBaseFileName[7] != '1')
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "Invalid filename. "
                 "Must be xxxxxx01.GEN where x is between A and Z");
        return nullptr;
    }

    for (int i = 0; i < 6; i++)
    {
        if (!(osBaseFileName[i] >= 'A' && osBaseFileName[i] <= 'Z'))
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                     "Invalid filename. "
                     "Must be xxxxxx01.GEN where x is between A and Z");
            return nullptr;
        }
    }

    VSILFILE *fdGEN = VSIFOpenL(pszFilename, "wb");
    if (fdGEN == nullptr)
    {
        CPLError(CE_Failure, CPLE_FileIO, "Cannot create GEN file : %s.\n",
                 pszFilename);
        return nullptr;
    }

    CPLString osDirname(CPLGetDirname(pszFilename));
    CPLString osTransh01THF(
        CPLFormFilename(osDirname.c_str(), "TRANSH01.THF", nullptr));
    VSILFILE *fdTHF = VSIFOpenL(osTransh01THF.c_str(), "wb");
    if (fdTHF == nullptr)
    {
        VSIFCloseL(fdGEN);
        CPLError(CE_Failure, CPLE_FileIO, "Cannot create THF file : %s.\n",
                 osTransh01THF.c_str());
        return nullptr;
    }

    CPLString osImgFilename = CPLResetExtension(pszFilename, "IMG");
    VSILFILE *fdIMG = VSIFOpenL(osImgFilename.c_str(), "w+b");
    if (fdIMG == nullptr)
    {
        VSIFCloseL(fdGEN);
        VSIFCloseL(fdTHF);
        CPLError(CE_Failure, CPLE_FileIO, "Cannot create image file : %s.\n",
                 osImgFilename.c_str());
        return nullptr;
    }

    ADRGDataset *poDS = new ADRGDataset();

    poDS->eAccess = GA_Update;

    poDS->fdGEN = fdGEN;
    poDS->fdIMG = fdIMG;
    poDS->fdTHF = fdTHF;

    poDS->osBaseFileName = std::move(osBaseFileName);
    poDS->bCreation = TRUE;
    poDS->nNextAvailableBlock = 1;
    poDS->NFC = (nXSize + 127) / 128;
    poDS->NFL = (nYSize + 127) / 128;
    poDS->nRasterXSize = nXSize;
    poDS->nRasterYSize = nYSize;
    poDS->bGeoTransformValid = FALSE;
    poDS->TILEINDEX = new int[poDS->NFC * poDS->NFL];
    memset(poDS->TILEINDEX, 0, sizeof(int) * poDS->NFC * poDS->NFL);
    poDS->offsetInIMG = 2048;
    poDS->poOverviewDS = nullptr;

    poDS->nBands = 3;
    for (int i = 0; i < poDS->nBands; i++)
        poDS->SetBand(i + 1, new ADRGRasterBand(poDS, i + 1));

    return poDS;
}

/************************************************************************/
/*                  WriteGENFile_Header()                               */
/************************************************************************/

static void WriteGENFile_Header(VSILFILE *fd)
{
    int nFields = 0;
    int sizeOfFields[] = {
        0, 0, 0, 0, 0, 0, 0, 0, 0,
    };
    const char *nameOfFields[] = {"000", "001", "DRF", "DSI", "OVI",
                                  "GEN", "SPR", "BDF", "TIM"};
    const int pos = BeginHeader(fd, 3, 4, 3, N_ELEMENTS(sizeOfFields));

    sizeOfFields[nFields++] += WriteFieldDecl(
        fd, ' ', ' ', "GENERAL_INFORMATION_FILE", "", ""); /* 000 */
    sizeOfFields[nFields++] +=
        WriteFieldDecl(fd, '1', '0', "RECORD_ID_FIELD", /* 001 */
                       "RTY!RID", "(A(3),A(2))");
    sizeOfFields[nFields++] +=
        WriteFieldDecl(fd, '1', '1', "DATA_SET_DESCRIPTION_FIELD", /* DRF */
                       "NSH!NSV!NOZ!NOS", "(4I(2))");
    sizeOfFields[nFields++] +=
        WriteFieldDecl(fd, '1', '0', "DATA_SET-ID_FIELD", /* DSI */
                       "PRT!NAM", "(A(4),A(8))");
    sizeOfFields[nFields++] +=
        WriteFieldDecl(fd, '1', '6', "OVERVIEW_INFORMATION_FIELD", /* OVI */
                       "STR!ARV!BRV!LSO!PSO", "(I(1),I(8),I(8),A(11),A(10))");
    sizeOfFields[nFields++] += WriteFieldDecl(
        fd, '1', '6', "GENERAL_INFORMATION_FIELD", /* GEN */
        "STR!LOD!LAD!UNIloa!SWO!SWA!NWO!NWA!NEO!NEA!SEO!SEA!SCA!ZNA!PSP!IMR!"
        "ARV!BRV!LSO!PSO!TXT",
        "(I(1),2R(6),I(3),A(11),A(10),A(11),A(10),A(11),A(10),A(11),A(10),I(9),"
        "I(2),R(5),A(1),2I(8),A(11),A(10),A(64))");
    sizeOfFields[nFields++] += WriteFieldDecl(
        fd, '1', '6', "DATA_SET_PARAMETERS_FIELD", /* SPR */
        "NUL!NUS!NLL!NLS!NFL!NFC!PNC!PNL!COD!ROD!POR!PCB!PVB!BAD!TIF",
        "(4I(6),2I(3),2I(6),5I(1),A(12),A(1))");
    sizeOfFields[nFields++] +=
        WriteFieldDecl(fd, '2', '6', "BAND_ID_FIELD", /* BDF */
                       "*BID!WS1!WS2", "(A(5),I(5),I(5))");
    sizeOfFields[nFields++] +=
        WriteFieldDecl(fd, '2', '1', "TILE_INDEX_MAP_FIELD", /* TIM */
                       "*TSI", "(I(5))");

    FinishWriteHeader(fd, pos, 3, 4, 3, N_ELEMENTS(sizeOfFields), sizeOfFields,
                      nameOfFields);
}

/************************************************************************/
/*            WriteGENFile_DataSetDescriptionRecord()                   */
/************************************************************************/

/* Write DATA_SET_DESCRIPTION_RECORD */
static void WriteGENFile_DataSetDescriptionRecord(VSILFILE *fd)
{
    int nFields = 0;
    int sizeOfFields[] = {0, 0};
    const char *nameOfFields[] = {"001", "DRF"};
    const int pos = BeginLeader(fd, 3, 4, 3, N_ELEMENTS(sizeOfFields));

    /* Field 001 */
    sizeOfFields[nFields] += WriteSubFieldStr(fd, "DSS", 3); /* RTY */
    sizeOfFields[nFields] += WriteSubFieldStr(fd, "01", 2);  /* RID */
    sizeOfFields[nFields] += WriteFieldTerminator(fd);
    nFields++;

    /* Field DRF */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 1, 2); /* NSH */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 1, 2); /* NSV */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 1, 2); /* NOZ */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 1, 2); /* NOS */
    sizeOfFields[nFields] += WriteFieldTerminator(fd);
    /* nFields++; */

    FinishWriteLeader(fd, pos, 3, 4, 3, N_ELEMENTS(sizeOfFields), sizeOfFields,
                      nameOfFields);
}

/************************************************************************/
/*                    WriteGENFile_OverviewRecord()                     */
/************************************************************************/

/* Write OVERVIEW_RECORD */
static void WriteGENFile_OverviewRecord(VSILFILE *fd, CPLString &osBaseFileName,
                                        int ARV, int BRV, double LSO,
                                        double PSO, int nOvSizeX, int nOvSizeY,
                                        int NFL, int NFC, int *TILEINDEX)
{
    int nFields = 0;
    int sizeOfFields[] = {0, 0, 0, 0, 0, 0};
    const char *nameOfFields[] = {"001", "DSI", "OVI", "SPR", "BDF", "TIM"};
    const int pos = BeginLeader(fd, 9, 9, 3, N_ELEMENTS(sizeOfFields));

    /* Field 001 */
    sizeOfFields[nFields] += WriteSubFieldStr(fd, "OVV", 3); /* RTY */
    sizeOfFields[nFields] += WriteSubFieldStr(fd, "01", 2);  /* RID */
    sizeOfFields[nFields] += WriteFieldTerminator(fd);
    nFields++;

    /* Field DSI */
    sizeOfFields[nFields] += WriteSubFieldStr(fd, "ADRG", 4);         /* PRT */
    sizeOfFields[nFields] += WriteSubFieldStr(fd, osBaseFileName, 8); /* NAM */
    sizeOfFields[nFields] += WriteFieldTerminator(fd);
    nFields++;

    /* Field OVI */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 3, 1);        /* STR */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, ARV, 8);      // ARV - FIXME
    sizeOfFields[nFields] += WriteSubFieldInt(fd, BRV, 8);      // BRV - FIXME
    sizeOfFields[nFields] += WriteLongitude(fd, LSO); /* LSO */ /* FIXME */
    sizeOfFields[nFields] += WriteLatitude(fd, PSO); /* PSO */  /* FIXME */
    sizeOfFields[nFields] += WriteFieldTerminator(fd);
    nFields++;

    /* Field SPR */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 0, 6);            /* NUL */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, nOvSizeX - 1, 6); /* NUS */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, nOvSizeY - 1, 6); /* NLL */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 0, 6);            /* NLS */
    sizeOfFields[nFields] +=
        WriteSubFieldInt(fd, (nOvSizeY + 127) / 128, 3); /* NFL */
    sizeOfFields[nFields] +=
        WriteSubFieldInt(fd, (nOvSizeX + 127) / 128, 3);   /* NFC */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 128, 6); /* PNC */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 128, 6); /* PNL */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 0, 1);   /* COD */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 1, 1);   /* ROD */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 0, 1);   /* POR */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 0, 1);   /* PCB */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 8, 1);   /* PVB */
    char tmp[12 + 1];
    snprintf(tmp, sizeof(tmp), "%s.IMG", osBaseFileName.c_str()); /* FIXME */
    sizeOfFields[nFields] += WriteSubFieldStr(fd, tmp, 12);       /* BAD */
    sizeOfFields[nFields] += WriteSubFieldStr(fd, "Y", 1);        /* TIF */
    sizeOfFields[nFields] += WriteFieldTerminator(fd);
    nFields++;

    /* Field BDF */
    sizeOfFields[nFields] += WriteSubFieldStr(fd, "Red", 5);   /* BID */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 0, 5);       /* WS1 */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 0, 5);       /* WS2 */
    sizeOfFields[nFields] += WriteSubFieldStr(fd, "Green", 5); /* BID */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 0, 5);       /* WS1 */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 0, 5);       /* WS2 */
    sizeOfFields[nFields] += WriteSubFieldStr(fd, "Blue", 5);  /* BID */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 0, 5);       /* WS1 */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 0, 5);       /* WS2 */
    sizeOfFields[nFields] += WriteFieldTerminator(fd);
    nFields++;

    /* Field TIM */
    for (int i = 0; i < NFL * NFC; i++)
    {
        sizeOfFields[nFields] += WriteSubFieldInt(fd, TILEINDEX[i], 5);  // TSI
    }
    sizeOfFields[nFields] += WriteFieldTerminator(fd);
    /* nFields++; */

    FinishWriteLeader(fd, pos, 9, 9, 3, N_ELEMENTS(sizeOfFields), sizeOfFields,
                      nameOfFields);
}

/************************************************************************/
/*              WriteGENFile_GeneralInformationRecord()                 */
/************************************************************************/

/* Write GENERAL_INFORMATION_RECORD */
static void WriteGENFile_GeneralInformationRecord(
    VSILFILE *fd, CPLString &osNAM, CPLString &osBAD, int ARV, int BRV,
    double LSO, double PSO, double *adfGeoTransform, int SCA, int nRasterXSize,
    int nRasterYSize, int NFL, int NFC, int *TILEINDEX)

{
    int nFields = 0;
    int sizeOfFields[] = {0, 0, 0, 0, 0, 0};
    const char *nameOfFields[] = {"001", "DSI", "GEN", "SPR", "BDF", "TIM"};
    int pos = BeginLeader(fd, 9, 9, 3, N_ELEMENTS(sizeOfFields));

    /* Field 001 */
    sizeOfFields[nFields] += WriteSubFieldStr(fd, "GIN", 3); /* RTY */
    sizeOfFields[nFields] += WriteSubFieldStr(fd, "01", 2);  /* RID */
    sizeOfFields[nFields] += WriteFieldTerminator(fd);
    nFields++;

    /* Field DSI */
    sizeOfFields[nFields] += WriteSubFieldStr(fd, "ADRG", 4);        /* PRT */
    sizeOfFields[nFields] += WriteSubFieldStr(fd, osNAM.c_str(), 8); /* NAM */
    sizeOfFields[nFields] += WriteFieldTerminator(fd);
    nFields++;

    /* Field `GEN */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 3, 1);         /* STR */
    sizeOfFields[nFields] += WriteSubFieldStr(fd, "0099.9", 6);  // LOD - FIXME
    sizeOfFields[nFields] += WriteSubFieldStr(fd, "0099.9", 6);  // LAD - FIXME
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 16, 3);  // UNIloa - FIXME
    sizeOfFields[nFields] += WriteLongitude(fd, LSO);      /* SWO */
    sizeOfFields[nFields] +=
        WriteLatitude(fd, PSO + nRasterYSize * adfGeoTransform[5]); /* SWA */
    sizeOfFields[nFields] += WriteLongitude(fd, LSO);               /* NWO */
    sizeOfFields[nFields] += WriteLatitude(fd, PSO);                /* NWA */
    sizeOfFields[nFields] +=
        WriteLongitude(fd, LSO + nRasterXSize * adfGeoTransform[1]); /* NEO */
    sizeOfFields[nFields] += WriteLatitude(fd, PSO);                 /* NEA */
    sizeOfFields[nFields] +=
        WriteLongitude(fd, LSO + nRasterXSize * adfGeoTransform[1]); /* SEO */
    sizeOfFields[nFields] +=
        WriteLatitude(fd, PSO + nRasterYSize * adfGeoTransform[5]); /* SEA */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, SCA, 9);          /* SCA */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 1, 2); /* ZNA */  /* FIXME */
    sizeOfFields[nFields] += WriteSubFieldStr(fd, "100.0", 5);      /* PSP */
    sizeOfFields[nFields] += WriteSubFieldStr(fd, "N", 1);          /* IMR */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, ARV, 8);          /* ARV */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, BRV, 8);          /* BRV */
    sizeOfFields[nFields] += WriteLongitude(fd, LSO);               /* LSO */
    sizeOfFields[nFields] += WriteLatitude(fd, PSO);                /* PSO */
    sizeOfFields[nFields] += WriteSubFieldStr(fd, "", 64);          /* TXT */
    sizeOfFields[nFields] += WriteFieldTerminator(fd);
    nFields++;

    /* Field SPR */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 0, 6); /* NUL */
    sizeOfFields[nFields] +=
        WriteSubFieldInt(fd, nRasterXSize - 1, 6); /* NUS */
    sizeOfFields[nFields] +=
        WriteSubFieldInt(fd, nRasterYSize - 1, 6);       /* NLL */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 0, 6); /* NLS */
    sizeOfFields[nFields] +=
        WriteSubFieldInt(fd, (nRasterYSize + 127) / 128, 3); /* NFL */
    sizeOfFields[nFields] +=
        WriteSubFieldInt(fd, (nRasterXSize + 127) / 128, 3);          /* NFC */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 128, 6);            /* PNC */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 128, 6);            /* PNL */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 0, 1);              /* COD */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 1, 1);              /* ROD */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 0, 1);              /* POR */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 0, 1);              /* PCB */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 8, 1);              /* PVB */
    sizeOfFields[nFields] += WriteSubFieldStr(fd, osBAD.c_str(), 12); /* BAD */
    sizeOfFields[nFields] += WriteSubFieldStr(fd, "Y", 1);            /* TIF */
    sizeOfFields[nFields] += WriteFieldTerminator(fd);
    nFields++;

    /* Field BDF */
    sizeOfFields[nFields] += WriteSubFieldStr(fd, "Red", 5);   /* BID */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 0, 5);       /* WS1 */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 0, 5);       /* WS2 */
    sizeOfFields[nFields] += WriteSubFieldStr(fd, "Green", 5); /* BID */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 0, 5);       /* WS1 */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 0, 5);       /* WS2 */
    sizeOfFields[nFields] += WriteSubFieldStr(fd, "Blue", 5);  /* BID */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 0, 5);       /* WS1 */
    sizeOfFields[nFields] += WriteSubFieldInt(fd, 0, 5);       /* WS2 */
    sizeOfFields[nFields] += WriteFieldTerminator(fd);
    nFields++;

    /* Field TIM */
    for (int i = 0; i < NFL * NFC; i++)
    {
        sizeOfFields[nFields] += WriteSubFieldInt(fd, TILEINDEX[i], 5);  // TSI
    }
    sizeOfFields[nFields] += WriteFieldTerminator(fd);
    /* nFields++; */

    FinishWriteLeader(fd, pos, 9, 9, 3, N_ELEMENTS(sizeOfFields), sizeOfFields,
                      nameOfFields);
}

/************************************************************************/
/*                        WriteGENFile()                                */
/************************************************************************/

void ADRGDataset::WriteGENFile()
{
    if (!bGeoTransformValid)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "No geo transform available !");
        adfGeoTransform[0] = 0;
        adfGeoTransform[3] = 0;
        adfGeoTransform[1] = 1;
        adfGeoTransform[5] = 1;
    }

    LSO = adfGeoTransform[0];
    PSO = adfGeoTransform[3];
    ARV = (int)floor(360. / adfGeoTransform[1] + .5);
    BRV = (int)floor(-360. / adfGeoTransform[5] + .5);

    /*ARV = ((ARV + 255) / 512) * 512;
    BRV = ((BRV + 255) / 512) * 512;*/

    const int SCA = (int)floor(1000000. * 400384 / BRV + 0.5);

    const int nOvSizeX = nRasterXSize;  // FIXME
    const int nOvSizeY = nRasterYSize;  // FIXME

    /* Write header */
    WriteGENFile_Header(fdGEN);

    /* Write DATA_SET_DESCRIPTION_RECORD */
    WriteGENFile_DataSetDescriptionRecord(fdGEN);

    /* Write OVERVIEW_RECORD */
    WriteGENFile_OverviewRecord(fdGEN, osBaseFileName, ARV, BRV, LSO, PSO,
                                nOvSizeX, nOvSizeY, NFL, NFC, TILEINDEX);

    /* Write GENERAL_INFORMATION_RECORD */
    CPLString osNAM = osBaseFileName;
    char tmp[12 + 1] = {};
    snprintf(tmp, sizeof(tmp), "%s.IMG", osNAM.c_str());
    CPLString osBAD = tmp;
    WriteGENFile_GeneralInformationRecord(
        fdGEN, osNAM, osBAD, ARV, BRV, LSO, PSO, adfGeoTransform, SCA,
        nRasterXSize, nRasterYSize, NFL, NFC, TILEINDEX);

    if (CPLTestBool(CPLGetConfigOption("ADRG_SIMULATE_MULTI_IMG", "OFF")))
    {
        strncpy(tmp, osBaseFileName.c_str(), 6);
        tmp[6] = '\0';
        strcat(tmp, "02");
        osNAM = tmp;
        snprintf(tmp, sizeof(tmp), "%s.IMG", osNAM.c_str());
        osBAD = tmp;
        WriteGENFile_GeneralInformationRecord(
            fdGEN, osNAM, osBAD, ARV, BRV, LSO, PSO, adfGeoTransform, SCA,
            nRasterXSize, nRasterYSize, NFL, NFC, TILEINDEX);
    }
}

/************************************************************************/
/*                        WriteTHFFile()                                */
/************************************************************************/

void ADRGDataset::WriteTHFFile()
{
    VSILFILE *fd = fdTHF;

    /* Write header */
    {
        int nFields = 0;
        int sizeOfFields[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
        const char *nameOfFields[] = {"000", "001", "VDR", "FDR", "QSR", "QUV",
                                      "CPS", "CPT", "SPR", "BDF", "VFF"};
        const int pos = BeginHeader(fd, 3, 4, 3, N_ELEMENTS(sizeOfFields));

        sizeOfFields[nFields++] += WriteFieldDecl(
            fd, ' ', ' ', "TRANSMITTAL_HEADER_FILE", "", ""); /* 000 */
        sizeOfFields[nFields++] +=
            WriteFieldDecl(fd, '1', '0', "RECORD_ID_FIELD", /* 001 */
                           "RTY!RID", "(A(3),A(2))");
        sizeOfFields[nFields++] += WriteFieldDecl(
            fd, '1', '6', "TRANSMITTAL_HEADER_FIELD", /* VDR */
            "MSD!VOO!ADR!NOV!SQN!NOF!URF!END!DAT",
            "(A(1),A(200),A(1),I(1),I(1),I(3),A(16),I(3),A(12))");
        sizeOfFields[nFields++] +=
            WriteFieldDecl(fd, '1', '6', "DATA_SET_DESCRIPTION_FIELD", /* FDR */
                           "NAM!STR!PRT!SWO!SWA!NEO!NEA",
                           "(A(8),I(1),A(4),A(11),A(10),A(11),A(10))");
        sizeOfFields[nFields++] +=
            WriteFieldDecl(fd, '1', '0', "SECURITY_AND_RELEASE_FIELD", /* QSR */
                           "QSS!QOD!DAT!QLE", "(A(1),A(1),A(12),A(200))");
        sizeOfFields[nFields++] += WriteFieldDecl(
            fd, '1', '0', "VOLUME_UP_TO_DATENESS_FIELD", /* QUV */
            "SRC!DAT!SPA", "(A(100),A(12),A(20))");
        sizeOfFields[nFields++] += WriteFieldDecl(
            fd, '1', '6', "TEST_PATCH_IDENTIFIER_FIELD", /* CPS */
            "PNM!DWV!REF!PUR!PIR!PIG!PIB",
            "(A(7),I(6),R(5),R(5),I(3),I(3),I(3))");
        sizeOfFields[nFields++] += WriteFieldDecl(
            fd, '1', '6', "TEST_PATCH_INFORMATION_FIELD", /* CPT */
            "STR!SCR", "(I(1),A(100))");
        sizeOfFields[nFields++] += WriteFieldDecl(
            fd, '1', '6', "DATA_SET_PARAMETERS_FIELD", /* SPR */
            "NUL!NUS!NLL!NLS!NFL!NFC!PNC!PNL!COD!ROD!POR!PCB!PVB!BAD!TIF",
            "(I(6),I(6),I(6),I(6),I(3),I(3),I(6),I(6),I(1),I(1),I(1),I(1),"
            "I(1),A(12),A(1))");
        sizeOfFields[nFields++] +=
            WriteFieldDecl(fd, '2', '6', "BAND_ID_FIELD", /* BDF */
                           "*BID!WS1!WS2", "(A(5),I(5),I(5))");
        sizeOfFields[nFields++] += WriteFieldDecl(
            fd, '1', '0', "TRANSMITTAL_FILENAMES_FIELD", "VFF", "(A(51))");

        FinishWriteHeader(fd, pos, 3, 4, 3, N_ELEMENTS(sizeOfFields),
                          sizeOfFields, nameOfFields);
    }

    /* Write TRANSMITTAL_DESCRIPTION_RECORD */
    {
        int nFields = 0;
        int sizeOfFields[] = {0, 0, 0};
        const char *nameOfFields[] = {"001", "VDR", "FDR"};
        int pos = BeginLeader(fd, 3, 4, 3, N_ELEMENTS(sizeOfFields));

        /* Field 001 */
        sizeOfFields[nFields] += WriteSubFieldStr(fd, "VTH", 3); /* RTY */
        sizeOfFields[nFields] += WriteSubFieldStr(fd, "01", 2);  /* RID */
        sizeOfFields[nFields] += WriteFieldTerminator(fd);
        nFields++;

        /* Field VDR */
        sizeOfFields[nFields] += WriteSubFieldStr(fd, " ", 1); /* MSD */
        // VOO - Title and address of originator
        sizeOfFields[nFields] += WriteSubFieldStr(fd, "", 200);
        sizeOfFields[nFields] += WriteSubFieldStr(fd, " ", 1); /* ADR */
        sizeOfFields[nFields] += WriteSubFieldInt(fd, 1, 1);   /* NOV */
        sizeOfFields[nFields] += WriteSubFieldInt(fd, 1, 1);   /* SQN */
        sizeOfFields[nFields] += WriteSubFieldInt(fd, 1, 3);   /* NOF */
        // URF - DMA stock number for this CDROM
        sizeOfFields[nFields] += WriteSubFieldStr(fd, "", 16);
        sizeOfFields[nFields] += WriteSubFieldInt(fd, 1, 3); /* END */
        sizeOfFields[nFields] +=
            WriteSubFieldStr(fd, "017,19940101", 12);  // DAT - Publication date
        sizeOfFields[nFields] += WriteFieldTerminator(fd);
        nFields++;

        /* Field FDR */
        sizeOfFields[nFields] +=
            WriteSubFieldStr(fd, osBaseFileName, 8);              // NAM
        sizeOfFields[nFields] += WriteSubFieldInt(fd, 3, 1);      /* STR */
        sizeOfFields[nFields] += WriteSubFieldStr(fd, "ADRG", 4); /* PRT */
        sizeOfFields[nFields] += WriteLongitude(fd, LSO);         /* SWO */
        sizeOfFields[nFields] +=
            WriteLatitude(fd, PSO + nRasterYSize * adfGeoTransform[5]);  // SWA
        sizeOfFields[nFields] +=
            WriteLongitude(fd, LSO + nRasterXSize * adfGeoTransform[1]);  // NEO
        sizeOfFields[nFields] += WriteLatitude(fd, PSO); /* NEA */
        sizeOfFields[nFields] += WriteFieldTerminator(fd);
        /* nFields++; */

        FinishWriteLeader(fd, pos, 3, 4, 3, N_ELEMENTS(sizeOfFields),
                          sizeOfFields, nameOfFields);
    }

    /* Write SECURITY_AND_UPDATE_RECORD */
    {
        int nFields = 0;
        int sizeOfFields[] = {0, 0, 0};
        const char *nameOfFields[] = {"001", "QSR", "QUV"};
        int pos = BeginLeader(fd, 3, 4, 3, N_ELEMENTS(sizeOfFields));

        /* Field 001 */
        sizeOfFields[nFields] += WriteSubFieldStr(fd, "LCF", 3); /* RTY */
        sizeOfFields[nFields] += WriteSubFieldStr(fd, "01", 2);  /* RID */
        sizeOfFields[nFields] += WriteFieldTerminator(fd);
        nFields++;

        /* Field VDR */
        sizeOfFields[nFields] += WriteSubFieldStr(fd, "U", 1);  /* QSS */
        sizeOfFields[nFields] += WriteSubFieldStr(fd, "N", 1);  /* QOD */
        sizeOfFields[nFields] += WriteSubFieldStr(fd, "", 12);  /* DAT */
        sizeOfFields[nFields] += WriteSubFieldStr(fd, "", 200); /* QLE */
        sizeOfFields[nFields] += WriteFieldTerminator(fd);
        nFields++;

        /* Field FDR */
        sizeOfFields[nFields] += WriteSubFieldStr(
            fd, "MILITARY SPECIFICATION ARC DIGITIZED RASTER GRAPHICS (ADRG)",
            100); /* SRC */
        sizeOfFields[nFields] +=
            WriteSubFieldStr(fd, "022,19900222", 12); /* DAT */
        sizeOfFields[nFields] +=
            WriteSubFieldStr(fd, "MIL-A-89007", 20); /* SPA */
        sizeOfFields[nFields] += WriteFieldTerminator(fd);
        /* nFields++; */

        FinishWriteLeader(fd, pos, 3, 4, 3, N_ELEMENTS(sizeOfFields),
                          sizeOfFields, nameOfFields);
    }

    /* Write TEST_PATCH_DATA_RECORD */
    {
        int nFields = 0;
        int sizeOfFields[] = {0, 0, 0, 0, 0};
        const char *nameOfFields[] = {"001", "CPS", "CPT", "SPR", "BDF"};
        const int pos = BeginLeader(fd, 3, 4, 3, N_ELEMENTS(sizeOfFields));

        /* Field 001 */
        sizeOfFields[nFields] += WriteSubFieldStr(fd, "TPA", 3); /* RTY */
        sizeOfFields[nFields] += WriteSubFieldStr(fd, "01", 2);  /* RID */
        sizeOfFields[nFields] += WriteFieldTerminator(fd);
        nFields++;

        /* Field CPS */
        sizeOfFields[nFields] += WriteSubFieldStr(fd, "Black", 7); /* PNM */
        sizeOfFields[nFields] += WriteSubFieldStr(fd, "", 6);      /* DMV */
        sizeOfFields[nFields] += WriteSubFieldStr(fd, "", 5);      /* REF */
        sizeOfFields[nFields] += WriteSubFieldStr(fd, "", 5);      /* PUR */
        sizeOfFields[nFields] += WriteSubFieldInt(fd, 0, 3);       /* PIR */
        sizeOfFields[nFields] += WriteSubFieldInt(fd, 0, 3);       /* PIG */
        sizeOfFields[nFields] += WriteSubFieldInt(fd, 0, 3);       /* PIB */
        sizeOfFields[nFields] += WriteFieldTerminator(fd);
        nFields++;

        /* Field CPT */
        sizeOfFields[nFields] += WriteSubFieldInt(fd, 3, 1);    /* STR */
        sizeOfFields[nFields] += WriteSubFieldStr(fd, "", 100); /* SCR */
        sizeOfFields[nFields] += WriteFieldTerminator(fd);
        nFields++;

        const int nPatchXSize = 512;
        const int nPatchYSize = 512;

        /* Field SPR */
        sizeOfFields[nFields] += WriteSubFieldInt(fd, 0, 6); /* NUL */
        sizeOfFields[nFields] +=
            WriteSubFieldInt(fd, nPatchXSize - 1, 6); /* NUS */
        sizeOfFields[nFields] +=
            WriteSubFieldInt(fd, nPatchYSize - 1, 6);        /* NLL */
        sizeOfFields[nFields] += WriteSubFieldInt(fd, 0, 6); /* NLS */
        sizeOfFields[nFields] +=
            WriteSubFieldInt(fd, (nPatchYSize + 127) / 128, 3); /* NFL */
        sizeOfFields[nFields] +=
            WriteSubFieldInt(fd, (nPatchXSize + 127) / 128, 3); /* NFC */
        sizeOfFields[nFields] += WriteSubFieldInt(fd, 128, 6);  /* PNC */
        sizeOfFields[nFields] += WriteSubFieldInt(fd, 128, 6);  /* PNL */
        sizeOfFields[nFields] += WriteSubFieldInt(fd, 0, 1);    /* COD */
        sizeOfFields[nFields] += WriteSubFieldInt(fd, 1, 1);    /* ROD */
        sizeOfFields[nFields] += WriteSubFieldInt(fd, 0, 1);    /* POR */
        sizeOfFields[nFields] += WriteSubFieldInt(fd, 0, 1);    /* PCB */
        sizeOfFields[nFields] += WriteSubFieldInt(fd, 8, 1);    /* PVB */
        // BAD
        sizeOfFields[nFields] += WriteSubFieldStr(fd, "TESTPA01.CPH", 12);
        sizeOfFields[nFields] += WriteSubFieldStr(fd, "N", 1); /* TIF */
        sizeOfFields[nFields] += WriteFieldTerminator(fd);
        nFields++;

        /* Field BDF */
        sizeOfFields[nFields] += WriteSubFieldStr(fd, "Red", 5);   /* BID */
        sizeOfFields[nFields] += WriteSubFieldInt(fd, 0, 5);       /* WS1 */
        sizeOfFields[nFields] += WriteSubFieldInt(fd, 0, 5);       /* WS2 */
        sizeOfFields[nFields] += WriteSubFieldStr(fd, "Green", 5); /* BID */
        sizeOfFields[nFields] += WriteSubFieldInt(fd, 0, 5);       /* WS1 */
        sizeOfFields[nFields] += WriteSubFieldInt(fd, 0, 5);       /* WS2 */
        sizeOfFields[nFields] += WriteSubFieldStr(fd, "Blue", 5);  /* BID */
        sizeOfFields[nFields] += WriteSubFieldInt(fd, 0, 5);       /* WS1 */
        sizeOfFields[nFields] += WriteSubFieldInt(fd, 0, 5);       /* WS2 */
        sizeOfFields[nFields] += WriteFieldTerminator(fd);
        /* nFields++; */

        FinishWriteLeader(fd, pos, 3, 4, 3, N_ELEMENTS(sizeOfFields),
                          sizeOfFields, nameOfFields);
    }

    /* Write TRANSMITTAL_FILENAMES_RECORD */
    {
        int nFields = 0;
        int sizeOfFields[] = {0, 0, 0, 0, 0, 0, 0};

        /* Debug option to simulate ADRG datasets made of several images */
        int nTotalFields =
            CPLTestBool(CPLGetConfigOption("ADRG_SIMULATE_MULTI_IMG", "OFF"))
                ? 6
                : 5;

        const char *nameOfFields[] = {"001", "VFF", "VFF", "VFF",
                                      "VFF", "VFF", "VFF"};
        const int pos = BeginLeader(fd, 9, 9, 3, nTotalFields);

        /* Field 001 */
        sizeOfFields[nFields] += WriteSubFieldStr(fd, "TFN", 3); /* RTY */
        sizeOfFields[nFields] += WriteSubFieldStr(fd, "01", 2);  /* RID */
        sizeOfFields[nFields] += WriteFieldTerminator(fd);
        nFields++;

        /* Field VFF */
        sizeOfFields[nFields] += WriteSubFieldStr(fd, "TRANSH01.THF", 51);
        sizeOfFields[nFields] += WriteFieldTerminator(fd);
        nFields++;

        /* Field VFF */
        sizeOfFields[nFields] += WriteSubFieldStr(fd, "TESTPA01.CPH", 51);
        sizeOfFields[nFields] += WriteFieldTerminator(fd);
        nFields++;

        /* Field VFF */
        char tmp[12 + 1] = {};
        snprintf(tmp, sizeof(tmp), "%s.GEN", osBaseFileName.c_str());
        sizeOfFields[nFields] += WriteSubFieldStr(fd, tmp, 51); /* VFF */
        sizeOfFields[nFields] += WriteFieldTerminator(fd);
        nFields++;

        /* Field VFF */
        snprintf(tmp, sizeof(tmp), "%s.IMG", osBaseFileName.c_str());
        sizeOfFields[nFields] += WriteSubFieldStr(fd, tmp, 51); /* VFF */
        sizeOfFields[nFields] += WriteFieldTerminator(fd);
        nFields++;

        if (nTotalFields == 6)
        {
            /* Field VFF */
            strncpy(tmp, osBaseFileName.c_str(), 6);
            tmp[6] = '\0';
            strcat(tmp, "02.IMG");
            sizeOfFields[nFields] += WriteSubFieldStr(fd, tmp, 51); /* VFF */
            sizeOfFields[nFields] += WriteFieldTerminator(fd);
            /* nFields++; */
        }

        FinishWriteLeader(fd, pos, 9, 9, 3, nTotalFields, sizeOfFields,
                          nameOfFields);
    }
}

/************************************************************************/
/*                         GDALRegister_ADRG()                          */
/************************************************************************/

void GDALRegister_ADRG()

{
    if (GDALGetDriverByName("ADRG") != nullptr)
        return;

    GDALDriver *poDriver = new GDALDriver();

    poDriver->SetDescription("ADRG");
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
                              "ARC Digitized Raster Graphics");
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/adrg.html");
    poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "gen");
    poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES, "Byte");
    poDriver->SetMetadataItem(GDAL_DMD_SUBDATASETS, "YES");
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");

    poDriver->pfnOpen = ADRGDataset::Open;
    poDriver->pfnCreate = ADRGDataset::Create;

    GetGDALDriverManager()->RegisterDriver(poDriver);
}
