/******************************************************************************
 *
 * Project:  SRTM HGT Driver
 * Purpose:  SRTM HGT File Read Support.
 *           http://dds.cr.usgs.gov/srtm/version2_1/Documentation/SRTM_Topo.pdf
 *           http://www2.jpl.nasa.gov/srtm/faq.html
 *           http://dds.cr.usgs.gov/srtm/version2_1
 * Authors:  Michael Mazzella, Even Rouault
 *
 ******************************************************************************
 * Copyright (c) 2005, Frank Warmerdam <warmerdam@pobox.com>
 * Copyright (c) 2007-2011, 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_port.h"
#include "cpl_string.h"
#include "gdal_frmts.h"
#include "gdal_pam.h"
#include "ogr_spatialref.h"

#include <cmath>

constexpr GInt16 SRTMHG_NODATA_VALUE = -32768;

/************************************************************************/
/* ==================================================================== */
/*                              SRTMHGTDataset                          */
/* ==================================================================== */
/************************************************************************/

class SRTMHGTRasterBand;

class SRTMHGTDataset final : public GDALPamDataset
{
    friend class SRTMHGTRasterBand;

    VSILFILE *fpImage = nullptr;
    double adfGeoTransform[6];
    GByte *pabyBuffer = nullptr;
    OGRSpatialReference m_oSRS{};

  public:
    SRTMHGTDataset();
    virtual ~SRTMHGTDataset();

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

    static int Identify(GDALOpenInfo *poOpenInfo);
    static GDALDataset *Open(GDALOpenInfo *);
    static GDALDataset *CreateCopy(const char *pszFilename,
                                   GDALDataset *poSrcDS, int bStrict,
                                   char **papszOptions,
                                   GDALProgressFunc pfnProgress,
                                   void *pProgressData);
};

/************************************************************************/
/* ==================================================================== */
/*                            SRTMHGTRasterBand                             */
/* ==================================================================== */
/************************************************************************/

class SRTMHGTRasterBand final : public GDALPamRasterBand
{
    friend class SRTMHGTDataset;

    int bNoDataSet;
    double dfNoDataValue;

  public:
    SRTMHGTRasterBand(SRTMHGTDataset *, int, GDALDataType);

    virtual CPLErr IReadBlock(int, int, void *) override;
    virtual CPLErr IWriteBlock(int nBlockXOff, int nBlockYOff,
                               void *pImage) override;

    virtual GDALColorInterp GetColorInterpretation() override;

    virtual double GetNoDataValue(int *pbSuccess = nullptr) override;

    virtual const char *GetUnitType() override;
};

/************************************************************************/
/*                         SRTMHGTRasterBand()                          */
/************************************************************************/

SRTMHGTRasterBand::SRTMHGTRasterBand(SRTMHGTDataset *poDSIn, int nBandIn,
                                     GDALDataType eDT)
    : bNoDataSet(TRUE), dfNoDataValue(SRTMHG_NODATA_VALUE)
{
    poDS = poDSIn;
    nBand = nBandIn;
    eDataType = eDT;
    nBlockXSize = poDSIn->nRasterXSize;
    nBlockYSize = 1;
}

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

CPLErr SRTMHGTRasterBand::IReadBlock(int /*nBlockXOff*/, int nBlockYOff,
                                     void *pImage)
{
    SRTMHGTDataset *poGDS = reinterpret_cast<SRTMHGTDataset *>(poDS);

    /* -------------------------------------------------------------------- */
    /*      Load the desired data into the working buffer.                  */
    /* -------------------------------------------------------------------- */
    const int nDTSize = GDALGetDataTypeSizeBytes(eDataType);
    VSIFSeekL(poGDS->fpImage,
              static_cast<size_t>(nBlockYOff) * nBlockXSize * nDTSize,
              SEEK_SET);
    VSIFReadL((unsigned char *)pImage, nBlockXSize, nDTSize, poGDS->fpImage);
#ifdef CPL_LSB
    GDALSwapWords(pImage, nDTSize, nBlockXSize, nDTSize);
#endif

    return CE_None;
}

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

