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

#include "ceos.h"
#include "cpl_string.h"
#include "gdal_frmts.h"
#include "gdal_priv.h"
#include "rawdataset.h"
#include "ogr_srs_api.h"

CPL_CVSID("$Id: sar_ceosdataset.cpp b2723bb9ee29fb36de5c3afec9e9a6b757ef743c 2018-05-10 21:21:26 +0200 Even Rouault $")

static GInt16 CastToGInt16(float val)
{
    if ( val < -32768.0 )
        val = -32768.0;

    if ( val > 32767 )
        val = 32767.0;

    return static_cast<GInt16>(val);
}

static const char * const CeosExtension[][6] = {
{ "vol", "led", "img", "trl", "nul", "ext" },
{ "vol", "lea", "img", "trl", "nul", "ext" },
{ "vol", "led", "img", "tra", "nul", "ext" },
{ "vol", "lea", "img", "tra", "nul", "ext" },
{ "vdf", "slf", "sdf", "stf", "nvd", "ext" },

{ "vdf", "ldr", "img", "tra", "nul", "ext2" },

/* Jers from Japan- not sure if this is generalized as much as it could be */
{ "VOLD", "Sarl_01", "Imop_%02d", "Sart_01", "NULL", "base" },

/* Radarsat: basename, not extension */
{ "vdf_dat", "lea_%02d", "dat_%02d", "tra_%02d", "nul_vdf", "base" },

/* Ers-1: basename, not extension */
{ "vdf_dat", "lea_%02d", "dat_%02d", "tra_%02d", "nul_dat", "base" },

/* Ers-2 from Telaviv */
{ "volume", "leader", "image", "trailer", "nul_dat", "whole" },

/* Ers-1 from D-PAF */
{ "VDF", "LF", "SLC", "", "", "ext" },

/* Radarsat-1 per #2051 */
{ "vol", "sarl", "sard", "sart", "nvol", "ext" },

/* end marker */
{ nullptr, nullptr, nullptr, nullptr, nullptr, nullptr }
};

static int
ProcessData( VSILFILE *fp, int fileid, CeosSARVolume_t *sar, int max_records,
             vsi_l_offset max_bytes );

static CeosTypeCode_t QuadToTC( int a, int b, int c, int d )
{
    CeosTypeCode_t   abcd;

    abcd.UCharCode.Subtype1 = (unsigned char) a;
    abcd.UCharCode.Type = (unsigned char) b;
    abcd.UCharCode.Subtype2 = (unsigned char) c;
    abcd.UCharCode.Subtype3 = (unsigned char) d;

    return abcd;
}

#define LEADER_DATASET_SUMMARY_TC          QuadToTC( 18, 10, 18, 20 )
#define LEADER_DATASET_SUMMARY_ERS2_TC     QuadToTC( 10, 10, 31, 20 )
#define LEADER_RADIOMETRIC_COMPENSATION_TC QuadToTC( 18, 51, 18, 20 )
#define VOLUME_DESCRIPTOR_RECORD_TC        QuadToTC( 192, 192, 18, 18 )
#define IMAGE_HEADER_RECORD_TC             QuadToTC( 63, 192, 18, 18 )
#define LEADER_RADIOMETRIC_DATA_RECORD_TC  QuadToTC( 18, 50, 18, 20 )
#define LEADER_MAP_PROJ_RECORD_TC          QuadToTC( 10, 20, 31, 20 )

// TODO: recond?
/* JERS from Japan has MAP_PROJ recond with different identifiers */
/* see CEOS-SAR-CCT Iss/Rev: 2/0 February 10, 1989 */
#define LEADER_MAP_PROJ_RECORD_JERS_TC         QuadToTC( 18, 20, 18, 20 )

/* For ERS calibration and incidence angle information */
#define ERS_GENERAL_FACILITY_DATA_TC  QuadToTC( 10, 200, 31, 50 )
#define ERS_GENERAL_FACILITY_DATA_ALT_TC QuadToTC( 10, 216, 31, 50 )

#define RSAT_PROC_PARAM_TC QuadToTC( 18, 120, 18, 20 )

/************************************************************************/
/* ==================================================================== */
/*                              SAR_CEOSDataset                         */
/* ==================================================================== */
/************************************************************************/

class SAR_CEOSRasterBand;
class CCPRasterBand;
class PALSARRasterBand;

class SAR_CEOSDataset : public GDALPamDataset
{
    friend class SAR_CEOSRasterBand;
    friend class CCPRasterBand;
    friend class PALSARRasterBand;

    CeosSARVolume_t sVolume;

    VSILFILE    *fpImage;

    char        **papszTempMD;

    int         nGCPCount;
    GDAL_GCP    *pasGCPList;

    void        ScanForGCPs();
    void        ScanForMetadata();
    int         ScanForMapProjection();

  public:
    SAR_CEOSDataset();
    ~SAR_CEOSDataset() override;

    int GetGCPCount() override;
    const char *GetGCPProjection() override;
    const GDAL_GCP *GetGCPs() override;

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

    static GDALDataset *Open( GDALOpenInfo * );
};

/************************************************************************/
/* ==================================================================== */
/*                          CCPRasterBand                               */
/* ==================================================================== */
/************************************************************************/

class CCPRasterBand : public GDALPamRasterBand
{
    friend class SAR_CEOSDataset;

  public:
    CCPRasterBand( SAR_CEOSDataset *, int, GDALDataType );

    CPLErr IReadBlock( int, int, void * ) override;
};

/************************************************************************/
/* ==================================================================== */
/*                        PALSARRasterBand                              */
/* ==================================================================== */
/************************************************************************/

class PALSARRasterBand : public GDALPamRasterBand
{
    friend class SAR_CEOSDataset;

  public:
    PALSARRasterBand( SAR_CEOSDataset *, int );

    CPLErr IReadBlock( int, int, void * ) override;
};

/************************************************************************/
/* ==================================================================== */
/*                       SAR_CEOSRasterBand                             */
/* ==================================================================== */
/************************************************************************/

class SAR_CEOSRasterBand : public GDALPamRasterBand
{
    friend class SAR_CEOSDataset;

  public:
    SAR_CEOSRasterBand( SAR_CEOSDataset *, int, GDALDataType );

    CPLErr IReadBlock( int, int, void * ) override;
};

/************************************************************************/
/*                         SAR_CEOSRasterBand()                         */
/************************************************************************/

SAR_CEOSRasterBand::SAR_CEOSRasterBand( SAR_CEOSDataset *poGDSIn, int nBandIn,
                                        GDALDataType eType )

{
    poDS = poGDSIn;
    nBand = nBandIn;

    eDataType = eType;

    nBlockXSize = poGDSIn->nRasterXSize;
    nBlockYSize = 1;
}

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

CPLErr SAR_CEOSRasterBand::IReadBlock( int /* nBlockXOff */,
                                       int nBlockYOff,
                                       void * pImage )
{
    SAR_CEOSDataset *poGDS = (SAR_CEOSDataset *) poDS;

    struct CeosSARImageDesc *ImageDesc = &(poGDS->sVolume.ImageDesc);

    int offset;
    CalcCeosSARImageFilePosition( &(poGDS->sVolume), nBand,
                                  nBlockYOff + 1, nullptr, &offset );

    offset += ImageDesc->ImageDataStart;

/* -------------------------------------------------------------------- */
/*      Load all the pixel data associated with this scanline.          */
/*      Ensure we handle multiple record scanlines properly.            */
/* -------------------------------------------------------------------- */
    int nPixelsRead = 0;

    GByte *pabyRecord
        = (GByte *) CPLMalloc( ImageDesc->BytesPerPixel * nBlockXSize );

    for( int iRecord = 0; iRecord < ImageDesc->RecordsPerLine; iRecord++ )
    {
        int nPixelsToRead;

        if( nPixelsRead + ImageDesc->PixelsPerRecord > nBlockXSize )
            nPixelsToRead = nBlockXSize - nPixelsRead;
        else
            nPixelsToRead = ImageDesc->PixelsPerRecord;

        CPL_IGNORE_RET_VAL(VSIFSeekL( poGDS->fpImage, offset, SEEK_SET ));
        CPL_IGNORE_RET_VAL(VSIFReadL( pabyRecord + nPixelsRead * ImageDesc->BytesPerPixel,
                   1, nPixelsToRead * ImageDesc->BytesPerPixel,
                   poGDS->fpImage ));

        nPixelsRead += nPixelsToRead;
        offset += ImageDesc->BytesPerRecord;
    }

/* -------------------------------------------------------------------- */
/*      Copy the desired band out based on the size of the type, and    */
/*      the interleaving mode.                                          */
/* -------------------------------------------------------------------- */
    const int nBytesPerSample = GDALGetDataTypeSize( eDataType ) / 8;

    if( ImageDesc->ChannelInterleaving == CEOS_IL_PIXEL )
    {
        GDALCopyWords( pabyRecord + (nBand-1) * nBytesPerSample,
                       eDataType, ImageDesc->BytesPerPixel,
                       pImage, eDataType, nBytesPerSample,
                       nBlockXSize );
    }
    else if( ImageDesc->ChannelInterleaving == CEOS_IL_LINE )
    {
        GDALCopyWords( pabyRecord + (nBand-1) * nBytesPerSample * nBlockXSize,
                       eDataType, nBytesPerSample,
                       pImage, eDataType, nBytesPerSample,
                       nBlockXSize );
    }
    else if( ImageDesc->ChannelInterleaving == CEOS_IL_BAND )
    {
        memcpy( pImage, pabyRecord, nBytesPerSample * nBlockXSize );
    }

#ifdef CPL_LSB
    GDALSwapWords( pImage, nBytesPerSample, nBlockXSize, nBytesPerSample );
#endif

    CPLFree( pabyRecord );

    return CE_None;
}