CPLErr SRTMHGTRasterBand::IWriteBlock(int /*nBlockXOff*/, int nBlockYOff,
                                      void *pImage)
{
    SRTMHGTDataset *poGDS = reinterpret_cast<SRTMHGTDataset *>(poDS);

    if (poGDS->eAccess != GA_Update)
        return CE_Failure;

    const int nDTSize = GDALGetDataTypeSizeBytes(eDataType);
    VSIFSeekL(poGDS->fpImage,
              static_cast<size_t>(nBlockYOff) * nBlockXSize * nDTSize,
              SEEK_SET);

#ifdef CPL_LSB
    if (nDTSize > 1)
    {
        memcpy(poGDS->pabyBuffer, pImage,
               static_cast<size_t>(nBlockXSize) * nDTSize);
        GDALSwapWords(poGDS->pabyBuffer, nDTSize, nBlockXSize, nDTSize);
        VSIFWriteL(reinterpret_cast<unsigned char *>(poGDS->pabyBuffer),
                   nBlockXSize, nDTSize, poGDS->fpImage);
    }
    else
#endif
    {
        VSIFWriteL(reinterpret_cast<unsigned char *>(pImage), nBlockXSize,
                   nDTSize, poGDS->fpImage);
    }

    return CE_None;
}

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

double SRTMHGTRasterBand::GetNoDataValue(int *pbSuccess)

{
    if (eDataType == GDT_Byte)
        return GDALPamRasterBand::GetNoDataValue(pbSuccess);

    if (pbSuccess)
        *pbSuccess = bNoDataSet;

    return dfNoDataValue;
}

/************************************************************************/
/*                             GetUnitType()                            */
/************************************************************************/

const char *SRTMHGTRasterBand::GetUnitType()
{
    const char *pszExt = CPLGetExtension(poDS->GetDescription());
    if (EQUAL(pszExt, "err") || EQUAL(pszExt, "img") || EQUAL(pszExt, "num") ||
        EQUAL(pszExt, "swb"))
    {
        return "";
    }
    return "m";
}

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

GDALColorInterp SRTMHGTRasterBand::GetColorInterpretation()
{
    return GCI_Undefined;
}

/************************************************************************/
/* ==================================================================== */
/*                             SRTMHGTDataset                               */
/* ==================================================================== */
/************************************************************************/

/************************************************************************/
/*                            SRTMHGTDataset()                              */
/************************************************************************/

SRTMHGTDataset::SRTMHGTDataset()
{
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    if (CPLTestBool(CPLGetConfigOption("REPORT_COMPD_CS", "NO")))
    {
        m_oSRS.importFromWkt(
            "COMPD_CS[\"WGS 84 + EGM96 geoid height\", GEOGCS[\"WGS 84\", "
            "DATUM[\"WGS_1984\", SPHEROID[\"WGS 84\",6378137,298.257223563, "
            "AUTHORITY[\"EPSG\",\"7030\"]], AUTHORITY[\"EPSG\",\"6326\"]], "
            "PRIMEM[\"Greenwich\",0, AUTHORITY[\"EPSG\",\"8901\"]], "
            "UNIT[\"degree\",0.0174532925199433, "
            "AUTHORITY[\"EPSG\",\"9122\"]], AUTHORITY[\"EPSG\",\"4326\"]], "
            "VERT_CS[\"EGM96 geoid height\", VERT_DATUM[\"EGM96 geoid\",2005, "
            "AUTHORITY[\"EPSG\",\"5171\"]], UNIT[\"metre\",1, "
            "AUTHORITY[\"EPSG\",\"9001\"]], AXIS[\"Up\",UP], "
            "AUTHORITY[\"EPSG\",\"5773\"]]]");
    }
    else
    {
        m_oSRS.importFromWkt(SRS_WKT_WGS84_LAT_LONG);
    }
    adfGeoTransform[0] = 0.0;
    adfGeoTransform[1] = 1.0;
    adfGeoTransform[2] = 0.0;
    adfGeoTransform[3] = 0.0;
    adfGeoTransform[4] = 0.0;
    adfGeoTransform[5] = 1.0;
}

/************************************************************************/
/*                           ~SRTMHGTDataset()                            */
/************************************************************************/

SRTMHGTDataset::~SRTMHGTDataset()
{
    FlushCache(true);
    if (fpImage != nullptr)
        VSIFCloseL(fpImage);
    CPLFree(pabyBuffer);
}

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