/************************************************************************/
/* ==================================================================== */
/*                              CCPRasterBand                           */
/* ==================================================================== */
/************************************************************************/

/************************************************************************/
/*                           CCPRasterBand()                            */
/************************************************************************/

CCPRasterBand::CCPRasterBand( SAR_CEOSDataset *poGDSIn, int nBandIn,
                              GDALDataType eType )

{
    poDS = poGDSIn;
    nBand = nBandIn;

    eDataType = eType;

    nBlockXSize = poGDSIn->nRasterXSize;
    nBlockYSize = 1;

    if( nBand == 1 )
        SetMetadataItem( "POLARIMETRIC_INTERP", "HH" );
    else if( nBand == 2 )
        SetMetadataItem( "POLARIMETRIC_INTERP", "HV" );
    else if( nBand == 3 )
        SetMetadataItem( "POLARIMETRIC_INTERP", "VH" );
    else if( nBand == 4 )
        SetMetadataItem( "POLARIMETRIC_INTERP", "VV" );
}

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

/* From: http://southport.jpl.nasa.gov/software/dcomp/dcomp.html

ysca = sqrt{ [ (Byte(2) / 254 ) + 1.5] 2Byte(1) }

Re(SHH) = byte(3) ysca/127

Im(SHH) = byte(4) ysca/127

Re(SHV) = byte(5) ysca/127

Im(SHV) = byte(6) ysca/127

Re(SVH) = byte(7) ysca/127

Im(SVH) = byte(8) ysca/127

Re(SVV) = byte(9) ysca/127

Im(SVV) = byte(10) ysca/127

*/

CPLErr CCPRasterBand::IReadBlock( CPL_UNUSED int nBlockXOff,
                                  int nBlockYOff,
                                  void * pImage )
{
    SAR_CEOSDataset *poGDS = (SAR_CEOSDataset *) poDS;

    struct CeosSARImageDesc *ImageDesc = &(poGDS->sVolume.ImageDesc);

    int offset = ImageDesc->FileDescriptorLength
        + ImageDesc->BytesPerRecord * nBlockYOff
        + ImageDesc->ImageDataStart;

/* -------------------------------------------------------------------- */
/*      Load all the pixel data associated with this scanline.          */
/* -------------------------------------------------------------------- */
    const int nBytesToRead = ImageDesc->BytesPerPixel * nBlockXSize;

    GByte *pabyRecord = (GByte *) CPLMalloc( nBytesToRead );

    if( VSIFSeekL( poGDS->fpImage, offset, SEEK_SET ) != 0
        || (int) VSIFReadL( pabyRecord, 1, nBytesToRead,
                           poGDS->fpImage ) != nBytesToRead )
    {
        CPLError( CE_Failure, CPLE_FileIO,
                  "Error reading %d bytes of CEOS record data at offset %d.\n"
                  "Reading file %s failed.",
                  nBytesToRead, offset, poGDS->GetDescription() );
        CPLFree( pabyRecord );
        return CE_Failure;
    }

/* -------------------------------------------------------------------- */
/*      Initialize our power table if this is our first time through.   */
/* -------------------------------------------------------------------- */
    static float afPowTable[256];
    static bool bPowTableInitialized = false;

    if( !bPowTableInitialized )
    {
        bPowTableInitialized = true;

        for( int i = 0; i < 256; i++ )
        {
            afPowTable[i] = (float)pow( 2.0, i-128 );
        }
    }

/* -------------------------------------------------------------------- */
/*      Copy the desired band out based on the size of the type, and    */
/*      the interleaving mode.                                          */
/* -------------------------------------------------------------------- */
    for( int iX = 0; iX < nBlockXSize; iX++ )
    {
        unsigned char *pabyGroup = pabyRecord + iX * ImageDesc->BytesPerPixel;
        signed char *Byte = (signed char*)pabyGroup-1; /* A ones based alias */
        double dfReSHH, dfImSHH, dfReSHV, dfImSHV,
            dfReSVH, dfImSVH, dfReSVV, dfImSVV;

        const double dfScale = sqrt( (Byte[2] / 254.0 + 1.5)
                                     * afPowTable[Byte[1] + 128] );

        if( nBand == 1 )
        {
            dfReSHH = Byte[3] * dfScale / 127.0;
            dfImSHH = Byte[4] * dfScale / 127.0;

            ((float *) pImage)[iX*2  ] = (float)dfReSHH;
            ((float *) pImage)[iX*2+1] = (float)dfImSHH;
        }
        else if( nBand == 2 )
        {
            dfReSHV = Byte[5] * dfScale / 127.0;
            dfImSHV = Byte[6] * dfScale / 127.0;

            ((float *) pImage)[iX*2  ] = (float)dfReSHV;
            ((float *) pImage)[iX*2+1] = (float)dfImSHV;
        }
        else if( nBand == 3 )
        {
            dfReSVH = Byte[7] * dfScale / 127.0;
            dfImSVH = Byte[8] * dfScale / 127.0;

            ((float *) pImage)[iX*2  ] = (float)dfReSVH;
            ((float *) pImage)[iX*2+1] = (float)dfImSVH;
        }
        else if( nBand == 4 )
        {
            dfReSVV = Byte[9] * dfScale / 127.0;
            dfImSVV = Byte[10]* dfScale / 127.0;

            ((float *) pImage)[iX*2  ] = (float)dfReSVV;
            ((float *) pImage)[iX*2+1] = (float)dfImSVV;
        }
    }

    CPLFree( pabyRecord );

    return CE_None;
}

/************************************************************************/
/* ==================================================================== */
/*                            PALSARRasterBand                          */
/* ==================================================================== */
/************************************************************************/

/************************************************************************/
/*                           PALSARRasterBand()                         */
/************************************************************************/

PALSARRasterBand::PALSARRasterBand( SAR_CEOSDataset *poGDSIn, int nBandIn )

{
    poDS = poGDSIn;
    nBand = nBandIn;

    eDataType = GDT_CInt16;

    nBlockXSize = poGDSIn->nRasterXSize;
    nBlockYSize = 1;

    if( nBand == 1 )
        SetMetadataItem( "POLARIMETRIC_INTERP", "Covariance_11" );
    else if( nBand == 2 )
        SetMetadataItem( "POLARIMETRIC_INTERP", "Covariance_22" );
    else if( nBand == 3 )
        SetMetadataItem( "POLARIMETRIC_INTERP", "Covariance_33" );
    else if( nBand == 4 )
        SetMetadataItem( "POLARIMETRIC_INTERP", "Covariance_12" );
    else if( nBand == 5 )
        SetMetadataItem( "POLARIMETRIC_INTERP", "Covariance_13" );
    else if( nBand == 6 )
        SetMetadataItem( "POLARIMETRIC_INTERP", "Covariance_23" );
}

/************************************************************************/
/*                             IReadBlock()                             */
/*                                                                      */
/*      Based on ERSDAC-VX-CEOS-004                                     */
/************************************************************************/

CPLErr PALSARRasterBand::IReadBlock( int /* nBlockXOff */,
                                     int nBlockYOff,
                                     void * pImage )
{
    SAR_CEOSDataset *poGDS = (SAR_CEOSDataset *) poDS;

    struct CeosSARImageDesc *ImageDesc = &(poGDS->sVolume.ImageDesc);

    int offset = ImageDesc->FileDescriptorLength
        + ImageDesc->BytesPerRecord * nBlockYOff
        + ImageDesc->ImageDataStart;

/* -------------------------------------------------------------------- */
/*      Load all the pixel data associated with this scanline.          */
/* -------------------------------------------------------------------- */
    const int nBytesToRead = ImageDesc->BytesPerPixel * nBlockXSize;

    GByte  *pabyRecord = (GByte *) CPLMalloc( nBytesToRead );

    if( VSIFSeekL( poGDS->fpImage, offset, SEEK_SET ) != 0
        || (int) VSIFReadL( pabyRecord, 1, nBytesToRead,
                           poGDS->fpImage ) != nBytesToRead )
    {
        CPLError( CE_Failure, CPLE_FileIO,
                  "Error reading %d bytes of CEOS record data at offset %d.\n"
                  "Reading file %s failed.",
                  nBytesToRead, offset, poGDS->GetDescription() );
        CPLFree( pabyRecord );
        return CE_Failure;
    }

/* -------------------------------------------------------------------- */
/*      Copy the desired band out based on the size of the type, and    */
/*      the interleaving mode.                                          */
/* -------------------------------------------------------------------- */
    if( nBand == 1 || nBand == 2 || nBand == 3 )
    {
        // we need to pre-initialize things to set the imaginary component to 0
        memset( pImage, 0, nBlockXSize * 4 );

        GDALCopyWords( pabyRecord + 4*(nBand - 1), GDT_Int16, 18,
                       pImage, GDT_Int16, 4,
                       nBlockXSize );
#ifdef CPL_LSB
        GDALSwapWords( pImage, 2, nBlockXSize, 4 );
#endif
    }
    else
    {
        GDALCopyWords( pabyRecord + 6 + 4*(nBand - 4), GDT_CInt16, 18,
                       pImage, GDT_CInt16, 4,
                       nBlockXSize );
#ifdef CPL_LSB
        GDALSwapWords( pImage, 2, nBlockXSize*2, 2 );
#endif
    }
    CPLFree( pabyRecord );

/* -------------------------------------------------------------------- */
/*      Convert the values into covariance form as per:                 */
/* -------------------------------------------------------------------- */
/*
** 1) PALSAR- adjust so that it reads bands as a covariance matrix, and
** set polarimetric interpretation accordingly:
**
** Covariance_11=HH*conj(HH): already there
** Covariance_22=2*HV*conj(HV): need a factor of 2
** Covariance_33=VV*conj(VV): already there
** Covariance_12=sqrt(2)*HH*conj(HV): need the sqrt(2) factor
** Covariance_13=HH*conj(VV): already there
** Covariance_23=sqrt(2)*HV*conj(VV): need to take the conjugate, then
**               multiply by sqrt(2)
**
*/

    if( nBand == 2 )
    {
        GInt16 *panLine = (GInt16 *) pImage;

        for( int i = 0; i < nBlockXSize * 2; i++ )
        {
          panLine[i] = (GInt16) CastToGInt16((float)2.0 * panLine[i]);
        }
    }
    else if( nBand == 4 )
    {
        const double sqrt_2 = pow(2.0,0.5);
        GInt16 *panLine = (GInt16 *) pImage;

        for( int i = 0; i < nBlockXSize * 2; i++ )
        {
          panLine[i] = (GInt16) CastToGInt16((float)floor(panLine[i] * sqrt_2 + 0.5));
        }
    }
    else if( nBand == 6 )
    {
        GInt16 *panLine = (GInt16 *) pImage;
        const double sqrt_2 = pow(2.0,0.5);

        // real portion - just multiple by sqrt(2)
        for( int i = 0; i < nBlockXSize * 2; i += 2 )
        {
          panLine[i] = (GInt16) CastToGInt16((float)floor(panLine[i] * sqrt_2 + 0.5));
        }

        // imaginary portion - conjugate and multiply
        for( int i = 1; i < nBlockXSize * 2; i += 2 )
        {
          panLine[i] = (GInt16) CastToGInt16((float)floor(-panLine[i] * sqrt_2 + 0.5));
        }
    }

    return CE_None;
}

/************************************************************************/
/* ==================================================================== */
/*                              SAR_CEOSDataset                         */
/* ==================================================================== */
/************************************************************************/

/************************************************************************/
/*                          SAR_CEOSDataset()                           */
/************************************************************************/

SAR_CEOSDataset::SAR_CEOSDataset() :
    fpImage(nullptr),
    papszTempMD(nullptr),
    nGCPCount(0),
    pasGCPList(nullptr)
{
    sVolume.Flavor = 0;
    sVolume.Sensor = 0;
    sVolume.ProductType = 0;
    sVolume.FileNamingConvention = 0;

    sVolume.VolumeDirectoryFile = 0;
    sVolume.SARLeaderFile = 0;
    sVolume.ImagryOptionsFile = 0;
    sVolume.SARTrailerFile = 0;
    sVolume.NullVolumeDirectoryFile = 0;

    sVolume.ImageDesc.ImageDescValid = 0;
    sVolume.ImageDesc.NumChannels = 0;
    sVolume.ImageDesc.ChannelInterleaving = 0;
    sVolume.ImageDesc.DataType = 0;
    sVolume.ImageDesc.BytesPerRecord = 0;
    sVolume.ImageDesc.Lines = 0;
    sVolume.ImageDesc.TopBorderPixels = 0;
    sVolume.ImageDesc.BottomBorderPixels = 0;
    sVolume.ImageDesc.PixelsPerLine = 0;
    sVolume.ImageDesc.LeftBorderPixels = 0;
    sVolume.ImageDesc.RightBorderPixels = 0;
    sVolume.ImageDesc.BytesPerPixel = 0;
    sVolume.ImageDesc.RecordsPerLine = 0;
    sVolume.ImageDesc.PixelsPerRecord = 0;
    sVolume.ImageDesc.ImageDataStart = 0;
    sVolume.ImageDesc.ImageSuffixData = 0;
    sVolume.ImageDesc.FileDescriptorLength = 0;
    sVolume.ImageDesc.PixelOrder = 0;
    sVolume.ImageDesc.LineOrder = 0;
    sVolume.ImageDesc.PixelDataBytesPerRecord = 0;

    sVolume.RecordList = nullptr;
}

/************************************************************************/
/*                          ~SAR_CEOSDataset()                          */
/************************************************************************/

SAR_CEOSDataset::~SAR_CEOSDataset()

{
    FlushCache();

    CSLDestroy( papszTempMD );

    if( fpImage != nullptr )
        CPL_IGNORE_RET_VAL(VSIFCloseL( fpImage ));

    if( nGCPCount > 0 )
    {
        GDALDeinitGCPs( nGCPCount, pasGCPList );
        CPLFree( pasGCPList );
    }

    if( sVolume.RecordList )
    {
        for(Link_t *Links = sVolume.RecordList;
            Links != nullptr;
            Links = Links->next)
        {
            if(Links->object)
            {
                DeleteCeosRecord( (CeosRecord_t *) Links->object );
                Links->object = nullptr;
            }
        }
        DestroyList( sVolume.RecordList );
    }
    FreeRecipes();
}

/************************************************************************/
/*                            GetGCPCount()                             */
/************************************************************************/

int SAR_CEOSDataset::GetGCPCount()

{
    return nGCPCount;
}

/************************************************************************/
/*                          GetGCPProjection()                          */
/************************************************************************/

const char *SAR_CEOSDataset::GetGCPProjection()

{
    if( nGCPCount > 0 )
        return SRS_WKT_WGS84;

    return "";
}

/************************************************************************/
/*                               GetGCP()                               */
/************************************************************************/

const GDAL_GCP *SAR_CEOSDataset::GetGCPs()

{
    return pasGCPList;
}

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

char **SAR_CEOSDataset::GetMetadataDomainList()
{
    return CSLAddString(GDALDataset::GetMetadataDomainList(), "ceos-FFF-n-n-n-n:r");
}

/************************************************************************/
/*                            GetMetadata()                             */
/*                                                                      */
/*      We provide our own GetMetadata() so that we can override        */
/*      behavior for some very specialized domain names intended to     */
/*      give us access to raw record data.                              */
/*                                                                      */
/*      The domain must look like:                                      */
/*        ceos-FFF-n-n-n-n:r                                            */
/*                                                                      */
/*        FFF - The file id - one of vol, lea, img, trl or nul.         */
/*        n-n-n-n - the record type code such as 18-10-18-20 for the    */
/*        dataset summary record in the leader file.                    */
/*        :r - The zero based record number to fetch (optional)         */
/*                                                                      */
/*      Note that only records that are pre-loaded will be              */
/*      accessible, and this normally means that most image records     */
/*      are not available.                                              */
/************************************************************************/

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

{
    if( pszDomain == nullptr || !STARTS_WITH_CI(pszDomain, "ceos-") )
        return GDALDataset::GetMetadata( pszDomain );

/* -------------------------------------------------------------------- */
/*      Identify which file to fetch the file from.                     */
/* -------------------------------------------------------------------- */
    int nFileId = -1;

    if( STARTS_WITH_CI(pszDomain, "ceos-vol") )
    {
        nFileId = CEOS_VOLUME_DIR_FILE;
    }
    else if( STARTS_WITH_CI(pszDomain, "ceos-lea") )
    {
        nFileId = CEOS_LEADER_FILE;
    }
    else if( STARTS_WITH_CI(pszDomain, "ceos-img") )
    {
        nFileId = CEOS_IMAGRY_OPT_FILE;
    }
    else if( STARTS_WITH_CI(pszDomain, "ceos-trl") )
    {
        nFileId = CEOS_TRAILER_FILE;
    }
    else if( STARTS_WITH_CI(pszDomain, "ceos-nul") )
    {
        nFileId = CEOS_NULL_VOL_FILE;
    }
    else
        return nullptr;

    pszDomain += 8;

/* -------------------------------------------------------------------- */
/*      Identify the record type.                                       */
/* -------------------------------------------------------------------- */
    int  a, b, c, d, nRecordIndex = -1;

    if( sscanf( pszDomain, "-%d-%d-%d-%d:%d",
                &a, &b, &c, &d, &nRecordIndex ) != 5
        && sscanf( pszDomain, "-%d-%d-%d-%d",
                   &a, &b, &c, &d ) != 4 )
    {
        return nullptr;
    }

    CeosTypeCode_t sTypeCode = QuadToTC( a, b, c, d );

/* -------------------------------------------------------------------- */
/*      Try to fetch the record.                                        */
/* -------------------------------------------------------------------- */
    CeosRecord_t *record =
        FindCeosRecord( sVolume.RecordList, sTypeCode, nFileId,
                        -1, nRecordIndex );

    if( record == nullptr )
        return nullptr;

/* -------------------------------------------------------------------- */
/*      Massage the data into a safe textual format.  The RawRecord     */
/*      just has zero bytes turned into spaces while the                */
/*      EscapedRecord has regular backslash escaping applied to zero    */
/*      chars, double quotes, and backslashes.                          */
/*      just turn zero bytes into spaces.                               */
/* -------------------------------------------------------------------- */

    CSLDestroy( papszTempMD );

    // Escaped version
    char *pszSafeCopy = CPLEscapeString( (char *) record->Buffer,
                                         record->Length,
                                         CPLES_BackslashQuotable );
    papszTempMD = CSLSetNameValue( nullptr, "EscapedRecord", pszSafeCopy );
    CPLFree( pszSafeCopy );

    // Copy with '\0' replaced by spaces.

    pszSafeCopy = (char *) CPLCalloc(1,record->Length+1);
    memcpy( pszSafeCopy, record->Buffer, record->Length );

    for( int i = 0; i < record->Length; i++ )
        if( pszSafeCopy[i] == '\0' )
            pszSafeCopy[i] = ' ';

    papszTempMD = CSLSetNameValue( papszTempMD, "RawRecord", pszSafeCopy );

    CPLFree( pszSafeCopy );

    return papszTempMD;
}