CPLErr SRTMHGTDataset::GetGeoTransform(double *padfTransform)
{
    memcpy(padfTransform, adfGeoTransform, sizeof(double) * 6);
    return CE_None;
}

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

const OGRSpatialReference *SRTMHGTDataset::GetSpatialRef() const

{
    return &m_oSRS;
}

/************************************************************************/
/*                              Identify()                              */
/************************************************************************/

int SRTMHGTDataset::Identify(GDALOpenInfo *poOpenInfo)

{
    const char *fileName = CPLGetFilename(poOpenInfo->pszFilename);
    if (strlen(fileName) < 11 || fileName[7] != '.')
        return FALSE;
    CPLString osLCFilename(CPLString(fileName).tolower());
    if ((osLCFilename[0] != 'n' && osLCFilename[0] != 's') ||
        (osLCFilename[3] != 'e' && osLCFilename[3] != 'w'))
        return FALSE;
    if (!STARTS_WITH(fileName, "/vsizip/") && osLCFilename.endsWith(".hgt.zip"))
    {
        CPLString osNewName("/vsizip/");
        osNewName += poOpenInfo->pszFilename;
        osNewName += "/";
        osNewName += CPLString(fileName).substr(0, 7);
        osNewName += ".hgt";
        GDALOpenInfo oOpenInfo(osNewName, GA_ReadOnly);
        return Identify(&oOpenInfo);
    }

    if (!STARTS_WITH(fileName, "/vsizip/") &&
        osLCFilename.endsWith(".srtmswbd.raw.zip"))
    {
        CPLString osNewName("/vsizip/");
        osNewName += poOpenInfo->pszFilename;
        osNewName += "/";
        osNewName += CPLString(fileName).substr(0, 7);
        osNewName += ".raw";
        GDALOpenInfo oOpenInfo(osNewName, GA_ReadOnly);
        return Identify(&oOpenInfo);
    }

    // .hgts and .err files from
    // https://e4ftl01.cr.usgs.gov/MEASURES/NASADEM_SHHP.001/2000.02.11/ .img
    // and .img.num files from
    // https://e4ftl01.cr.usgs.gov/MEASURES/NASADEM_SIM.001/2000.02.11/
    if (!osLCFilename.endsWith(".hgt") && !osLCFilename.endsWith(".hgts") &&
        !osLCFilename.endsWith(".err") && !osLCFilename.endsWith(".img") &&
        !osLCFilename.endsWith(".num") &&  // .img.num or .num
        !osLCFilename.endsWith(".raw") &&
        !osLCFilename.endsWith(
            ".swb") &&  // https://e4ftl01.cr.usgs.gov/MEASURES/NASADEM_HGT.001/2000.02.11/
        !osLCFilename.endsWith(".hgt.gz"))
        return FALSE;

    /* -------------------------------------------------------------------- */
    /*      We check the file size to see if it is                          */
    /*      SRTM1 (below or above lat 50) or SRTM 3                         */
    /* -------------------------------------------------------------------- */
    VSIStatBufL fileStat;

    if (VSIStatL(poOpenInfo->pszFilename, &fileStat) != 0)
        return FALSE;
    if (fileStat.st_size != 3601 * 3601 &&
        fileStat.st_size != 3601 * 3601 * 2 &&
        fileStat.st_size != 3601 * 3601 * 4 &&  // .hgts
        fileStat.st_size != 1801 * 3601 * 2 &&
        fileStat.st_size != 1201 * 1201 * 2)
        return FALSE;

    return TRUE;
}

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

GDALDataset *SRTMHGTDataset::Open(GDALOpenInfo *poOpenInfo)
{
    if (!Identify(poOpenInfo))
        return nullptr;

    const char *fileName = CPLGetFilename(poOpenInfo->pszFilename);
    CPLString osLCFilename(CPLString(fileName).tolower());
    if (!STARTS_WITH(fileName, "/vsizip/") && osLCFilename.endsWith(".hgt.zip"))
    {
        CPLString osFilename("/vsizip/");
        osFilename += poOpenInfo->pszFilename;
        osFilename += "/";
        osFilename += CPLString(fileName).substr(0, 7);
        osFilename += ".hgt";
        GDALOpenInfo oOpenInfo(osFilename, poOpenInfo->eAccess);
        GDALDataset *poDS = Open(&oOpenInfo);
        if (poDS != nullptr)
        {
            // override description with the main one
            poDS->SetDescription(poOpenInfo->pszFilename);
        }
        return poDS;
    }

    if (!STARTS_WITH(fileName, "/vsizip/") &&
        osLCFilename.endsWith(".srtmswbd.raw.zip"))
    {
        CPLString osFilename("/vsizip/");
        osFilename += poOpenInfo->pszFilename;
        osFilename += "/";
        osFilename += CPLString(fileName).substr(0, 7);
        osFilename += ".raw";
        GDALOpenInfo oOpenInfo(osFilename, poOpenInfo->eAccess);
        GDALDataset *poDS = Open(&oOpenInfo);
        if (poDS != nullptr)
        {
            // override description with the main one
            poDS->SetDescription(poOpenInfo->pszFilename);
        }
        return poDS;
    }

    char latLonValueString[4];
    memset(latLonValueString, 0, 4);
    strncpy(latLonValueString, &fileName[1], 2);
    int southWestLat = atoi(latLonValueString);
    memset(latLonValueString, 0, 4);
    // cppcheck-suppress redundantCopy
    strncpy(latLonValueString, &fileName[4], 3);
    int southWestLon = atoi(latLonValueString);

    if (fileName[0] == 'N' || fileName[0] == 'n')
        /*southWestLat = southWestLat */;
    else if (fileName[0] == 'S' || fileName[0] == 's')
        southWestLat = southWestLat * -1;
    else
        return nullptr;

    if (fileName[3] == 'E' || fileName[3] == 'e')
        /*southWestLon = southWestLon */;
    else if (fileName[3] == 'W' || fileName[3] == 'w')
        southWestLon = southWestLon * -1;
    else
        return nullptr;

    /* -------------------------------------------------------------------- */
    /*      Create a corresponding GDALDataset.                             */
    /* -------------------------------------------------------------------- */
    SRTMHGTDataset *poDS = new SRTMHGTDataset();

    poDS->fpImage = poOpenInfo->fpL;
    poOpenInfo->fpL = nullptr;

    VSIStatBufL fileStat;
    if (VSIStatL(poOpenInfo->pszFilename, &fileStat) != 0)
    {
        delete poDS;
        return nullptr;
    }

    int numPixels_x, numPixels_y;

    GDALDataType eDT = GDT_Int16;
    switch (fileStat.st_size)
    {
        case 3601 * 3601:
            numPixels_x = numPixels_y = 3601;
            eDT = GDT_Byte;
            break;
        case 3601 * 3601 * 2:
            numPixels_x = numPixels_y = 3601;
            break;
        case 3601 * 3601 * 4:  // .hgts
            numPixels_x = numPixels_y = 3601;
            eDT = GDT_Float32;
            break;
        case 1801 * 3601 * 2:
            numPixels_x = 1801;
            numPixels_y = 3601;
            break;
        case 1201 * 1201 * 2:
            numPixels_x = numPixels_y = 1201;
            break;
        default:
            numPixels_x = numPixels_y = 0;
            break;
    }

    poDS->eAccess = poOpenInfo->eAccess;
#ifdef CPL_LSB
    if (poDS->eAccess == GA_Update && eDT != GDT_Byte)
    {
        poDS->pabyBuffer =
            static_cast<GByte *>(CPLMalloc(numPixels_x * sizeof(eDT)));
    }
#endif

    /* -------------------------------------------------------------------- */
    /*      Capture some information from the file that is of interest.     */
    /* -------------------------------------------------------------------- */
    poDS->nRasterXSize = numPixels_x;
    poDS->nRasterYSize = numPixels_y;
    poDS->nBands = 1;

    poDS->adfGeoTransform[0] = southWestLon - 0.5 / (numPixels_x - 1);
    poDS->adfGeoTransform[1] = 1.0 / (numPixels_x - 1);
    poDS->adfGeoTransform[2] = 0.0;
    poDS->adfGeoTransform[3] = southWestLat + 1 + 0.5 / (numPixels_y - 1);
    poDS->adfGeoTransform[4] = 0.0;
    poDS->adfGeoTransform[5] = -1.0 / (numPixels_y - 1);

    poDS->SetMetadataItem(GDALMD_AREA_OR_POINT, GDALMD_AOP_POINT);

    /* -------------------------------------------------------------------- */
    /*      Create band information object.                                 */
    /* -------------------------------------------------------------------- */
    SRTMHGTRasterBand *tmpBand = new SRTMHGTRasterBand(poDS, 1, eDT);
    poDS->SetBand(1, tmpBand);

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

    /* -------------------------------------------------------------------- */
    /*      Support overviews.                                              */
    /* -------------------------------------------------------------------- */
    poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename);

    return poDS;
}