/************************************************************************/
/*                          ScanForMetadata()                           */
/************************************************************************/

void SAR_CEOSDataset::ScanForMetadata()

{
/* -------------------------------------------------------------------- */
/*      Get the volume id (with the sensor name)                        */
/* -------------------------------------------------------------------- */
    CeosRecord_t *record
        = FindCeosRecord( sVolume.RecordList, VOLUME_DESCRIPTOR_RECORD_TC,
                          CEOS_VOLUME_DIR_FILE, -1, -1 );

    char szVolId[128];
    szVolId[0] = '\0';
    char szField[128];
    szField[0] = '\0';
    if( record != nullptr )
    {
        szVolId[16] = '\0';

        GetCeosField( record, 61, "A16", szVolId );

        SetMetadataItem( "CEOS_LOGICAL_VOLUME_ID", szVolId );

/* -------------------------------------------------------------------- */
/*      Processing facility                                             */
/* -------------------------------------------------------------------- */
        szField[0] = '\0';
        szField[12] = '\0';

        GetCeosField( record, 149, "A12", szField );

        if( !STARTS_WITH_CI(szField, "            ") )
            SetMetadataItem( "CEOS_PROCESSING_FACILITY", szField );

/* -------------------------------------------------------------------- */
/*      Agency                                                          */
/* -------------------------------------------------------------------- */
        szField[8] = '\0';

        GetCeosField( record, 141, "A8", szField );

        if( !STARTS_WITH_CI(szField, "            ") )
            SetMetadataItem( "CEOS_PROCESSING_AGENCY", szField );

/* -------------------------------------------------------------------- */
/*      Country                                                         */
/* -------------------------------------------------------------------- */
        szField[12] = '\0';

        GetCeosField( record, 129, "A12", szField );

        if( !STARTS_WITH_CI(szField, "            ") )
            SetMetadataItem( "CEOS_PROCESSING_COUNTRY", szField );

/* -------------------------------------------------------------------- */
/*      software id.                                                    */
/* -------------------------------------------------------------------- */
        szField[12] = '\0';

        GetCeosField( record, 33, "A12", szField );

        if( !STARTS_WITH_CI(szField, "            ") )
            SetMetadataItem( "CEOS_SOFTWARE_ID", szField );

/* -------------------------------------------------------------------- */
/*      product identifier.                                                    */
/* -------------------------------------------------------------------- */
        szField[8] = '\0';

        GetCeosField( record, 261, "A8", szField );

        if( !STARTS_WITH_CI(szField, "        ") )
            SetMetadataItem( "CEOS_PRODUCT_ID", szField );

/* -------------------------------------------------------------------- */
/*      volume identifier.                                                    */
/* -------------------------------------------------------------------- */
        szField[16] = '\0';

        GetCeosField( record, 77, "A16", szField );

        if( !STARTS_WITH_CI(szField, "                ") )
            SetMetadataItem( "CEOS_VOLSET_ID", szField );
    }

/* ==================================================================== */
/*      Dataset summary record.                                         */
/* ==================================================================== */
    record = FindCeosRecord( sVolume.RecordList, LEADER_DATASET_SUMMARY_TC,
                             CEOS_LEADER_FILE, -1, -1 );

    if( record == nullptr )
        record = FindCeosRecord( sVolume.RecordList, LEADER_DATASET_SUMMARY_TC,
                                 CEOS_TRAILER_FILE, -1, -1 );

    if( record == nullptr )
        record = FindCeosRecord( sVolume.RecordList,
                                 LEADER_DATASET_SUMMARY_ERS2_TC,
                                 CEOS_LEADER_FILE, -1, -1 );

    if( record != nullptr )
    {
/* -------------------------------------------------------------------- */
/*      Get the acquisition date.                                       */
/* -------------------------------------------------------------------- */
        szField[0] = '\0';
        szField[32] = '\0';

        GetCeosField( record, 69, "A32", szField );

        SetMetadataItem( "CEOS_ACQUISITION_TIME", szField );

/* -------------------------------------------------------------------- */
/*      Ascending/Descending                                            */
/* -------------------------------------------------------------------- */
        GetCeosField( record, 101, "A16", szField );
        szField[16] = '\0';

        if( strstr(szVolId,"RSAT") != nullptr
            && !STARTS_WITH_CI(szField, "                ") )
            SetMetadataItem( "CEOS_ASC_DES", szField );

/* -------------------------------------------------------------------- */
/*      True heading - at least for ERS2.                               */
/* -------------------------------------------------------------------- */
        GetCeosField( record, 149, "A16", szField );
        szField[16] = '\0';

        if( !STARTS_WITH_CI(szField, "                ") )
            SetMetadataItem( "CEOS_TRUE_HEADING", szField );

/* -------------------------------------------------------------------- */
/*      Ellipsoid                                                       */
/* -------------------------------------------------------------------- */
        GetCeosField( record, 165, "A16", szField );
        szField[16] = '\0';

        if( !STARTS_WITH_CI(szField, "                ") )
            SetMetadataItem( "CEOS_ELLIPSOID", szField );

/* -------------------------------------------------------------------- */
/*      Semimajor, semiminor axis                                       */
/* -------------------------------------------------------------------- */
        GetCeosField( record, 181, "A16", szField );
        szField[16] = '\0';

        if( !STARTS_WITH_CI(szField, "                ") )
            SetMetadataItem( "CEOS_SEMI_MAJOR", szField );

        GetCeosField( record, 197, "A16", szField );
        szField[16] = '\0';

        if( !STARTS_WITH_CI(szField, "                ") )
            SetMetadataItem( "CEOS_SEMI_MINOR", szField );

/* -------------------------------------------------------------------- */
/*      SCENE LENGTH KM                                                 */
/* -------------------------------------------------------------------- */
        GetCeosField( record, 341, "A16", szField );
        szField[16] = '\0';

        if( !STARTS_WITH_CI(szField, "                ") )
            SetMetadataItem( "CEOS_SCENE_LENGTH_KM", szField );

/* -------------------------------------------------------------------- */
/*      SCENE WIDTH KM                                                  */
/* -------------------------------------------------------------------- */
        GetCeosField( record, 357, "A16", szField );
        szField[16] = '\0';

        if( !STARTS_WITH_CI(szField, "                ") )
            SetMetadataItem( "CEOS_SCENE_WIDTH_KM", szField );

/* -------------------------------------------------------------------- */
/*      MISSION ID                                                      */
/* -------------------------------------------------------------------- */
        GetCeosField( record, 397, "A16", szField );
        szField[16] = '\0';

        if( !STARTS_WITH_CI(szField, "                ") )
            SetMetadataItem( "CEOS_MISSION_ID", szField );

/* -------------------------------------------------------------------- */
/*      SENSOR ID                                                      */
/* -------------------------------------------------------------------- */
        GetCeosField( record, 413, "A32", szField );
        szField[32] = '\0';

        if( !STARTS_WITH_CI(szField, "                                ") )
            SetMetadataItem( "CEOS_SENSOR_ID", szField );

/* -------------------------------------------------------------------- */
/*      ORBIT NUMBER                                                    */
/* -------------------------------------------------------------------- */
        GetCeosField( record, 445, "A8", szField );
        szField[8] = '\0';

        if( !STARTS_WITH_CI(szField, "        ") )
            SetMetadataItem( "CEOS_ORBIT_NUMBER", szField );

/* -------------------------------------------------------------------- */
/*      Platform latitude                                               */
/* -------------------------------------------------------------------- */
        GetCeosField( record, 453, "A8", szField );
        szField[8] = '\0';

        if( !STARTS_WITH_CI(szField, "        ") )
            SetMetadataItem( "CEOS_PLATFORM_LATITUDE", szField );

/* -------------------------------------------------------------------- */
/*      Platform longitude                                               */
/* -------------------------------------------------------------------- */
        GetCeosField( record, 461, "A8", szField );
        szField[8] = '\0';

        if( !STARTS_WITH_CI(szField, "        ") )
            SetMetadataItem( "CEOS_PLATFORM_LONGITUDE", szField );

/* -------------------------------------------------------------------- */
/*      Platform heading - at least for ERS2.                           */
/* -------------------------------------------------------------------- */
        GetCeosField( record, 469, "A8", szField );
        szField[8] = '\0';

        if( !STARTS_WITH_CI(szField, "        ") )
            SetMetadataItem( "CEOS_PLATFORM_HEADING", szField );

/* -------------------------------------------------------------------- */
/*      Look Angle.                                                     */
/* -------------------------------------------------------------------- */
        GetCeosField( record, 477, "A8", szField );
        szField[8] = '\0';

        if( !STARTS_WITH_CI(szField, "        ") )
            SetMetadataItem( "CEOS_SENSOR_CLOCK_ANGLE", szField );

/* -------------------------------------------------------------------- */
/*      Incidence angle                                                 */
/* -------------------------------------------------------------------- */
        GetCeosField( record, 485, "A8", szField );
        szField[8] = '\0';

        if( !STARTS_WITH_CI(szField, "        ") )
            SetMetadataItem( "CEOS_INC_ANGLE", szField );

/* -------------------------------------------------------------------- */
/*      Pixel time direction indicator                                  */
/* -------------------------------------------------------------------- */
        GetCeosField( record, 1527, "A8", szField );
        szField[8] = '\0';

        if( !STARTS_WITH_CI(szField, "        ") )
            SetMetadataItem( "CEOS_PIXEL_TIME_DIR", szField );

/* -------------------------------------------------------------------- */
/*      Line spacing                                                    */
/* -------------------------------------------------------------------- */
        GetCeosField( record, 1687, "A16", szField );
        szField[16] = '\0';

        if( !STARTS_WITH_CI(szField, "                ") )
            SetMetadataItem( "CEOS_LINE_SPACING_METERS", szField );
/* -------------------------------------------------------------------- */
/*      Pixel spacing                                                    */
/* -------------------------------------------------------------------- */
        GetCeosField( record, 1703, "A16", szField );
        szField[16] = '\0';

        if( !STARTS_WITH_CI(szField, "                ") )
            SetMetadataItem( "CEOS_PIXEL_SPACING_METERS", szField );
    }

/* -------------------------------------------------------------------- */
/*      Get the beam mode, for radarsat.                                */
/* -------------------------------------------------------------------- */
    record = FindCeosRecord( sVolume.RecordList,
                             LEADER_RADIOMETRIC_COMPENSATION_TC,
                             CEOS_LEADER_FILE, -1, -1 );

    if( strstr(szVolId,"RSAT") != nullptr && record != nullptr )
    {
        szField[16] = '\0';

        GetCeosField( record, 4189, "A16", szField );

        SetMetadataItem( "CEOS_BEAM_TYPE", szField );
    }

/* ==================================================================== */
/*      ERS calibration and incidence angle info                        */
/* ==================================================================== */
    record = FindCeosRecord( sVolume.RecordList, ERS_GENERAL_FACILITY_DATA_TC,
                             CEOS_LEADER_FILE, -1, -1 );

    if( record == nullptr )
        record = FindCeosRecord( sVolume.RecordList,
                                 ERS_GENERAL_FACILITY_DATA_ALT_TC,
                                 CEOS_LEADER_FILE, -1, -1 );

    if( record != nullptr )
    {
        GetCeosField( record, 13 , "A64", szField );
        szField[64] = '\0';

        /* Avoid PCS records, which don't contain necessary info */
        if( strstr( szField, "GENERAL") == nullptr )
            record = nullptr;
    }

    if( record != nullptr )
    {
        GetCeosField( record, 583 , "A16", szField );
        szField[16] = '\0';

        if( !STARTS_WITH_CI(szField, "                ") )
            SetMetadataItem( "CEOS_INC_ANGLE_FIRST_RANGE", szField );

        GetCeosField( record, 599 , "A16", szField );
        szField[16] = '\0';

        if( !STARTS_WITH_CI(szField, "                ") )
            SetMetadataItem( "CEOS_INC_ANGLE_CENTRE_RANGE", szField );

        GetCeosField( record, 615, "A16", szField );
        szField[16] = '\0';

        if( !STARTS_WITH_CI(szField, "                ") )
            SetMetadataItem( "CEOS_INC_ANGLE_LAST_RANGE", szField );

        GetCeosField( record, 663, "A16", szField );
        szField[16] = '\0';

        if( !STARTS_WITH_CI(szField, "                ") )
            SetMetadataItem( "CEOS_CALIBRATION_CONSTANT_K", szField );

        GetCeosField( record, 1855, "A20", szField );
        szField[20] = '\0';

        if( !STARTS_WITH_CI(szField, "                    ") )
            SetMetadataItem( "CEOS_GROUND_TO_SLANT_C0", szField );

        GetCeosField( record, 1875, "A20", szField );
        szField[20] = '\0';

        if( !STARTS_WITH_CI(szField, "                    ") )
            SetMetadataItem( "CEOS_GROUND_TO_SLANT_C1", szField );

        GetCeosField( record, 1895, "A20", szField );
        szField[20] = '\0';

        if( !STARTS_WITH_CI(szField, "                    ") )
            SetMetadataItem( "CEOS_GROUND_TO_SLANT_C2", szField );

        GetCeosField( record, 1915, "A20", szField );
        szField[20] = '\0';

        if( !STARTS_WITH_CI(szField, "                    ") )
            SetMetadataItem( "CEOS_GROUND_TO_SLANT_C3", szField );
    }
/* -------------------------------------------------------------------- */
/*      Detailed Processing Parameters (Radarsat)                       */
/* -------------------------------------------------------------------- */
    record = FindCeosRecord( sVolume.RecordList, RSAT_PROC_PARAM_TC,
                             CEOS_LEADER_FILE, -1, -1 );

    if( record == nullptr )
        record = FindCeosRecord( sVolume.RecordList, RSAT_PROC_PARAM_TC,
                             CEOS_TRAILER_FILE, -1, -1 );

    if( record != nullptr )
    {
        GetCeosField( record, 192, "A21", szField );
        szField[21] = '\0';

        if( !STARTS_WITH_CI(szField, "                     ") )
            SetMetadataItem( "CEOS_PROC_START", szField );

        GetCeosField( record, 213, "A21", szField );
        szField[21] = '\0';

        if( !STARTS_WITH_CI(szField, "                     ") )
            SetMetadataItem( "CEOS_PROC_STOP", szField );

        GetCeosField( record, 4649, "A16", szField );
        szField[16] = '\0';

        if( !STARTS_WITH_CI(szField, "                ") )
            SetMetadataItem( "CEOS_EPH_ORB_DATA_0", szField );

        GetCeosField( record, 4665, "A16", szField );
        szField[16] = '\0';

        if( !STARTS_WITH_CI(szField, "                ") )
            SetMetadataItem( "CEOS_EPH_ORB_DATA_1", szField );

        GetCeosField( record, 4681, "A16", szField );
        szField[16] = '\0';

        if( !STARTS_WITH_CI(szField, "                ") )
            SetMetadataItem( "CEOS_EPH_ORB_DATA_2", szField );

        GetCeosField( record, 4697, "A16", szField );
        szField[16] = '\0';

        if( !STARTS_WITH_CI(szField, "                ") )
            SetMetadataItem( "CEOS_EPH_ORB_DATA_3", szField );

        GetCeosField( record, 4713, "A16", szField );
        szField[16] = '\0';

        if( !STARTS_WITH_CI(szField, "                ") )
            SetMetadataItem( "CEOS_EPH_ORB_DATA_4", szField );

        GetCeosField( record, 4729, "A16", szField );
        szField[16] = '\0';

        if( !STARTS_WITH_CI(szField, "                ") )
            SetMetadataItem( "CEOS_EPH_ORB_DATA_5", szField );

        GetCeosField( record, 4745, "A16", szField );
        szField[16] = '\0';

        if( !STARTS_WITH_CI(szField, "                ") )
            SetMetadataItem( "CEOS_EPH_ORB_DATA_6", szField );

        GetCeosField( record, 4908, "A16", szField );
        szField[16] = '\0';

        if( !STARTS_WITH_CI(szField, "                ") )
            SetMetadataItem( "CEOS_GROUND_TO_SLANT_C0", szField );

        GetCeosField( record, 4924, "A16", szField );
        szField[16] = '\0';

        if( !STARTS_WITH_CI(szField, "                ") )
            SetMetadataItem( "CEOS_GROUND_TO_SLANT_C1", szField );

        GetCeosField( record, 4940, "A16", szField );
        szField[16] = '\0';

        if( !STARTS_WITH_CI(szField, "                ") )
            SetMetadataItem( "CEOS_GROUND_TO_SLANT_C2", szField );

        GetCeosField( record, 4956, "A16", szField );
        szField[16] = '\0';

        if( !STARTS_WITH_CI(szField, "                ") )
            SetMetadataItem( "CEOS_GROUND_TO_SLANT_C3", szField );

        GetCeosField( record, 4972, "A16", szField );
        szField[16] = '\0';

        if( !STARTS_WITH_CI(szField, "                ") )
            SetMetadataItem( "CEOS_GROUND_TO_SLANT_C4", szField );

        GetCeosField( record, 4988, "A16", szField );
        szField[16] = '\0';

        if( !STARTS_WITH_CI(szField, "                ") )
            SetMetadataItem( "CEOS_GROUND_TO_SLANT_C5", szField );

        GetCeosField( record, 7334, "A16", szField );
        szField[16] = '\0';

        if( !STARTS_WITH_CI(szField, "                ") )
            SetMetadataItem( "CEOS_INC_ANGLE_FIRST_RANGE", szField );

        GetCeosField( record, 7350, "A16", szField );
        szField[16] = '\0';

        if( !STARTS_WITH_CI(szField, "                ") )
            SetMetadataItem( "CEOS_INC_ANGLE_LAST_RANGE", szField );
    }
/* -------------------------------------------------------------------- */
/*      Get process-to-raw data coordinate translation values.  These   */
/*      are likely specific to Atlantis APP products.                   */
/* -------------------------------------------------------------------- */
    record = FindCeosRecord( sVolume.RecordList,
                             IMAGE_HEADER_RECORD_TC,
                             CEOS_IMAGRY_OPT_FILE, -1, -1 );

    if( record != nullptr )
    {
        GetCeosField( record, 449, "A4", szField );
        szField[4] = '\0';

        if( !STARTS_WITH_CI(szField, "    ") )
            SetMetadataItem( "CEOS_DM_CORNER", szField );

        GetCeosField( record, 453, "A4", szField );
        szField[4] = '\0';

        if( !STARTS_WITH_CI(szField, "    ") )
            SetMetadataItem( "CEOS_DM_TRANSPOSE", szField );

        GetCeosField( record, 457, "A4", szField );
        szField[4] = '\0';

        if( !STARTS_WITH_CI(szField, "    ") )
            SetMetadataItem( "CEOS_DM_START_SAMPLE", szField );

        GetCeosField( record, 461, "A5", szField );
        szField[5] = '\0';

        if( !STARTS_WITH_CI(szField, "     ") )
            SetMetadataItem( "CEOS_DM_START_PULSE", szField );

        GetCeosField( record, 466, "A16", szField );
        szField[16] = '\0';

        if( !STARTS_WITH_CI(szField, "                ") )
            SetMetadataItem( "CEOS_DM_FAST_ALPHA", szField );

        GetCeosField( record, 482, "A16", szField );
        szField[16] = '\0';

        if( !STARTS_WITH_CI(szField, "                ") )
            SetMetadataItem( "CEOS_DM_FAST_BETA", szField );

        GetCeosField( record, 498, "A16", szField );
        szField[16] = '\0';

        if( !STARTS_WITH_CI(szField, "                ") )
            SetMetadataItem( "CEOS_DM_SLOW_ALPHA", szField );

        GetCeosField( record, 514, "A16", szField );
        szField[16] = '\0';

        if( !STARTS_WITH_CI(szField, "                ") )
            SetMetadataItem( "CEOS_DM_SLOW_BETA", szField );

        GetCeosField( record, 530, "A16", szField );
        szField[16] = '\0';

        if( !STARTS_WITH_CI(szField, "                ") )
            SetMetadataItem( "CEOS_DM_FAST_ALPHA_2", szField );
    }

/* -------------------------------------------------------------------- */
/*      Try to find calibration information from Radiometric Data       */
/*      Record.                                                         */
/* -------------------------------------------------------------------- */
    record = FindCeosRecord( sVolume.RecordList,
                             LEADER_RADIOMETRIC_DATA_RECORD_TC,
                             CEOS_LEADER_FILE, -1, -1 );

    if( record == nullptr )
        record = FindCeosRecord( sVolume.RecordList,
                             LEADER_RADIOMETRIC_DATA_RECORD_TC,
                             CEOS_TRAILER_FILE, -1, -1 );

    if( record != nullptr )
    {
        GetCeosField( record, 8317, "A16", szField );
        szField[16] = '\0';

        if( !STARTS_WITH_CI(szField, "                ") )
            SetMetadataItem( "CEOS_CALIBRATION_OFFSET", szField );
    }

/* -------------------------------------------------------------------- */
/*      For ERS Standard Format Landsat scenes we pick up the           */
/*      calibration offset and gain from the Radiometric Ancillary      */
/*      Record.                                                         */
/* -------------------------------------------------------------------- */
    record = FindCeosRecord( sVolume.RecordList,
                             QuadToTC( 0x3f, 0x24, 0x12, 0x09 ),
                             CEOS_LEADER_FILE, -1, -1 );
    if( record != nullptr )
    {
        GetCeosField( record, 29, "A20", szField );
        szField[20] = '\0';

        if( !STARTS_WITH_CI(szField, "                    ") )
            SetMetadataItem( "CEOS_OFFSET_A0", szField );

        GetCeosField( record, 49, "A20", szField );
        szField[20] = '\0';

        if( !STARTS_WITH_CI(szField, "                    ") )
            SetMetadataItem( "CEOS_GAIN_A1", szField );
    }

/* -------------------------------------------------------------------- */
/*      For ERS Standard Format Landsat scenes we pick up the           */
/*      gain setting from the Scene Header Record.                      */
/* -------------------------------------------------------------------- */
    record = FindCeosRecord( sVolume.RecordList,
                             QuadToTC( 0x12, 0x12, 0x12, 0x09 ),
                             CEOS_LEADER_FILE, -1, -1 );
    if( record != nullptr )
    {
        GetCeosField( record, 1486, "A1", szField );
        szField[1] = '\0';

        if( szField[0] == 'H' || szField[0] == 'V' )
            SetMetadataItem( "CEOS_GAIN_SETTING", szField );
    }
}

/************************************************************************/
/*                        ScanForMapProjection()                        */
/*                                                                      */
/*      Try to find a map projection record, and read corner points     */
/*      from it.  This has only been tested with ERS products.          */
/************************************************************************/

int SAR_CEOSDataset::ScanForMapProjection()

{
/* -------------------------------------------------------------------- */
/*      Find record, and try to determine if it has useful GCPs.        */
/* -------------------------------------------------------------------- */

    CeosRecord_t *record = FindCeosRecord( sVolume.RecordList,
                                           LEADER_MAP_PROJ_RECORD_TC,
                                           CEOS_LEADER_FILE, -1, -1 );

    /* JERS from Japan */
    if( record == nullptr )
        record = FindCeosRecord( sVolume.RecordList,
                             LEADER_MAP_PROJ_RECORD_JERS_TC,
                             CEOS_LEADER_FILE, -1, -1 );

    if( record == nullptr )
        return FALSE;

    char szField[100];
    memset( szField, 0, 17 );
    GetCeosField( record, 29, "A16", szField );

    if( !STARTS_WITH_CI(szField, "Slant Range") && !STARTS_WITH_CI(szField, "Ground Range")
        && !STARTS_WITH_CI(szField, "GEOCODED") )
        return FALSE;

    GetCeosField( record, 1073, "A16", szField );
    if( STARTS_WITH_CI(szField, "        ") )
        return FALSE;

/* -------------------------------------------------------------------- */
/*      Read corner points.                                             */
/* -------------------------------------------------------------------- */
    nGCPCount = 4;
    pasGCPList = (GDAL_GCP *) CPLCalloc(sizeof(GDAL_GCP),nGCPCount);

    GDALInitGCPs( nGCPCount, pasGCPList );

    for( int i = 0; i < nGCPCount; i++ )
    {
        char         szId[32];

        snprintf( szId, sizeof(szId), "%d", i+1 );
        pasGCPList[i].pszId = CPLStrdup( szId );

        GetCeosField( record, 1073+32*i, "A16", szField );
        pasGCPList[i].dfGCPY = CPLAtof(szField);
        GetCeosField( record, 1089+32*i, "A16", szField );
        pasGCPList[i].dfGCPX = CPLAtof(szField);
        pasGCPList[i].dfGCPZ = 0.0;
    }

    pasGCPList[0].dfGCPLine = 0.5;
    pasGCPList[0].dfGCPPixel = 0.5;

    pasGCPList[1].dfGCPLine = 0.5;
    pasGCPList[1].dfGCPPixel = nRasterXSize-0.5;

    pasGCPList[2].dfGCPLine = nRasterYSize-0.5;
    pasGCPList[2].dfGCPPixel = nRasterXSize-0.5;

    pasGCPList[3].dfGCPLine = nRasterYSize-0.5;
    pasGCPList[3].dfGCPPixel = 0.5;

    return TRUE;
}