/************************************************************************/
/*                              CreateCopy()                            */
/************************************************************************/

GDALDataset *SRTMHGTDataset::CreateCopy(const char *pszFilename,
                                        GDALDataset *poSrcDS, int bStrict,
                                        char ** /* papszOptions*/,
                                        GDALProgressFunc pfnProgress,
                                        void *pProgressData)
{
    /* -------------------------------------------------------------------- */
    /*      Some some rudimentary checks                                    */
    /* -------------------------------------------------------------------- */
    const int nBands = poSrcDS->GetRasterCount();
    if (nBands == 0)
    {
        CPLError(
            CE_Failure, CPLE_NotSupported,
            "SRTMHGT driver does not support source dataset with zero band.\n");
        return nullptr;
    }
    else if (nBands != 1)
    {
        CPLError((bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported,
                 "SRTMHGT driver only uses the first band of the dataset.\n");
        if (bStrict)
            return nullptr;
    }

    /* -------------------------------------------------------------------- */
    /*      Checks the input SRS                                            */
    /* -------------------------------------------------------------------- */
    OGRSpatialReference ogrsr_input;
    ogrsr_input.importFromWkt(poSrcDS->GetProjectionRef());

    OGRSpatialReference ogrsr_wgs84;
    ogrsr_wgs84.SetWellKnownGeogCS("WGS84");

    if (ogrsr_input.IsSameGeogCS(&ogrsr_wgs84) == FALSE)
    {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "The source projection coordinate system is %s. Only WGS 84 "
                 "is supported.\nThe SRTMHGT driver will generate a file as "
                 "if the source was WGS 84 projection coordinate system.",
                 poSrcDS->GetProjectionRef());
    }

    /* -------------------------------------------------------------------- */
    /*      Work out the LL origin.                                         */
    /* -------------------------------------------------------------------- */
    double adfGeoTransform[6];
    if (poSrcDS->GetGeoTransform(adfGeoTransform) != CE_None)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Source image must have a geo transform matrix.");
        return nullptr;
    }

    const int nLLOriginLat = static_cast<int>(
        std::floor(adfGeoTransform[3] +
                   poSrcDS->GetRasterYSize() * adfGeoTransform[5] + 0.5));

    int nLLOriginLong = static_cast<int>(std::floor(adfGeoTransform[0] + 0.5));

    if (std::abs(nLLOriginLat -
                 (adfGeoTransform[3] + (poSrcDS->GetRasterYSize() - 0.5) *
                                           adfGeoTransform[5])) > 1e-10 ||
        std::abs(nLLOriginLong -
                 (adfGeoTransform[0] + 0.5 * adfGeoTransform[1])) > 1e-10)
    {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "The corner coordinates of the source are not properly "
                 "aligned on plain latitude/longitude boundaries.");
    }

    /* -------------------------------------------------------------------- */
    /*      Check image dimensions.                                         */
    /* -------------------------------------------------------------------- */
    const int nXSize = poSrcDS->GetRasterXSize();
    const int nYSize = poSrcDS->GetRasterYSize();

    if (!((nXSize == 1201 && nYSize == 1201) ||
          (nXSize == 3601 && nYSize == 3601) ||
          (nXSize == 1801 && nYSize == 3601)))
    {
        CPLError(
            CE_Failure, CPLE_AppDefined,
            "Image dimensions should be 1201x1201, 3601x3601 or 1801x3601.");
        return nullptr;
    }

    /* -------------------------------------------------------------------- */
    /*      Check filename.                                                 */
    /* -------------------------------------------------------------------- */
    char expectedFileName[12];

    CPLsnprintf(expectedFileName, sizeof(expectedFileName), "%c%02d%c%03d.HGT",
                (nLLOriginLat >= 0) ? 'N' : 'S',
                (nLLOriginLat >= 0) ? nLLOriginLat : -nLLOriginLat,
                (nLLOriginLong >= 0) ? 'E' : 'W',
                (nLLOriginLong >= 0) ? nLLOriginLong : -nLLOriginLong);

    if (!EQUAL(expectedFileName, CPLGetFilename(pszFilename)))
    {
        CPLError(CE_Warning, CPLE_AppDefined, "Expected output filename is %s.",
                 expectedFileName);
    }

    /* -------------------------------------------------------------------- */
    /*      Write output file.                                              */
    /* -------------------------------------------------------------------- */
    VSILFILE *fp = VSIFOpenL(pszFilename, "wb");
    if (fp == nullptr)
    {
        CPLError(CE_Failure, CPLE_FileIO, "Cannot create file %s", pszFilename);
        return nullptr;
    }

    GInt16 *panData =
        reinterpret_cast<GInt16 *>(CPLMalloc(sizeof(GInt16) * nXSize));
    GDALRasterBand *poSrcBand = poSrcDS->GetRasterBand(1);

    int bSrcBandHasNoData;
    double srcBandNoData = poSrcBand->GetNoDataValue(&bSrcBandHasNoData);

    for (int iY = 0; iY < nYSize; iY++)
    {
        if (poSrcBand->RasterIO(GF_Read, 0, iY, nXSize, 1,
                                reinterpret_cast<void *>(panData), nXSize, 1,
                                GDT_Int16, 0, 0, nullptr) != CE_None)
        {
            VSIFCloseL(fp);
            CPLFree(panData);
            return nullptr;
        }

        /* Translate nodata values */
        if (bSrcBandHasNoData && srcBandNoData != SRTMHG_NODATA_VALUE)
        {
            for (int iX = 0; iX < nXSize; iX++)
            {
                if (panData[iX] == srcBandNoData)
                    panData[iX] = SRTMHG_NODATA_VALUE;
            }
        }

#ifdef CPL_LSB
        GDALSwapWords(panData, 2, nXSize, 2);
#endif

        if (VSIFWriteL(panData, sizeof(GInt16) * nXSize, 1, fp) != 1)
        {
            CPLError(CE_Failure, CPLE_FileIO,
                     "Failed to write line %d in SRTMHGT dataset.\n", iY);
            VSIFCloseL(fp);
            CPLFree(panData);
            return nullptr;
        }

        if (pfnProgress && !pfnProgress((iY + 1) / static_cast<double>(nYSize),
                                        nullptr, pProgressData))
        {
            CPLError(CE_Failure, CPLE_UserInterrupt,
                     "User terminated CreateCopy()");
            VSIFCloseL(fp);
            CPLFree(panData);
            return nullptr;
        }
    }

    CPLFree(panData);
    VSIFCloseL(fp);

    /* -------------------------------------------------------------------- */
    /*      Reopen and copy missing information into a PAM file.            */
    /* -------------------------------------------------------------------- */
    GDALPamDataset *poDS =
        reinterpret_cast<GDALPamDataset *>(GDALOpen(pszFilename, GA_ReadOnly));

    if (poDS)
        poDS->CloneInfo(poSrcDS, GCIF_PAM_DEFAULT);

    return poDS;
}

/************************************************************************/
/*                         GDALRegister_SRTMHGT()                       */
/************************************************************************/
void GDALRegister_SRTMHGT()
{
    if (GDALGetDriverByName("SRTMHGT") != nullptr)
        return;

    GDALDriver *poDriver = new GDALDriver();

    poDriver->SetDescription("SRTMHGT");
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "SRTMHGT File Format");
    poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "hgt");
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
                              "drivers/raster/srtmhgt.html");
    poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES, "Byte Int16 UInt16");
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");

    poDriver->pfnIdentify = SRTMHGTDataset::Identify;
    poDriver->pfnOpen = SRTMHGTDataset::Open;
    poDriver->pfnCreateCopy = SRTMHGTDataset::CreateCopy;

    GetGDALDriverManager()->RegisterDriver(poDriver);
}