/************************************************************************/
/*                            ScanForGCPs()                             */
/************************************************************************/

void SAR_CEOSDataset::ScanForGCPs()

{
/* -------------------------------------------------------------------- */
/*      Do we have a standard 180 bytes of prefix data (192 bytes       */
/*      including the record marker information)?  If not, it is        */
/*      unlikely that the GCPs are available.                           */
/* -------------------------------------------------------------------- */
    if( sVolume.ImageDesc.ImageDataStart < 192 )
    {
        ScanForMapProjection();
        return;
    }

/* -------------------------------------------------------------------- */
/*      Just sample fix scanlines through the image for GCPs, to        */
/*      return 15 GCPs.  That is an adequate coverage for most          */
/*      purposes.  A GCP is collected from the beginning, middle and    */
/*      end of each scanline.                                           */
/* -------------------------------------------------------------------- */
    nGCPCount = 0;
    int nGCPMax = 15;
    pasGCPList = (GDAL_GCP *) CPLCalloc(sizeof(GDAL_GCP),nGCPMax);

    int nStep = (GetRasterYSize()-1) / (nGCPMax / 3 - 1);
    for( int iScanline = 0; iScanline < GetRasterYSize(); iScanline += nStep )
    {
        if( nGCPCount > nGCPMax-3 )
            break;

        int nFileOffset;
        CalcCeosSARImageFilePosition( &sVolume, 1, iScanline+1, nullptr,
                                      &nFileOffset );

        GInt32 anRecord[192/4];
        if( VSIFSeekL( fpImage, nFileOffset, SEEK_SET ) != 0
            || VSIFReadL( anRecord, 1, 192, fpImage ) != 192 )
            break;

        /* loop over first, middle and last pixel gcps */

        for( int iGCP = 0; iGCP < 3; iGCP++ )
        {
            const int nLat  = CPL_MSBWORD32( anRecord[132/4 + iGCP] );
            const int nLong = CPL_MSBWORD32( anRecord[144/4 + iGCP] );

            if( nLat != 0 || nLong != 0 )
            {
                GDALInitGCPs( 1, pasGCPList + nGCPCount );

                CPLFree( pasGCPList[nGCPCount].pszId );

                char szId[32];
                snprintf( szId, sizeof(szId), "%d", nGCPCount+1 );
                pasGCPList[nGCPCount].pszId = CPLStrdup( szId );

                pasGCPList[nGCPCount].dfGCPX = nLong / 1000000.0;
                pasGCPList[nGCPCount].dfGCPY = nLat / 1000000.0;
                pasGCPList[nGCPCount].dfGCPZ = 0.0;

                pasGCPList[nGCPCount].dfGCPLine = iScanline + 0.5;

                if( iGCP == 0 )
                    pasGCPList[nGCPCount].dfGCPPixel = 0.5;
                else if( iGCP == 1 )
                    pasGCPList[nGCPCount].dfGCPPixel =
                        GetRasterXSize() / 2.0;
                else
                    pasGCPList[nGCPCount].dfGCPPixel =
                        GetRasterXSize() - 0.5;

                nGCPCount++;
            }
        }
    }
    /* If general GCP's were not found, look for Map Projection (e.g. JERS) */
    if( nGCPCount == 0 )
    {
        ScanForMapProjection();
        return;
    }
}

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

GDALDataset *SAR_CEOSDataset::Open( GDALOpenInfo * poOpenInfo )

{
/* -------------------------------------------------------------------- */
/*      Does this appear to be a valid ceos leader record?              */
/* -------------------------------------------------------------------- */
    if( poOpenInfo->nHeaderBytes < CEOS_HEADER_LENGTH || poOpenInfo->fpL == nullptr )
        return nullptr;

    if( (poOpenInfo->pabyHeader[4] != 0x3f
         && poOpenInfo->pabyHeader[4] != 0x32)
        || poOpenInfo->pabyHeader[5] != 0xc0
        || poOpenInfo->pabyHeader[6] != 0x12
        || poOpenInfo->pabyHeader[7] != 0x12 )
        return nullptr;

    // some products (#1862) have byte swapped record length/number
    // values and will blow stuff up -- explicitly ignore if record index
    // value appears to be little endian.
    if( poOpenInfo->pabyHeader[0] != 0 )
        return nullptr;

/* -------------------------------------------------------------------- */
/*      Confirm the requested access is supported.                      */
/* -------------------------------------------------------------------- */
    if( poOpenInfo->eAccess == GA_Update )
    {
        CPLError( CE_Failure, CPLE_NotSupported,
                  "The SAR_CEOS driver does not support update access to existing"
                  " datasets.\n" );
        return nullptr;
    }

    VSILFILE *fp = poOpenInfo->fpL;
    poOpenInfo->fpL = nullptr;

/* -------------------------------------------------------------------- */
/*      Create a corresponding GDALDataset.                             */
/* -------------------------------------------------------------------- */

    SAR_CEOSDataset *poDS = new SAR_CEOSDataset();
    CeosSARVolume_t *psVolume = &(poDS->sVolume);
    InitCeosSARVolume( psVolume, 0 );

/* -------------------------------------------------------------------- */
/*      Try to read the current file as an imagery file.                */
/* -------------------------------------------------------------------- */

    psVolume->ImagryOptionsFile = TRUE;
    if( ProcessData( fp, CEOS_IMAGRY_OPT_FILE, psVolume, 4, VSI_L_OFFSET_MAX) != CE_None )
    {
        delete poDS;
        CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
        return nullptr;
    }

/* -------------------------------------------------------------------- */
/*      Try the various filenames.                                      */
/* -------------------------------------------------------------------- */
    char *pszPath = CPLStrdup(CPLGetPath(poOpenInfo->pszFilename));
    char *pszBasename = CPLStrdup(CPLGetBasename(poOpenInfo->pszFilename));
    char *pszExtension = CPLStrdup(CPLGetExtension(poOpenInfo->pszFilename));

    int nBand;
    if( strlen(pszBasename) > 4 )
        nBand = atoi( pszBasename + 4 );
    else
        nBand = 0;

    for( int iFile = 0; iFile < 5;iFile++ )
    {
        /* skip image file ... we already did it */
        if( iFile == 2 )
            continue;

        int e = 0;
        while( CeosExtension[e][iFile] != nullptr )
        {
            char *pszFilename = nullptr;

            /* build filename */
            if( EQUAL(CeosExtension[e][5],"base") )
            {
                char    szMadeBasename[32];

                snprintf( szMadeBasename, sizeof(szMadeBasename),
                          CeosExtension[e][iFile], nBand );
                pszFilename = CPLStrdup(
                    CPLFormFilename(pszPath,szMadeBasename, pszExtension));
            }
            else if( EQUAL(CeosExtension[e][5],"ext") )
            {
                pszFilename = CPLStrdup(
                    CPLFormFilename(pszPath,pszBasename,
                                    CeosExtension[e][iFile]));
            }
            else if( EQUAL(CeosExtension[e][5],"whole") )
            {
                pszFilename = CPLStrdup(
                    CPLFormFilename(pszPath,CeosExtension[e][iFile],""));
            }

            // This is for SAR SLC as per the SAR Toolbox (from ASF).
            else if( EQUAL(CeosExtension[e][5],"ext2") )
            {
                char szThisExtension[32];

                if( strlen(pszExtension) > 3 )
                    snprintf( szThisExtension, sizeof(szThisExtension), "%s%s",
                             CeosExtension[e][iFile],
                             pszExtension+3 );
                else
                    snprintf( szThisExtension, sizeof(szThisExtension), "%s",
                             CeosExtension[e][iFile] );

                pszFilename = CPLStrdup(
                    CPLFormFilename(pszPath,pszBasename,szThisExtension));
            }

            CPLAssert( pszFilename != nullptr );
            if( pszFilename == nullptr )
                return nullptr;

            /* try to open */
            VSILFILE *process_fp = VSIFOpenL( pszFilename, "rb" );

            /* try upper case */
            if( process_fp == nullptr )
            {
                for( int i = static_cast<int>(strlen(pszFilename))-1;
                     i >= 0 && pszFilename[i] != '/' && pszFilename[i] != '\\';
                     i-- )
                {
                    if( pszFilename[i] >= 'a' && pszFilename[i] <= 'z' )
                        pszFilename[i] = pszFilename[i] - 'a' + 'A';
                }

                process_fp = VSIFOpenL( pszFilename, "rb" );
            }

            if( process_fp != nullptr )
            {
                CPLDebug( "CEOS", "Opened %s.\n", pszFilename );

                CPL_IGNORE_RET_VAL(VSIFSeekL( process_fp, 0, SEEK_END ));
                if( ProcessData( process_fp, iFile, psVolume, -1,
                                 VSIFTellL( process_fp ) ) == 0 )
                {
                    switch( iFile )
                    {
                      case 0: psVolume->VolumeDirectoryFile = TRUE;
                        break;
                      case 1: psVolume->SARLeaderFile = TRUE;
                        break;
                      case 3: psVolume->SARTrailerFile = TRUE;
                        break;
                      case 4: psVolume->NullVolumeDirectoryFile = TRUE;
                        break;
                    }

                    CPL_IGNORE_RET_VAL(VSIFCloseL( process_fp ));
                    CPLFree( pszFilename );
                    break; /* Exit the while loop, we have this data type*/
                }

                CPL_IGNORE_RET_VAL(VSIFCloseL( process_fp ));
            }

            CPLFree( pszFilename );

            e++;
        }
    }

    CPLFree( pszPath );
    CPLFree( pszBasename );
    CPLFree( pszExtension );

/* -------------------------------------------------------------------- */
/*      Check that we have an image description.                        */
/* -------------------------------------------------------------------- */
    GetCeosSARImageDesc( psVolume );
    struct CeosSARImageDesc *psImageDesc = &(psVolume->ImageDesc);
    if( !psImageDesc->ImageDescValid )
    {
        delete poDS;

        CPLDebug( "CEOS",
                  "Unable to extract CEOS image description\n"
                  "from %s.",
                  poOpenInfo->pszFilename );

        CPL_IGNORE_RET_VAL(VSIFCloseL(fp));

        return nullptr;
    }

/* -------------------------------------------------------------------- */
/*      Establish image type.                                           */
/* -------------------------------------------------------------------- */
    GDALDataType eType;

    switch( psImageDesc->DataType )
    {
      case CEOS_TYP_CHAR:
      case CEOS_TYP_UCHAR:
        eType = GDT_Byte;
        break;

      case CEOS_TYP_SHORT:
        eType = GDT_Int16;
        break;

      case CEOS_TYP_COMPLEX_SHORT:
      case CEOS_TYP_PALSAR_COMPLEX_SHORT:
        eType = GDT_CInt16;
        break;

      case CEOS_TYP_USHORT:
        eType = GDT_UInt16;
        break;

      case CEOS_TYP_LONG:
        eType = GDT_Int32;
        break;

      case CEOS_TYP_ULONG:
        eType = GDT_UInt32;
        break;

      case CEOS_TYP_FLOAT:
        eType = GDT_Float32;
        break;

      case CEOS_TYP_DOUBLE:
        eType = GDT_Float64;
        break;

      case CEOS_TYP_COMPLEX_FLOAT:
      case CEOS_TYP_CCP_COMPLEX_FLOAT:
        eType = GDT_CFloat32;
        break;

      default:
        CPLError( CE_Failure, CPLE_AppDefined,
                  "Unsupported CEOS image data type %d.\n",
                  psImageDesc->DataType );
        delete poDS;
        return nullptr;
    }

/* -------------------------------------------------------------------- */
/*      Capture some information from the file that is of interest.     */
/* -------------------------------------------------------------------- */
    poDS->nRasterXSize = psImageDesc->PixelsPerLine;
    poDS->nRasterYSize = psImageDesc->Lines;

    const int bNative =
#ifdef CPL_LSB
    FALSE
#else
    TRUE
#endif
    ;

/* -------------------------------------------------------------------- */
/*      Special case for compressed cross products.                     */
/* -------------------------------------------------------------------- */
    if( psImageDesc->DataType == CEOS_TYP_CCP_COMPLEX_FLOAT )
    {
        for( int iBand = 0; iBand < psImageDesc->NumChannels; iBand++ )
        {
            poDS->SetBand( poDS->nBands+1,
                           new CCPRasterBand( poDS, poDS->nBands+1, eType ) );
        }

        /* mark this as a Scattering Matrix product */
        if ( poDS->GetRasterCount() == 4 ) {
            poDS->SetMetadataItem( "MATRIX_REPRESENTATION", "SCATTERING" );
        }
    }

/* -------------------------------------------------------------------- */
/*      Special case for PALSAR data.                                   */
/* -------------------------------------------------------------------- */
    else if( psImageDesc->DataType == CEOS_TYP_PALSAR_COMPLEX_SHORT )
    {
        for( int iBand = 0; iBand < psImageDesc->NumChannels; iBand++ )
        {
            poDS->SetBand( poDS->nBands+1,
                           new PALSARRasterBand( poDS, poDS->nBands+1 ) );
        }

        /* mark this as a Symmetrized Covariance product if appropriate */
        if ( poDS->GetRasterCount() == 6 ) {
            poDS->SetMetadataItem( "MATRIX_REPRESENTATION",
                "SYMMETRIZED_COVARIANCE" );
        }
    }

/* -------------------------------------------------------------------- */
/*      Roll our own ...                                                */
/* -------------------------------------------------------------------- */
    else if( psImageDesc->RecordsPerLine > 1
             || psImageDesc->DataType == CEOS_TYP_CHAR
             || psImageDesc->DataType == CEOS_TYP_LONG
             || psImageDesc->DataType == CEOS_TYP_ULONG
             || psImageDesc->DataType == CEOS_TYP_DOUBLE )
    {
        for( int iBand = 0; iBand < psImageDesc->NumChannels; iBand++ )
        {
            poDS->SetBand( poDS->nBands+1,
                           new SAR_CEOSRasterBand( poDS, poDS->nBands+1,
                                                   eType ) );
        }
    }

/* -------------------------------------------------------------------- */
/*      Use raw services for well behaved files.                        */
/* -------------------------------------------------------------------- */
    else
    {
        int StartData;
        CalcCeosSARImageFilePosition( psVolume, 1, 1, nullptr, &StartData );

        /*StartData += psImageDesc->ImageDataStart; */

        int nLineSize, nLineSize2;
        CalcCeosSARImageFilePosition( psVolume, 1, 1, nullptr, &nLineSize );
        CalcCeosSARImageFilePosition( psVolume, 1, 2, nullptr, &nLineSize2 );

        nLineSize = nLineSize2 - nLineSize;

        for( int iBand = 0; iBand < psImageDesc->NumChannels; iBand++ )
        {
            int           nStartData, nPixelOffset, nLineOffset;

            if( psImageDesc->ChannelInterleaving == CEOS_IL_PIXEL )
            {
                CalcCeosSARImageFilePosition(psVolume,1,1,nullptr,&nStartData);

                nStartData += psImageDesc->ImageDataStart;
                nStartData += psImageDesc->BytesPerPixel * iBand;

                nPixelOffset =
                    psImageDesc->BytesPerPixel * psImageDesc->NumChannels;
                nLineOffset = nLineSize;
            }
            else if( psImageDesc->ChannelInterleaving == CEOS_IL_LINE )
            {
                CalcCeosSARImageFilePosition(psVolume, iBand+1, 1, nullptr,
                                             &nStartData);

                nStartData += psImageDesc->ImageDataStart;
                nPixelOffset = psImageDesc->BytesPerPixel;
                nLineOffset = nLineSize * psImageDesc->NumChannels;
            }
            else if( psImageDesc->ChannelInterleaving == CEOS_IL_BAND )
            {
                CalcCeosSARImageFilePosition(psVolume, iBand+1, 1, nullptr,
                                             &nStartData);

                nStartData += psImageDesc->ImageDataStart;
                nPixelOffset = psImageDesc->BytesPerPixel;
                nLineOffset = nLineSize;
            }
            else
            {
                CPLAssert( false );
                return nullptr;
            }

            poDS->SetBand( poDS->nBands+1,
                    new RawRasterBand(
                        poDS, poDS->nBands+1, fp,
                        nStartData, nPixelOffset, nLineOffset,
                        eType, bNative, RawRasterBand::OwnFP::NO ) );
        }
    }

/* -------------------------------------------------------------------- */
/*      Adopt the file pointer.                                         */
/* -------------------------------------------------------------------- */
    poDS->fpImage = fp;

/* -------------------------------------------------------------------- */
/*      Collect metadata.                                               */
/* -------------------------------------------------------------------- */
    poDS->ScanForMetadata();

/* -------------------------------------------------------------------- */
/*      Check for GCPs.                                                 */
/* -------------------------------------------------------------------- */
    poDS->ScanForGCPs();

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

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

    return poDS;
}

/************************************************************************/
/*                            ProcessData()                             */
/************************************************************************/
static int
ProcessData( VSILFILE *fp, int fileid, CeosSARVolume_t *sar, int max_records,
             vsi_l_offset max_bytes )

{
    unsigned char      temp_buffer[CEOS_HEADER_LENGTH];
    unsigned char      *temp_body = nullptr;
    int                start = 0;
    int                CurrentBodyLength = 0;
    int                CurrentType = 0;
    int                CurrentSequence = 0;
    int                iThisRecord = 0;

    while(max_records != 0 && max_bytes != 0)
    {
        iThisRecord++;

        if( VSIFSeekL( fp, start, SEEK_SET ) != 0 ||
            VSIFReadL( temp_buffer, 1, CEOS_HEADER_LENGTH, fp ) != CEOS_HEADER_LENGTH )
        {
            CPLError( CE_Failure, CPLE_AppDefined,
                      "Corrupt CEOS File - cannot read record %d.",
                      iThisRecord );
            CPLFree(temp_body);
            return CE_Failure;
        }
        CeosRecord_t *record =
            (CeosRecord_t *) CPLMalloc( sizeof( CeosRecord_t ) );
        record->Length = DetermineCeosRecordBodyLength( temp_buffer );

        CeosToNative( &(record->Sequence), temp_buffer, 4, 4 );

        if( iThisRecord != record->Sequence )
        {
            if( fileid == CEOS_IMAGRY_OPT_FILE && iThisRecord == 2 )
            {
                CPLDebug( "SAR_CEOS", "Ignoring CEOS file with wrong second record sequence number - likely it has padded records." );
                CPLFree(record);
                CPLFree(temp_body);
                return CE_Warning;
            }
            else
            {
                CPLError( CE_Failure, CPLE_AppDefined,
                          "Corrupt CEOS File - got record seq# %d instead of the expected %d.",
                          record->Sequence, iThisRecord );
                CPLFree(record);
                CPLFree(temp_body);
                return CE_Failure;
            }
        }

        if( record->Length <= CEOS_HEADER_LENGTH )
        {
            CPLError( CE_Failure, CPLE_AppDefined,
                      "Corrupt CEOS File - cannot read record %d.",
                      iThisRecord );
            CPLFree(record);
            CPLFree(temp_body);
            return CE_Failure;
        }

        if( record->Length > CurrentBodyLength )
        {
            unsigned char* temp_body_new = (unsigned char *)
                    VSI_REALLOC_VERBOSE( temp_body, record->Length );
            if( temp_body_new == nullptr )
            {
                CPLFree(record);
                CPLFree(temp_body);
                return CE_Failure;
            }
            temp_body = temp_body_new;
            CurrentBodyLength = record->Length;
        }

        int nToRead = record->Length-CEOS_HEADER_LENGTH;
        if( (int)VSIFReadL( temp_body, 1, nToRead,fp) != nToRead )
        {
            CPLError( CE_Failure, CPLE_AppDefined,
                      "Corrupt CEOS File - cannot read record %d.",
                      iThisRecord );
            CPLFree(record);
            CPLFree(temp_body);
            return CE_Failure;
        }

        InitCeosRecordWithHeader( record, temp_buffer, temp_body );
        if( record->Length == 0 )
        {
            CPLError( CE_Failure, CPLE_AppDefined,
                      "Corrupt CEOS File - invalid record %d.",
                      iThisRecord );
            CPLFree(record);
            CPLFree(temp_body);
            return CE_Failure;
        }

        if( CurrentType == record->TypeCode.Int32Code )
            record->Subsequence = ++CurrentSequence;
        else {
            CurrentType = record->TypeCode.Int32Code;
            record->Subsequence = 0;
            CurrentSequence = 0;
        }

        record->FileId = fileid;

        Link_t *TheLink = ceos2CreateLink( record );

        if( sar->RecordList == nullptr )
            sar->RecordList = TheLink;
        else
            sar->RecordList = InsertLink( sar->RecordList, TheLink );

        start += record->Length;

        if(max_records > 0)
            max_records--;
        if(max_bytes > 0)
        {
          if( (vsi_l_offset)record->Length <= max_bytes )
                max_bytes -= record->Length;
            else {
                CPLDebug( "SAR_CEOS", "Partial record found.  %d > " CPL_FRMT_GUIB,
                          record->Length, max_bytes );
                max_bytes = 0;
            }
        }
    }

    CPLFree(temp_body);

    return CE_None;
}

/************************************************************************/
/*                       GDALRegister_SAR_CEOS()                        */
/************************************************************************/

void GDALRegister_SAR_CEOS()

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

    GDALDriver *poDriver = new GDALDriver();

    poDriver->SetDescription( "SAR_CEOS" );
    poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" );
    poDriver->SetMetadataItem( GDAL_DMD_LONGNAME,
                               "CEOS SAR Image" );
    poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC,
                               "frmt_various.html#SAR_CEOS" );
    poDriver->SetMetadataItem( GDAL_DCAP_VIRTUALIO, "YES" );

    poDriver->pfnOpen = SAR_CEOSDataset::Open;

    GetGDALDriverManager()->RegisterDriver( poDriver );
}
