/******************************************************************************
 * $Id: ehdrdataset.cpp 33862 2016-04-02 11:17:23Z goatbar $
 *
 * Project:  ESRI .hdr Driver
 * Purpose:  Implementation of EHdrDataset
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 1999, Frank Warmerdam <warmerdam@pobox.com>
 * Copyright (c) 2007-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 "cpl_string.h"
#include "gdal_frmts.h"
#include "ogr_spatialref.h"
#include "rawdataset.h"

CPL_CVSID("$Id: ehdrdataset.cpp 33862 2016-04-02 11:17:23Z goatbar $");

static const int HAS_MIN_FLAG = 0x1;
static const int HAS_MAX_FLAG = 0x2;
static const int HAS_MEAN_FLAG = 0x4;
static const int HAS_STDDEV_FLAG = 0x8;
static const int HAS_ALL_FLAGS =
    HAS_MIN_FLAG | HAS_MAX_FLAG | HAS_MEAN_FLAG | HAS_STDDEV_FLAG;

/************************************************************************/
/* ==================================================================== */
/*                       EHdrDataset                                    */
/* ==================================================================== */
/************************************************************************/

class EHdrRasterBand;

class EHdrDataset : public RawDataset
{
    friend class EHdrRasterBand;

    VSILFILE   *fpImage;  // image data file.

    CPLString   osHeaderExt;

    int         bGotTransform;
    double      adfGeoTransform[6];
    char       *pszProjection;

    int         bHDRDirty;
    char      **papszHDR;

    int         bCLRDirty;

    CPLErr      ReadSTX();
    CPLErr      RewriteSTX();
    CPLErr      RewriteHDR();
    void        ResetKeyValue( const char *pszKey, const char *pszValue );
    const char *GetKeyValue( const char *pszKey, const char *pszDefault = "" );
    void        RewriteColorTable( GDALColorTable * );

  public:
    EHdrDataset();
    virtual ~EHdrDataset();

    virtual CPLErr GetGeoTransform( double * padfTransform );
    virtual CPLErr SetGeoTransform( double *padfTransform );
    virtual const char *GetProjectionRef(void);
    virtual CPLErr SetProjection( const char * );

    virtual char **GetFileList();

    static GDALDataset *Open( GDALOpenInfo * );
    static GDALDataset *Create( const char * pszFilename,
                                int nXSize, int nYSize, int nBands,
                                GDALDataType eType, char ** papszParmList );
    static GDALDataset *CreateCopy( const char * pszFilename,
                                    GDALDataset * poSrcDS,
                                    int bStrict, char ** papszOptions,
                                    GDALProgressFunc pfnProgress,
                                    void * pProgressData );
    static CPLString GetImageRepFilename(const char* pszFilename);
};

/************************************************************************/
/* ==================================================================== */
/*                          EHdrRasterBand                              */
/* ==================================================================== */
/************************************************************************/

class EHdrRasterBand : public RawRasterBand
{
   friend class EHdrDataset;

    int            nBits;
    vsi_l_offset   nStartBit;
    int            nPixelOffsetBits;
    vsi_l_offset   nLineOffsetBits;

    int            bNoDataSet;
    double         dfNoData;
    double         dfMin;
    double         dfMax;
    double         dfMean;
    double         dfStdDev;

    int            minmaxmeanstddev;

    virtual CPLErr IRasterIO( GDALRWFlag, int, int, int, int,
                              void *, int, int, GDALDataType,
                              GSpacing nPixelSpace,
                              GSpacing nLineSpace,
                              GDALRasterIOExtraArg* psExtraArg );

  public:
    EHdrRasterBand( GDALDataset *poDS, int nBand, VSILFILE * fpRaw,
                    vsi_l_offset nImgOffset, int nPixelOffset,
                    int nLineOffset,
                    GDALDataType eDataType, int bNativeOrder,
                    int nBits);
    virtual ~EHdrRasterBand() {}

    virtual CPLErr IReadBlock( int, int, void * );
    virtual CPLErr IWriteBlock( int, int, void * );

    virtual double GetNoDataValue( int *pbSuccess = NULL );
    virtual double GetMinimum( int *pbSuccess = NULL );
    virtual double GetMaximum(int *pbSuccess = NULL );
    virtual CPLErr GetStatistics( int bApproxOK, int bForce,
                                  double *pdfMin, double *pdfMax,
                                  double *pdfMean, double *pdfStdDev );
    virtual CPLErr SetStatistics( double dfMin, double dfMax,
                                  double dfMean, double dfStdDev );
    virtual CPLErr SetColorTable( GDALColorTable *poNewCT );

};

/************************************************************************/
/*                           EHdrRasterBand()                           */
/************************************************************************/

EHdrRasterBand::EHdrRasterBand( GDALDataset *poDSIn,
                                int nBandIn, VSILFILE * fpRawIn,
                                vsi_l_offset nImgOffsetIn, int nPixelOffsetIn,
                                int nLineOffsetIn,
                                GDALDataType eDataTypeIn, int bNativeOrderIn,
                                int nBitsIn) :
  RawRasterBand( poDSIn, nBandIn, fpRawIn, nImgOffsetIn, nPixelOffsetIn,
                 nLineOffsetIn, eDataTypeIn, bNativeOrderIn, TRUE ),
  nBits(nBitsIn),
  nStartBit(0),
  nPixelOffsetBits(0),
  nLineOffsetBits(0),
  bNoDataSet(FALSE),
  dfNoData(0.0),
  dfMin(0.0),
  dfMax(0.0),
  dfMean(0.0),
  dfStdDev(0.0),
  minmaxmeanstddev(0)
{
    EHdrDataset* poEDS = reinterpret_cast<EHdrDataset *>( poDS );

    if (nBits < 8)
    {
        int nSkipBytes = atoi(poEDS->GetKeyValue("SKIPBYTES"));
        if( nSkipBytes < 0 || nSkipBytes > INT_MAX / 8 )
        {
            CPLError( CE_Failure, CPLE_AppDefined,
                      "Invalid SKIPBYTES: %d", nSkipBytes);
            nStartBit = 0;
        }
        else
        {
            nStartBit = nSkipBytes * 8;
        }
        if (nBand >= 2)
        {
            GIntBig nBandRowBytes =
                CPLAtoGIntBig(poEDS->GetKeyValue("BANDROWBYTES"));
            if( nBandRowBytes < 0 )
            {
                CPLError( CE_Failure, CPLE_AppDefined,
                          "Invalid BANDROWBYTES: " CPL_FRMT_GIB,
                          nBandRowBytes );
                nBandRowBytes = 0;
            }
            vsi_l_offset nRowBytes = 0;
            if (nBandRowBytes == 0)
                nRowBytes =
                    (static_cast<vsi_l_offset>(nBits) *
                     poDS->GetRasterXSize() + 7) / 8;
            else
                nRowBytes = static_cast<vsi_l_offset>(nBandRowBytes);

            nStartBit += nRowBytes * (nBand-1) * 8;
        }

        nPixelOffsetBits = nBits;
        GIntBig nTotalRowBytes =
            CPLAtoGIntBig(poEDS->GetKeyValue("TOTALROWBYTES"));
        if( nTotalRowBytes < 0 )
        {
            CPLError( CE_Failure, CPLE_AppDefined,
                      "Invalid TOTALROWBYTES: " CPL_FRMT_GIB, nTotalRowBytes);
            nTotalRowBytes = 0;
        }
        if( nTotalRowBytes > 0 )
            nLineOffsetBits = static_cast<vsi_l_offset>(nTotalRowBytes * 8);
        else
            nLineOffsetBits =
                static_cast<vsi_l_offset>(nPixelOffsetBits) *
                poDS->GetRasterXSize();

        nBlockXSize = poDS->GetRasterXSize();
        nBlockYSize = 1;

        SetMetadataItem( "NBITS",
                         CPLString().Printf( "%d", nBits ),
                         "IMAGE_STRUCTURE" );
    }

    if( eDataType == GDT_Byte
        && EQUAL(poEDS->GetKeyValue("PIXELTYPE",""),"SIGNEDINT") )
        SetMetadataItem( "PIXELTYPE", "SIGNEDBYTE",
                         "IMAGE_STRUCTURE" );
}

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

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

{
    if (nBits >= 8)
      return RawRasterBand::IReadBlock(nBlockXOff, nBlockYOff, pImage);

/* -------------------------------------------------------------------- */
/*      Establish desired position.                                     */
/* -------------------------------------------------------------------- */
    const vsi_l_offset nLineBytesBig =
        (static_cast<vsi_l_offset>(nPixelOffsetBits)*nBlockXSize + 7) / 8;
    if( nLineBytesBig > INT_MAX )
        return CE_Failure;
    const unsigned int nLineBytes = static_cast<unsigned int>(nLineBytesBig);
    const vsi_l_offset nLineStart =
        (nStartBit + nLineOffsetBits * nBlockYOff) / 8;
    int iBitOffset = static_cast<int>(
        (nStartBit + nLineOffsetBits * nBlockYOff) % 8);

/* -------------------------------------------------------------------- */
/*      Read data into buffer.                                          */
/* -------------------------------------------------------------------- */
    GByte *pabyBuffer =
        static_cast<GByte *>( VSI_MALLOC_VERBOSE(nLineBytes) );
    if( pabyBuffer == NULL )
        return CE_Failure;

    if( VSIFSeekL( GetFPL(), nLineStart, SEEK_SET ) != 0
        || VSIFReadL( pabyBuffer, 1, nLineBytes, GetFPL() ) != nLineBytes )
    {
        CPLError( CE_Failure, CPLE_FileIO,
                  "Failed to read %u bytes at offset %lu.\n%s",
                  nLineBytes, static_cast<unsigned long>(nLineStart),
                  VSIStrerror( errno ) );
        CPLFree( pabyBuffer );
        return CE_Failure;
    }

/* -------------------------------------------------------------------- */
/*      Copy data, promoting to 8bit.                                   */
/* -------------------------------------------------------------------- */
    for( int iX = 0, iPixel = 0; iX < nBlockXSize; iX++ )
    {
        int nOutWord = 0;

        for( int iBit = 0; iBit < nBits; iBit++ )
        {
            if( pabyBuffer[iBitOffset>>3]  & (0x80 >>(iBitOffset & 7)) )
                nOutWord |= (1 << (nBits - 1 - iBit));
            iBitOffset++;
        }

        iBitOffset = iBitOffset + nPixelOffsetBits - nBits;

        reinterpret_cast<GByte *>( pImage )[iPixel++]
            = static_cast<GByte>( nOutWord );
    }

    CPLFree( pabyBuffer );

    return CE_None;
}

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

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

{
    if (nBits >= 8)
      return RawRasterBand::IWriteBlock(nBlockXOff, nBlockYOff, pImage);

/* -------------------------------------------------------------------- */
/*      Establish desired position.                                     */
/* -------------------------------------------------------------------- */
    const vsi_l_offset nLineBytesBig =
        (static_cast<vsi_l_offset>(nPixelOffsetBits)*nBlockXSize + 7) / 8;
    if( nLineBytesBig > INT_MAX )
        return CE_Failure;
    const unsigned int nLineBytes = static_cast<unsigned int>(nLineBytesBig);
    const vsi_l_offset nLineStart =
        (nStartBit + nLineOffsetBits * nBlockYOff) / 8;
    int iBitOffset = static_cast<int>(
        (nStartBit + nLineOffsetBits * nBlockYOff) % 8 );

/* -------------------------------------------------------------------- */
/*      Read data into buffer.                                          */
/* -------------------------------------------------------------------- */
    GByte *pabyBuffer =
        static_cast<GByte *>( VSI_CALLOC_VERBOSE(nLineBytes, 1) );
    if( pabyBuffer == NULL )
        return CE_Failure;

    if( VSIFSeekL( GetFPL(), nLineStart, SEEK_SET ) != 0 )
    {
        CPLError( CE_Failure, CPLE_FileIO,
                  "Failed to read %u bytes at offset %lu.\n%s",
                  nLineBytes, static_cast<unsigned long>(nLineStart),
                  VSIStrerror( errno ) );
        CPLFree( pabyBuffer );
        return CE_Failure;
    }

    CPL_IGNORE_RET_VAL(VSIFReadL( pabyBuffer, nLineBytes, 1, GetFPL() ));

/* -------------------------------------------------------------------- */
/*      Copy data, promoting to 8bit.                                   */
/* -------------------------------------------------------------------- */
    for( int iX = 0, iPixel = 0; iX < nBlockXSize; iX++ )
    {
        const int nOutWord = reinterpret_cast<GByte *>( pImage )[iPixel++];

        for( int iBit = 0; iBit < nBits; iBit++ )
        {
            if( nOutWord & (1 << (nBits - 1 - iBit)) )
                pabyBuffer[iBitOffset>>3] |= (0x80 >>(iBitOffset & 7));
            else
                pabyBuffer[iBitOffset>>3] &= ~((0x80 >>(iBitOffset & 7)));

            iBitOffset++;
        }

        iBitOffset = iBitOffset + nPixelOffsetBits - nBits;
    }

/* -------------------------------------------------------------------- */
/*      Write the data back out.                                        */
/* -------------------------------------------------------------------- */
    if( VSIFSeekL( GetFPL(), nLineStart, SEEK_SET ) != 0
        || VSIFWriteL( pabyBuffer, 1, nLineBytes, GetFPL() ) != nLineBytes )
    {
        CPLError( CE_Failure, CPLE_FileIO,
                  "Failed to write %u bytes at offset %lu.\n%s",
                  nLineBytes, static_cast<unsigned long>(nLineStart),
                  VSIStrerror( errno ) );
        return CE_Failure;
    }

    CPLFree( pabyBuffer );

    return CE_None;
}

/************************************************************************/
/*                             IRasterIO()                              */
/************************************************************************/

CPLErr EHdrRasterBand::IRasterIO( GDALRWFlag eRWFlag,
                                  int nXOff, int nYOff, int nXSize, int nYSize,
                                  void * pData, int nBufXSize, int nBufYSize,
                                  GDALDataType eBufType,
                                  GSpacing nPixelSpace,
                                  GSpacing nLineSpace,
                                  GDALRasterIOExtraArg* psExtraArg )

{
    // Defer to RawRasterBand
    if (nBits >= 8)
        return RawRasterBand::IRasterIO( eRWFlag,
                                         nXOff, nYOff, nXSize, nYSize,
                                         pData, nBufXSize, nBufYSize,
                                         eBufType, nPixelSpace, nLineSpace,
                                         psExtraArg );

    // Force use of IReadBlock() and IWriteBlock()
    return GDALRasterBand::IRasterIO( eRWFlag,
                                      nXOff, nYOff, nXSize, nYSize,
                                      pData, nBufXSize, nBufYSize,
                                      eBufType, nPixelSpace, nLineSpace,
                                      psExtraArg );
}

/************************************************************************/
/*                              OSR_GDS()                               */
/************************************************************************/

static const char*OSR_GDS( char* pszResult, int nResultLen,
                           char **papszNV, const char * pszField,
                           const char *pszDefaultValue )

{
    if( papszNV == NULL || papszNV[0] == NULL )
        return pszDefaultValue;

    int iLine = 0;  // Used after for.
    for( ;
         papszNV[iLine] != NULL &&
             !EQUALN(papszNV[iLine],pszField,strlen(pszField));
         iLine++ ) {}

    if( papszNV[iLine] == NULL )
        return pszDefaultValue;

    char **papszTokens = CSLTokenizeString(papszNV[iLine]);

    if( CSLCount(papszTokens) > 1 )
        strncpy( pszResult, papszTokens[1], nResultLen);
    else
        strncpy( pszResult, pszDefaultValue, nResultLen);
    pszResult[nResultLen-1] = '\0';

    CSLDestroy( papszTokens );
    return pszResult;
}


/************************************************************************/
/* ==================================================================== */
/*                            EHdrDataset                               */
/* ==================================================================== */
/************************************************************************/

/************************************************************************/
/*                            EHdrDataset()                             */
/************************************************************************/

EHdrDataset::EHdrDataset() :
    fpImage(NULL),
    osHeaderExt("hdr"),
    bGotTransform(FALSE),
    pszProjection(CPLStrdup("")),
    bHDRDirty(FALSE),
    papszHDR(NULL),
    bCLRDirty(FALSE)
{
    adfGeoTransform[0] = 0.0;
    adfGeoTransform[1] = 1.0;
    adfGeoTransform[2] = 0.0;
    adfGeoTransform[3] = 0.0;
    adfGeoTransform[4] = 0.0;
    adfGeoTransform[5] = 1.0;
}

/************************************************************************/
/*                            ~EHdrDataset()                            */
/************************************************************************/

EHdrDataset::~EHdrDataset()

{
    FlushCache();

    if( nBands > 0 && GetAccess() == GA_Update )
    {
        int bNoDataSet;
        RawRasterBand *poBand
            = reinterpret_cast<RawRasterBand *>( GetRasterBand( 1 ) );

        const double dfNoData
            = poBand->GetNoDataValue(&bNoDataSet);
        if( bNoDataSet )
        {
            ResetKeyValue( "NODATA",
                           CPLString().Printf( "%.8g", dfNoData ) );
        }

        if( bCLRDirty )
            RewriteColorTable( poBand->GetColorTable() );

        if( bHDRDirty )
            RewriteHDR();
    }

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

    CPLFree( pszProjection );
    CSLDestroy( papszHDR );
}

/************************************************************************/
/*                            GetKeyValue()                             */
/************************************************************************/

const char *EHdrDataset::GetKeyValue( const char *pszKey,
                                      const char *pszDefault )

{
    for( int i = 0; papszHDR[i] != NULL; i++ )
    {
        if( EQUALN(pszKey,papszHDR[i],strlen(pszKey))
            && isspace((unsigned char)papszHDR[i][strlen(pszKey)]) )
        {
            const char *pszValue = papszHDR[i] + strlen(pszKey);
            while( isspace((unsigned char)*pszValue) )
                pszValue++;

            return pszValue;
        }
    }

    return pszDefault;
}

/************************************************************************/
/*                           ResetKeyValue()                            */
/*                                                                      */
/*      Replace or add the keyword with the indicated value in the      */
/*      papszHDR list.                                                  */
/************************************************************************/

void EHdrDataset::ResetKeyValue( const char *pszKey, const char *pszValue )

{
    if( strlen(pszValue) > 65 )
    {
        CPLAssert( strlen(pszValue) <= 65 );
        return;
    }

  char szNewLine[82] = { '\0' };
    snprintf( szNewLine, sizeof(szNewLine), "%-15s%s", pszKey, pszValue );

    for( int i = CSLCount(papszHDR)-1; i >= 0; i-- )
    {
        if( EQUALN(papszHDR[i], szNewLine, strlen(pszKey) + 1 ) )
        {
            if( strcmp(papszHDR[i],szNewLine) != 0 )
            {
                CPLFree( papszHDR[i] );
                papszHDR[i] = CPLStrdup( szNewLine );
                bHDRDirty = TRUE;
            }
            return;
        }
    }

    bHDRDirty = TRUE;
    papszHDR = CSLAddString( papszHDR, szNewLine );
}

/************************************************************************/
/*                         RewriteColorTable()                          */
/************************************************************************/

void EHdrDataset::RewriteColorTable( GDALColorTable *poTable )

{
    CPLString osCLRFilename = CPLResetExtension( GetDescription(), "clr" );
    if( poTable )
    {
        VSILFILE *fp = VSIFOpenL( osCLRFilename, "wt" );
        if( fp != NULL )
        {
            for( int iColor = 0;
                 iColor < poTable->GetColorEntryCount();
                 iColor++ )
            {
                GDALColorEntry sEntry;
                poTable->GetColorEntryAsRGB( iColor, &sEntry );

                // I wish we had a way to mark transparency.
                CPLString oLine;
                oLine.Printf( "%3d %3d %3d %3d\n",
                              iColor, sEntry.c1, sEntry.c2, sEntry.c3 );
                if( VSIFWriteL(
                    reinterpret_cast<void *>(
                        const_cast<char *>( oLine.c_str() ) ),
                    strlen(oLine), 1, fp ) != 1 )
                {
                    CPLError( CE_Failure, CPLE_FileIO,
                              "Error while write color table");
                    CPL_IGNORE_RET_VAL(VSIFCloseL( fp ));
                    return;
                }
            }
            if( VSIFCloseL( fp ) != 0 )
            {
                CPLError(CE_Failure, CPLE_FileIO, "Error while write color table");
            }
        }
        else
        {
            CPLError( CE_Failure, CPLE_OpenFailed,
                      "Unable to create color file %s.",
                      osCLRFilename.c_str() );
        }
    }
    else
        VSIUnlink( osCLRFilename );
}

/************************************************************************/
/*                          GetProjectionRef()                          */
/************************************************************************/

const char *EHdrDataset::GetProjectionRef()

{
    if (pszProjection && strlen(pszProjection) > 0)
        return pszProjection;

    return GDALPamDataset::GetProjectionRef();
}

/************************************************************************/
/*                           SetProjection()                            */
/************************************************************************/

CPLErr EHdrDataset::SetProjection( const char *pszSRS )

{
/* -------------------------------------------------------------------- */
/*      Reset coordinate system on the dataset.                         */
/* -------------------------------------------------------------------- */
    CPLFree( pszProjection );
    pszProjection = CPLStrdup( pszSRS );

    if( strlen(pszSRS) == 0 )
        return CE_None;

/* -------------------------------------------------------------------- */
/*      Convert to ESRI WKT.                                            */
/* -------------------------------------------------------------------- */
    OGRSpatialReference oSRS( pszSRS );
    oSRS.morphToESRI();

    char *pszESRI_SRS = NULL;
    oSRS.exportToWkt( &pszESRI_SRS );

/* -------------------------------------------------------------------- */
/*      Write to .prj file.                                             */
/* -------------------------------------------------------------------- */
    CPLString osPrjFilename = CPLResetExtension( GetDescription(), "prj" );
    VSILFILE *fp = VSIFOpenL( osPrjFilename.c_str(), "wt" );
    if( fp != NULL )
    {
        size_t nCount = VSIFWriteL( pszESRI_SRS, strlen(pszESRI_SRS), 1, fp );
        nCount += VSIFWriteL(
            reinterpret_cast<void *>( const_cast<char *>( "\n" ) ),
            1, 1, fp );
        if( VSIFCloseL( fp ) != 0 ||
            nCount != 2 )
        {
            CPLFree( pszESRI_SRS );
            return CE_Failure;
        }
    }

    CPLFree( pszESRI_SRS );

    return CE_None;
}

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

CPLErr EHdrDataset::GetGeoTransform( double * padfTransform )

{
    if( bGotTransform )
    {
        memcpy( padfTransform, adfGeoTransform, sizeof(double) * 6 );
        return CE_None;
    }

    return GDALPamDataset::GetGeoTransform( padfTransform );
}

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

CPLErr EHdrDataset::SetGeoTransform( double *padfGeoTransform )

{
/* -------------------------------------------------------------------- */
/*      We only support non-rotated images with info in the .HDR file.  */
/* -------------------------------------------------------------------- */
    if( padfGeoTransform[2] != 0.0 || padfGeoTransform[4] != 0.0 )
    {
        return GDALPamDataset::SetGeoTransform( padfGeoTransform );
    }

/* -------------------------------------------------------------------- */
/*      Record new geotransform.                                        */
/* -------------------------------------------------------------------- */
    bGotTransform = TRUE;
    memcpy( adfGeoTransform, padfGeoTransform, sizeof(double) * 6 );

/* -------------------------------------------------------------------- */
/*      Strip out all old geotransform keywords from HDR records.       */
/* -------------------------------------------------------------------- */
    for( int i = CSLCount(papszHDR)-1; i >= 0; i-- )
    {
        if( STARTS_WITH_CI(papszHDR[i], "ul")
            || STARTS_WITH_CI(papszHDR[i]+1, "ll")
            || STARTS_WITH_CI(papszHDR[i], "cell")
            || STARTS_WITH_CI(papszHDR[i]+1, "dim") )
        {
            papszHDR = CSLRemoveStrings( papszHDR, i, 1, NULL );
        }
    }

/* -------------------------------------------------------------------- */
/*      Set the transformation information.                             */
/* -------------------------------------------------------------------- */
    CPLString  oValue;

    oValue.Printf( "%.15g", adfGeoTransform[0] + adfGeoTransform[1] * 0.5 );
    ResetKeyValue( "ULXMAP", oValue );

    oValue.Printf( "%.15g", adfGeoTransform[3] + adfGeoTransform[5] * 0.5 );
    ResetKeyValue( "ULYMAP", oValue );

    oValue.Printf( "%.15g", adfGeoTransform[1] );
    ResetKeyValue( "XDIM", oValue );

    oValue.Printf( "%.15g", fabs(adfGeoTransform[5]) );
    ResetKeyValue( "YDIM", oValue );

    return CE_None;
}

/************************************************************************/
/*                             RewriteHDR()                             */
/************************************************************************/

CPLErr EHdrDataset::RewriteHDR()

{
    const CPLString osPath = CPLGetPath( GetDescription() );
    const CPLString osName = CPLGetBasename( GetDescription() );
    const CPLString osHDRFilename = CPLFormCIFilename( osPath, osName, osHeaderExt );

/* -------------------------------------------------------------------- */
/*      Write .hdr file.                                                */
/* -------------------------------------------------------------------- */
    VSILFILE *fp = VSIFOpenL( osHDRFilename, "wt" );

    if( fp == NULL )
    {
        CPLError( CE_Failure, CPLE_OpenFailed,
                  "Failed to rewrite .hdr file %s.",
                  osHDRFilename.c_str() );
        return CE_Failure;
    }

    for( int i = 0; papszHDR[i] != NULL; i++ )
    {
        size_t nCount = VSIFWriteL( papszHDR[i], strlen(papszHDR[i]), 1, fp );
        nCount += VSIFWriteL(
            reinterpret_cast<void *>( const_cast<char *>( "\n" ) ),
            1, 1, fp );
        if( nCount != 2 )
        {
            CPL_IGNORE_RET_VAL(VSIFCloseL( fp ));
            return CE_Failure;
        }
    }

    bHDRDirty = FALSE;

    if( VSIFCloseL( fp ) != 0 )
        return CE_Failure;

    return CE_None;
}

/************************************************************************/
/*                             RewriteSTX()                             */
/************************************************************************/

CPLErr EHdrDataset::RewriteSTX()
{
    const CPLString osPath = CPLGetPath( GetDescription() );
    const CPLString osName = CPLGetBasename( GetDescription() );
    const CPLString osSTXFilename = CPLFormCIFilename( osPath, osName, "stx" );

/* -------------------------------------------------------------------- */
/*      Write .stx file.                                                */
/* -------------------------------------------------------------------- */
    VSILFILE *fp = VSIFOpenL( osSTXFilename, "wt" );
    if( fp == NULL )
    {
        CPLDebug( "EHDR", "Failed to rewrite .stx file %s.",
                  osSTXFilename.c_str() );
        return CE_Failure;
    }

    bool bOK = true;
    for ( int i = 0; bOK && i < nBands; ++i )
    {
        EHdrRasterBand* poBand
            = reinterpret_cast<EHdrRasterBand *>( papoBands[i] );
        bOK &= VSIFPrintfL( fp, "%d %.10f %.10f ", i+1,
                            poBand->dfMin, poBand->dfMax ) >= 0;
        if ( poBand->minmaxmeanstddev & HAS_MEAN_FLAG )
            bOK &= VSIFPrintfL( fp, "%.10f ", poBand->dfMean) >= 0;
        else
            bOK &= VSIFPrintfL( fp, "# ") >= 0;

        if ( poBand->minmaxmeanstddev & HAS_STDDEV_FLAG )
            bOK &= VSIFPrintfL( fp, "%.10f\n", poBand->dfStdDev) >= 0;
        else
            bOK &= VSIFPrintfL( fp, "#\n") >= 0;
    }

    if( VSIFCloseL( fp ) != 0 )
        bOK = false;

    return bOK ? CE_None : CE_Failure;
}

/************************************************************************/
/*                              ReadSTX()                               */
/************************************************************************/

CPLErr EHdrDataset::ReadSTX()
{
    const CPLString osPath = CPLGetPath( GetDescription() );
    const CPLString osName = CPLGetBasename( GetDescription() );
    const CPLString osSTXFilename = CPLFormCIFilename( osPath, osName, "stx" );

/* -------------------------------------------------------------------- */
/*      Read .stx file.                                                 */
/* -------------------------------------------------------------------- */
    VSILFILE *fp = VSIFOpenL( osSTXFilename, "rt" );
    if (fp == NULL)
        return CE_None;

    const char *pszLine = NULL;
    while( (pszLine = CPLReadLineL( fp )) != NULL )
    {
        char **papszTokens =
            CSLTokenizeStringComplex( pszLine, " \t", TRUE, FALSE );
        const int nTokens = CSLCount( papszTokens );
        if( nTokens >= 5 )
        {
            const int i = atoi(papszTokens[0]);
            if (i > 0 && i <= nBands)
            {
              EHdrRasterBand* poBand
                  = reinterpret_cast<EHdrRasterBand *>( papoBands[i-1] );
              poBand->dfMin = CPLAtof(papszTokens[1]);
              poBand->dfMax = CPLAtof(papszTokens[2]);

              int bNoDataSet = FALSE;
              const double dfNoData = poBand->GetNoDataValue(&bNoDataSet);
              if (bNoDataSet && dfNoData == poBand->dfMin)
              {
                  // Triggered by
                  // /vsicurl/http://eros.usgs.gov/archive/nslrsda/GeoTowns/HongKong/srtm/n22e113.zip/n22e113.bil
                  CPLDebug( "EHDr", "Ignoring .stx file where min == nodata. "
                            "The nodata value should not be taken into account "
                            "in minimum value computation.");
                  CSLDestroy( papszTokens );
                  papszTokens = NULL;
                  break;
              }

              poBand->minmaxmeanstddev = HAS_MIN_FLAG | HAS_MAX_FLAG;
              // Reads optional mean and stddev.
              if ( !EQUAL(papszTokens[3], "#") )
              {
                poBand->dfMean   = CPLAtof(papszTokens[3]);
                poBand->minmaxmeanstddev |= HAS_MEAN_FLAG;
              }
              if ( !EQUAL(papszTokens[4], "#") )
              {
                poBand->dfStdDev = CPLAtof(papszTokens[4]);
                poBand->minmaxmeanstddev |= HAS_STDDEV_FLAG;
              }

              if( nTokens >= 6 && !EQUAL(papszTokens[5], "#") )
                poBand->SetMetadataItem(
                  "STRETCHMIN", papszTokens[5], "RENDERING_HINTS" );

              if( nTokens >= 7 && !EQUAL(papszTokens[6], "#") )
                poBand->SetMetadataItem(
                  "STRETCHMAX", papszTokens[6], "RENDERING_HINTS" );
            }
          }

          CSLDestroy( papszTokens );
    }

    CPL_IGNORE_RET_VAL(VSIFCloseL( fp ));

    return CE_None;
}


/************************************************************************/
/*                      GetImageRepFilename()                           */
/************************************************************************/

/* -------------------------------------------------------------------- */
/*  Check for IMAGE.REP (Spatiocarte Defense 1.0) or name_of_image.rep  */
/*  if it's a GIS-GeoSPOT image                                         */
/*  For the specification of SPDF (in French),                          */
/*   see http://eden.ign.fr/download/pub/doc/emabgi/spdf10.pdf/download */
/* -------------------------------------------------------------------- */

CPLString EHdrDataset::GetImageRepFilename(const char* pszFilename)
{

    const CPLString osPath = CPLGetPath( pszFilename );
    const CPLString osName = CPLGetBasename( pszFilename );
    const CPLString osREPFilename =
        CPLFormCIFilename( osPath, osName, "rep" );

    VSIStatBufL sStatBuf;
    if( VSIStatExL(
            osREPFilename.c_str(), &sStatBuf, VSI_STAT_EXISTS_FLAG ) == 0 )
        return osREPFilename;

    if (EQUAL(CPLGetFilename(pszFilename), "imspatio.bil") ||
        EQUAL(CPLGetFilename(pszFilename), "haspatio.bil"))
    {
        CPLString osImageRepFilename(CPLFormCIFilename( osPath, "image", "rep" ));
        if( VSIStatExL(
                osImageRepFilename.c_str(), &sStatBuf, VSI_STAT_EXISTS_FLAG ) == 0 )
            return osImageRepFilename;

        /* Try in the upper directories if not found in the BIL image directory */
        CPLString dirName(CPLGetDirname(osPath));
        if (CPLIsFilenameRelative(osPath.c_str()))
        {
            char* cwd = CPLGetCurrentDir();
            if (cwd)
            {
                dirName = CPLFormFilename(cwd, dirName.c_str(), NULL);
                CPLFree(cwd);
            }
        }
        while( dirName[0] != 0 && EQUAL(dirName, ".") == FALSE &&
               EQUAL(dirName, "/") == FALSE )
        {
            osImageRepFilename =
                CPLFormCIFilename( dirName.c_str(), "image", "rep" );
            if( VSIStatExL(
                    osImageRepFilename.c_str(), &sStatBuf,
                    VSI_STAT_EXISTS_FLAG ) == 0 )
                return osImageRepFilename;

            /* Don't try to recurse above the 'image' subdirectory */
            if( EQUAL(dirName, "image") )
            {
                break;
            }
            dirName = CPLString(CPLGetDirname(dirName));
        }
    }
    return CPLString();
}

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

char **EHdrDataset::GetFileList()

{
    const CPLString osPath = CPLGetPath( GetDescription() );
    const CPLString osName = CPLGetBasename( GetDescription() );

    // Main data file, etc.
    char **papszFileList = GDALPamDataset::GetFileList();

    // Header file.
    CPLString osFilename = CPLFormCIFilename( osPath, osName, osHeaderExt );
    papszFileList = CSLAddString( papszFileList, osFilename );

    // Statistics file
    osFilename = CPLFormCIFilename( osPath, osName, "stx" );
    VSIStatBufL sStatBuf;
    if( VSIStatExL( osFilename, &sStatBuf, VSI_STAT_EXISTS_FLAG ) == 0 )
        papszFileList = CSLAddString( papszFileList, osFilename );

    // color table file.
    osFilename = CPLFormCIFilename( osPath, osName, "clr" );
    if( VSIStatExL( osFilename, &sStatBuf, VSI_STAT_EXISTS_FLAG ) == 0 )
        papszFileList = CSLAddString( papszFileList, osFilename );

    // projections file.
    osFilename = CPLFormCIFilename( osPath, osName, "prj" );
    if( VSIStatExL( osFilename, &sStatBuf, VSI_STAT_EXISTS_FLAG ) == 0 )
        papszFileList = CSLAddString( papszFileList, osFilename );

    const CPLString imageRepFilename = GetImageRepFilename( GetDescription() );
    if (!imageRepFilename.empty())
        papszFileList = CSLAddString( papszFileList, imageRepFilename.c_str() );

    return papszFileList;
}

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

GDALDataset *EHdrDataset::Open( GDALOpenInfo * poOpenInfo )

{
/* -------------------------------------------------------------------- */
/*      We assume the user is pointing to the binary (i.e. .bil) file.  */
/* -------------------------------------------------------------------- */
    if( poOpenInfo->nHeaderBytes < 2 )
        return NULL;

/* -------------------------------------------------------------------- */
/*      Now we need to tear apart the filename to form a .HDR           */
/*      filename.                                                       */
/* -------------------------------------------------------------------- */
    const CPLString osPath = CPLGetPath( poOpenInfo->pszFilename );
    const CPLString osName = CPLGetBasename( poOpenInfo->pszFilename );

    const char* pszHeaderExt = "hdr";
    if( EQUAL( CPLGetExtension( poOpenInfo->pszFilename ), "SRC" ) &&
        osName.size() == 7 &&
        (osName[0] == 'e' || osName[0] == 'E' ||
         osName[0] == 'w' || osName[0] == 'W') &&
        (osName[4] == 'n' || osName[4] == 'N' ||
         osName[4] == 's' || osName[4] == 'S') )
    {
        // It is a GTOPO30 or SRTM30 source file, whose header extension is .sch
        // see http://dds.cr.usgs.gov/srtm/version1/SRTM30/GTOPO30_Documentation
        pszHeaderExt = "sch";
    }

    char** papszSiblingFiles = poOpenInfo->GetSiblingFiles();
    CPLString osHDRFilename;
    if( papszSiblingFiles )
    {
        const int iFile =
            CSLFindString( papszSiblingFiles,
                           CPLFormFilename( NULL, osName, pszHeaderExt ) );
        if( iFile < 0 )  // Return if there is no corresponding .hdr file.
            return NULL;

        osHDRFilename =
            CPLFormFilename( osPath, papszSiblingFiles[iFile],
                             NULL );
    }
    else
    {
        osHDRFilename = CPLFormCIFilename( osPath, osName, pszHeaderExt );
    }

    const bool bSelectedHDR = EQUAL( osHDRFilename, poOpenInfo->pszFilename );

/* -------------------------------------------------------------------- */
/*      Do we have a .hdr file?                                         */
/* -------------------------------------------------------------------- */
    VSILFILE *fp = VSIFOpenL( osHDRFilename, "r" );
    if( fp == NULL )
    {
        return NULL;
    }

/* -------------------------------------------------------------------- */
/*      Is this file an ESRI header file?  Read a few lines of text     */
/*      searching for something starting with nrows or ncols.           */
/* -------------------------------------------------------------------- */
    int nRows = -1;
    int nCols = -1;
    int nBands = 1;
    int nSkipBytes = 0;
    double dfULXMap = 0.5;
    double dfULYMap = 0.5;
    double dfYLLCorner = -123.456;
    int bCenter = TRUE;
    double dfXDim = 1.0;
    double dfYDim = 1.0;
    double dfNoData = 0.0;
    int nLineCount = 0;
    int bNoDataSet = FALSE;
    GDALDataType eDataType = GDT_Byte;
    int nBits = -1;
    char chByteOrder = 'M';
    char chPixelType = 'N'; // not defined
    char szLayout[10] = "BIL";
    char **papszHDR = NULL;
    int bHasInternalProjection = FALSE;
    int bHasMin = FALSE;
    int bHasMax = FALSE;
    double dfMin = 0;
    double dfMax = 0;

    const char *pszLine;
    while( (pszLine = CPLReadLineL( fp )) != NULL )
    {
        nLineCount++;

        if( nLineCount > 50 || strlen(pszLine) > 1000 )
            break;

        papszHDR = CSLAddString( papszHDR, pszLine );

        char **papszTokens
            = CSLTokenizeStringComplex( pszLine, " \t", TRUE, FALSE );
        if( CSLCount( papszTokens ) < 2 )
        {
            CSLDestroy( papszTokens );
            continue;
        }

        if( EQUAL(papszTokens[0],"ncols") )
        {
            nCols = atoi(papszTokens[1]);
        }
        else if( EQUAL(papszTokens[0],"nrows") )
        {
            nRows = atoi(papszTokens[1]);
        }
        else if( EQUAL(papszTokens[0],"skipbytes") )
        {
            nSkipBytes = atoi(papszTokens[1]);
        }
        else if( EQUAL(papszTokens[0],"ulxmap")
                 || EQUAL(papszTokens[0],"xllcorner")
                 || EQUAL(papszTokens[0],"xllcenter") )
        {
            dfULXMap = CPLAtofM(papszTokens[1]);
            if( EQUAL(papszTokens[0],"xllcorner") )
                bCenter = FALSE;
        }
        else if( EQUAL(papszTokens[0],"ulymap") )
        {
            dfULYMap = CPLAtofM(papszTokens[1]);
        }
        else if( EQUAL(papszTokens[0],"yllcorner")
                 || EQUAL(papszTokens[0],"yllcenter") )
        {
            dfYLLCorner = CPLAtofM(papszTokens[1]);
            if( EQUAL(papszTokens[0],"yllcorner") )
                bCenter = FALSE;
        }
        else if( EQUAL(papszTokens[0],"xdim") )
        {
            dfXDim = CPLAtofM(papszTokens[1]);
        }
        else if( EQUAL(papszTokens[0],"ydim") )
        {
            dfYDim = CPLAtofM(papszTokens[1]);
        }
        else if( EQUAL(papszTokens[0],"cellsize") )
        {
            dfXDim = dfYDim = CPLAtofM(papszTokens[1]);
        }
        else if( EQUAL(papszTokens[0],"nbands") )
        {
            nBands = atoi(papszTokens[1]);
        }
        else if( EQUAL(papszTokens[0],"layout") )
        {
            strncpy( szLayout, papszTokens[1], sizeof(szLayout) );
            szLayout[sizeof(szLayout)-1] = '\0';
        }
        else if( EQUAL(papszTokens[0],"NODATA_value")
                 || EQUAL(papszTokens[0],"NODATA") )
        {
            dfNoData = CPLAtofM(papszTokens[1]);
            bNoDataSet = TRUE;
        }
        else if( EQUAL(papszTokens[0],"NBITS") )
        {
            nBits = atoi(papszTokens[1]);
        }
        else if( EQUAL(papszTokens[0],"PIXELTYPE") )
        {
            chPixelType = static_cast<char>( toupper(papszTokens[1][0]) );
        }
        else if( EQUAL(papszTokens[0],"byteorder") )
        {
            chByteOrder = static_cast<char>( toupper(papszTokens[1][0]) );
        }

        /* http://www.worldclim.org/futdown.htm have the projection extensions */
        else if( EQUAL(papszTokens[0],"Projection") )
        {
            bHasInternalProjection = TRUE;
        }
        else if( EQUAL(papszTokens[0],"MinValue") ||
                 EQUAL(papszTokens[0],"MIN_VALUE") )
        {
            dfMin = CPLAtofM(papszTokens[1]);
            bHasMin = TRUE;
        }
        else if( EQUAL(papszTokens[0],"MaxValue") ||
                 EQUAL(papszTokens[0],"MAX_VALUE") )
        {
            dfMax = CPLAtofM(papszTokens[1]);
            bHasMax = TRUE;
        }

        CSLDestroy( papszTokens );
    }

    CPL_IGNORE_RET_VAL(VSIFCloseL( fp ));

/* -------------------------------------------------------------------- */
/*      Did we get the required keywords?  If not we return with        */
/*      this never having been considered to be a match. This isn't     */
/*      an error!                                                       */
/* -------------------------------------------------------------------- */
    if( nRows == -1 || nCols == -1 )
    {
        CSLDestroy( papszHDR );
        return NULL;
    }

    if (!GDALCheckDatasetDimensions(nCols, nRows) ||
        !GDALCheckBandCount(nBands, FALSE))
    {
        CSLDestroy( papszHDR );
        return NULL;
    }

/* -------------------------------------------------------------------- */
/*      Has the user selected the .hdr file to open?                    */
/* -------------------------------------------------------------------- */
    if( bSelectedHDR )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "The selected file is an ESRI BIL header file, but to "
                  "open ESRI BIL datasets, the data file should be selected "
                  "instead of the .hdr file.  Please try again selecting "
                  "the data file (often with the extension .bil) corresponding "
                  "to the header file: %s",
                  poOpenInfo->pszFilename );
        CSLDestroy( papszHDR );
        return NULL;
    }

/* -------------------------------------------------------------------- */
/*      If we aren't sure of the file type, check the data file         */
/*      size.  If it is 4 bytes or more per pixel then we assume it     */
/*      is floating point data.                                         */
/* -------------------------------------------------------------------- */
    if( nBits == -1 && chPixelType == 'N' )
    {
        VSIStatBufL sStatBuf;
        if( VSIStatL( poOpenInfo->pszFilename, &sStatBuf ) == 0 )
        {
            const size_t nBytes
                = static_cast<size_t>( sStatBuf.st_size/nCols/nRows/nBands );
            if( nBytes > 0 && nBytes != 3 )
                nBits = static_cast<int>(nBytes * 8);

            if( nBytes == 4 )
                chPixelType = 'F';
        }
    }

/* -------------------------------------------------------------------- */
/*      If the extension is FLT it is likely a floating point file.     */
/* -------------------------------------------------------------------- */
    if( chPixelType == 'N' )
    {
        if( EQUAL( CPLGetExtension( poOpenInfo->pszFilename ), "FLT" ) )
            chPixelType = 'F';
    }

/* -------------------------------------------------------------------- */
/*      If we have a negative nodata value, let's assume that the       */
/*      pixel type is signed. This is necessary for datasets from       */
/*      http://www.worldclim.org/futdown.htm                            */
/* -------------------------------------------------------------------- */
    if( bNoDataSet && dfNoData < 0 && chPixelType == 'N' )
    {
        chPixelType = 'S';
    }

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

    poDS->osHeaderExt = pszHeaderExt;

/* -------------------------------------------------------------------- */
/*      Capture some information from the file that is of interest.     */
/* -------------------------------------------------------------------- */
    poDS->nRasterXSize = nCols;
    poDS->nRasterYSize = nRows;
    poDS->papszHDR = papszHDR;

/* -------------------------------------------------------------------- */
/*      Open target binary file.                                        */
/* -------------------------------------------------------------------- */
    if( poOpenInfo->eAccess == GA_ReadOnly )
        poDS->fpImage = VSIFOpenL( poOpenInfo->pszFilename, "rb" );
    else
        poDS->fpImage = VSIFOpenL( poOpenInfo->pszFilename, "r+b" );

    if( poDS->fpImage == NULL )
    {
        CPLError( CE_Failure, CPLE_OpenFailed,
                  "Failed to open %s with write permission.\n%s",
                  osName.c_str(), VSIStrerror( errno ) );
        delete poDS;
        return NULL;
    }

    poDS->eAccess = poOpenInfo->eAccess;

/* -------------------------------------------------------------------- */
/*      Figure out the data type.                                       */
/* -------------------------------------------------------------------- */
    if( nBits == 16 )
    {
        if ( chPixelType == 'S' )
            eDataType = GDT_Int16;
        else
            eDataType = GDT_UInt16; // default
    }
    else if( nBits == 32 )
    {
        if( chPixelType == 'S' )
            eDataType = GDT_Int32;
        else if( chPixelType == 'F' )
            eDataType = GDT_Float32;
        else
            eDataType = GDT_UInt32; // default
    }
    else if( nBits == 8 )
    {
        eDataType = GDT_Byte;
        nBits = 8;
    }
    else if( nBits < 8 && nBits >= 1 )
        eDataType = GDT_Byte;
    else if( nBits == -1 )
    {
        if( chPixelType == 'F' )
        {
            eDataType = GDT_Float32;
            nBits = 32;
        }
        else
        {
            eDataType = GDT_Byte;
            nBits = 8;
        }
    }
    else
    {
        CPLError( CE_Failure, CPLE_NotSupported,
                  "EHdr driver does not support %d NBITS value.",
                  nBits );
        delete poDS;
        poDS = NULL;
        return NULL;
    }

/* -------------------------------------------------------------------- */
/*      Compute the line offset.                                        */
/* -------------------------------------------------------------------- */
    const int nItemSize = GDALGetDataTypeSizeBytes(eDataType);
    int nPixelOffset = 0;
    int nLineOffset = 0;
    vsi_l_offset    nBandOffset;
    CPLAssert(nItemSize != 0);
    CPLAssert(nBands != 0);

    if( EQUAL(szLayout, "BIP") )
    {
        if (nCols > INT_MAX / (nItemSize * nBands))
        {
            delete poDS;
            poDS = NULL;
            CPLError( CE_Failure, CPLE_AppDefined,
                      "Int overflow occurred.");
            return NULL;
        }
        nPixelOffset = nItemSize * nBands;
        nLineOffset = nPixelOffset * nCols;
        nBandOffset = (vsi_l_offset)nItemSize;
    }
    else if( EQUAL(szLayout,"BSQ") )
    {
        if (nCols > INT_MAX / nItemSize)
        {
            delete poDS;
            poDS = NULL;
            CPLError( CE_Failure, CPLE_AppDefined,
                      "Int overflow occurred.");
            return NULL;
        }
        nPixelOffset = nItemSize;
        nLineOffset = nPixelOffset * nCols;
        nBandOffset = (vsi_l_offset)nLineOffset * nRows;
    }
    else /* assume BIL */
    {
        if (nCols > INT_MAX / (nItemSize * nBands))
        {
            delete poDS;
            poDS = NULL;
            CPLError( CE_Failure, CPLE_AppDefined,
                      "Int overflow occurred.");
            return NULL;
        }
        nPixelOffset = nItemSize;
        nLineOffset = nItemSize * nBands * nCols;
        nBandOffset = (vsi_l_offset)nItemSize * nCols;
    }

    poDS->SetDescription( poOpenInfo->pszFilename );
    poDS->PamInitialize();

/* -------------------------------------------------------------------- */
/*      Create band information objects.                                */
/* -------------------------------------------------------------------- */
    poDS->nBands = nBands;
    CPLErrorReset();
    for( int i = 0; i < poDS->nBands; i++ )
    {
        EHdrRasterBand *poBand =
            new EHdrRasterBand( poDS, i+1, poDS->fpImage,
                                nSkipBytes + nBandOffset * i,
                                nPixelOffset, nLineOffset, eDataType,
#ifdef CPL_LSB
                                chByteOrder == 'I' || chByteOrder == 'L',
#else
                                chByteOrder == 'M',
#endif
                                nBits );

        poBand->bNoDataSet = bNoDataSet;
        poBand->dfNoData = dfNoData;

        if( bHasMin && bHasMax )
        {
            poBand->dfMin = dfMin;
            poBand->dfMax = dfMax;
            poBand->minmaxmeanstddev = HAS_MIN_FLAG | HAS_MAX_FLAG;
        }

        poDS->SetBand( i+1, poBand );
        if( CPLGetLastErrorType() != CE_None )
        {
            poDS->nBands = i+1;
            delete poDS;
            return NULL;
        }
    }

/* -------------------------------------------------------------------- */
/*      If we didn't get bounds in the .hdr, look for a worldfile.      */
/* -------------------------------------------------------------------- */
    if( dfYLLCorner != -123.456 )
    {
        if( bCenter )
            dfULYMap = dfYLLCorner + (nRows-1) * dfYDim;
        else
            dfULYMap = dfYLLCorner + nRows * dfYDim;
    }

    if( dfULXMap != 0.5 || dfULYMap != 0.5 || dfXDim != 1.0 || dfYDim != 1.0 )
    {
        poDS->bGotTransform = TRUE;

        if( bCenter )
        {
            poDS->adfGeoTransform[0] = dfULXMap - dfXDim * 0.5;
            poDS->adfGeoTransform[1] = dfXDim;
            poDS->adfGeoTransform[2] = 0.0;
            poDS->adfGeoTransform[3] = dfULYMap + dfYDim * 0.5;
            poDS->adfGeoTransform[4] = 0.0;
            poDS->adfGeoTransform[5] = - dfYDim;
        }
        else
        {
            poDS->adfGeoTransform[0] = dfULXMap;
            poDS->adfGeoTransform[1] = dfXDim;
            poDS->adfGeoTransform[2] = 0.0;
            poDS->adfGeoTransform[3] = dfULYMap;
            poDS->adfGeoTransform[4] = 0.0;
            poDS->adfGeoTransform[5] = - dfYDim;
        }
    }

    if( !poDS->bGotTransform )
        poDS->bGotTransform =
            GDALReadWorldFile( poOpenInfo->pszFilename, NULL,
                               poDS->adfGeoTransform );

    if( !poDS->bGotTransform )
        poDS->bGotTransform =
            GDALReadWorldFile( poOpenInfo->pszFilename, "wld",
                               poDS->adfGeoTransform );

/* -------------------------------------------------------------------- */
/*      Check for a .prj file.                                          */
/* -------------------------------------------------------------------- */
    const char *pszPrjFilename = CPLFormCIFilename( osPath, osName, "prj" );

    fp = VSIFOpenL( pszPrjFilename, "r" );

    // .hdr files from http://www.worldclim.org/futdown.htm have the projection
    // info in the .hdr file itself.
    if (fp == NULL && bHasInternalProjection)
    {
        pszPrjFilename = osHDRFilename;
        fp = VSIFOpenL( pszPrjFilename, "r" );
    }

    if( fp != NULL )
    {
        CPL_IGNORE_RET_VAL(VSIFCloseL( fp ));
        fp = NULL;

        char **papszLines = CSLLoad( pszPrjFilename );

        OGRSpatialReference oSRS;
        if( oSRS.importFromESRI( papszLines ) == OGRERR_NONE )
        {
            // If geographic values are in seconds, we must transform.
            // Is there a code for minutes too?
            char szResult[80] = { '\0' };
            if( oSRS.IsGeographic()
                && EQUAL(OSR_GDS( szResult, sizeof(szResult),
                                  papszLines, "Units", ""), "DS") )
            {
                poDS->adfGeoTransform[0] /= 3600.0;
                poDS->adfGeoTransform[1] /= 3600.0;
                poDS->adfGeoTransform[2] /= 3600.0;
                poDS->adfGeoTransform[3] /= 3600.0;
                poDS->adfGeoTransform[4] /= 3600.0;
                poDS->adfGeoTransform[5] /= 3600.0;
            }

            CPLFree( poDS->pszProjection );
            oSRS.exportToWkt( &(poDS->pszProjection) );
        }

        CSLDestroy( papszLines );
    }
    else
    {
/* -------------------------------------------------------------------- */
/*  Check for IMAGE.REP (Spatiocarte Defense 1.0) or name_of_image.rep  */
/*  if it's a GIS-GeoSPOT image                                         */
/*  For the specification of SPDF (in French),                          */
/*   see http://eden.ign.fr/download/pub/doc/emabgi/spdf10.pdf/download */
/* -------------------------------------------------------------------- */
        const CPLString szImageRepFilename =
            GetImageRepFilename(poOpenInfo->pszFilename );
        if (!szImageRepFilename.empty())
        {
            fp = VSIFOpenL( szImageRepFilename.c_str(), "r" );
        }
        if (fp != NULL)
        {
            bool bUTM = false;
            bool bWGS84 = false;
            int bNorth = FALSE;
            bool bSouth = false;
            int utmZone = 0;

            while( (pszLine = CPLReadLineL( fp )) != NULL )
            {
                if (STARTS_WITH(pszLine, "PROJ_ID") &&
                    strstr(pszLine, "UTM"))
                {
                    bUTM = true;
                }
                else if (STARTS_WITH(pszLine, "PROJ_ZONE"))
                {
                    const char* c = strchr(pszLine, '"');
                    if (c)
                    {
                        c++;
                        if (*c >= '0' && *c <= '9')
                        {
                            utmZone = atoi(c);
                            if (utmZone >= 1 && utmZone <= 60)
                            {
                                if( strstr(pszLine, "Nord") ||
                                    strstr(pszLine, "NORD") )
                                {
                                    bNorth = TRUE;
                                }
                                else if( strstr(pszLine, "Sud") ||
                                         strstr(pszLine, "SUD") )
                                {
                                    bSouth = true;
                                }
                            }
                        }
                    }
                }
                else if (STARTS_WITH(pszLine, "PROJ_CODE") &&
                         strstr(pszLine, "FR-MINDEF"))
                {
                    const char* c = strchr(pszLine, 'A');
                    if (c)
                    {
                        c++;
                        if (*c >= '0' && *c <= '9')
                        {
                            utmZone = atoi(c);
                            if (utmZone >= 1 && utmZone <= 60)
                            {
                                if (c[1] == 'N' ||
                                    (c[1] != '\0' && c[2] == 'N'))
                                {
                                    bNorth = TRUE;
                                }
                                else if (c[1] == 'S' ||
                                         (c[1] != '\0' && c[2] == 'S'))
                                {
                                    bSouth = true;
                                }
                            }
                        }
                    }
                }
                else if( STARTS_WITH(pszLine, "HORIZ_DATUM") &&
                         (strstr(pszLine, "WGS 84") ||
                          strstr(pszLine, "WGS84")) )
                {
                    bWGS84 = true;
                }
                else if(STARTS_WITH(pszLine, "MAP_NUMBER") )
                {
                    const char* c = strchr(pszLine, '"');
                    if (c)
                    {
                        char* pszMapNumber = CPLStrdup(c+1);
                        char* c2 = strchr(pszMapNumber, '"');
                        if (c2) *c2 = 0;
                        poDS->SetMetadataItem("SPDF_MAP_NUMBER", pszMapNumber);
                        CPLFree(pszMapNumber);
                    }
                }
                else if (STARTS_WITH(pszLine, "PRODUCTION_DATE"))
                {
                    const char* c = pszLine + strlen("PRODUCTION_DATE");
                    while(*c == ' ')
                        c++;
                    if (*c)
                    {
                        poDS->SetMetadataItem("SPDF_PRODUCTION_DATE", c );
                    }
                }
            }

            CPL_IGNORE_RET_VAL(VSIFCloseL( fp ));

            if (utmZone != 0 && bUTM && bWGS84 && (bNorth || bSouth))
            {
                char projCSStr[64] = { '\0' };
                snprintf(projCSStr, sizeof(projCSStr), "WGS 84 / UTM zone %d%c",
                        utmZone, (bNorth) ? 'N' : 'S');

                OGRSpatialReference oSRS;
                oSRS.SetProjCS(projCSStr);
                oSRS.SetWellKnownGeogCS( "WGS84" );
                oSRS.SetUTM(utmZone, bNorth);
                oSRS.SetAuthority("PROJCS", "EPSG",
                                  (bNorth ? 32600 : 32700) + utmZone);
                oSRS.AutoIdentifyEPSG();

                CPLFree( poDS->pszProjection );
                oSRS.exportToWkt( &(poDS->pszProjection) );
            }
            else
            {
                CPLError( CE_Warning, CPLE_NotSupported,
                          "Cannot retrieve projection from IMAGE.REP" );
            }
        }
    }

/* -------------------------------------------------------------------- */
/*      Check for a color table.                                        */
/* -------------------------------------------------------------------- */
    const char  *pszCLRFilename = CPLFormCIFilename( osPath, osName, "clr" );

    /* Only read the .clr for byte, int16 or uint16 bands */
    if (nItemSize <= 2)
        fp = VSIFOpenL( pszCLRFilename, "r" );
    else
        fp = NULL;

    if( fp != NULL )
    {
        GDALColorTable oColorTable;
        int bHasWarned = FALSE;

        while( true )
        {
            pszLine =  CPLReadLineL(fp);
            if ( !pszLine )
                break;

            if( *pszLine == '#' || *pszLine == '!' )
                continue;

            char **papszValues = CSLTokenizeString2(pszLine, "\t ",
                                                    CSLT_HONOURSTRINGS);


            if ( CSLCount(papszValues) >= 4 )
            {
                int nIndex = atoi( papszValues[0] ); // Index
                if (nIndex >= 0 && nIndex < 65536)
                {
                    GDALColorEntry oEntry;
                    oEntry.c1 =
                        static_cast<short>( atoi( papszValues[1] ) ); // Red
                    oEntry.c2 =
                        static_cast<short>( atoi( papszValues[2] ) ); // Green
                    oEntry.c3 =
                        static_cast<short>( atoi( papszValues[3] ) ); // Blue
                    oEntry.c4 = 255;

                    oColorTable.SetColorEntry( nIndex, &oEntry );
                }
                else
                {
                    // Negative values are valid. At least we can find use of
                    // them here: http://www.ngdc.noaa.gov/mgg/topo/elev/esri/clr/
                    // but there's no way of representing them with GDAL color
                    // table model.
                    if (!bHasWarned)
                        CPLDebug( "EHdr", "Ignoring color index : %d", nIndex );
                    bHasWarned = TRUE;
                }
            }

            CSLDestroy( papszValues );
        }

        CPL_IGNORE_RET_VAL(VSIFCloseL( fp ));

        for( int i = 1; i <= poDS->nBands; i++ )
        {
            GDALRasterBand *poBand = poDS->GetRasterBand( i );
            poBand->SetColorTable( &oColorTable );
            poBand->SetColorInterpretation( GCI_PaletteIndex );
        }

        poDS->bCLRDirty = FALSE;
    }

/* -------------------------------------------------------------------- */
/*      Read statistics (.STX)                                          */
/* -------------------------------------------------------------------- */
    poDS->ReadSTX();

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

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

    return poDS;
}

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

GDALDataset *EHdrDataset::Create( const char * pszFilename,
                                  int nXSize, int nYSize, int nBands,
                                  GDALDataType eType,
                                  char **papszParmList )

{
/* -------------------------------------------------------------------- */
/*      Verify input options.                                           */
/* -------------------------------------------------------------------- */
    if (nBands <= 0)
    {
        CPLError( CE_Failure, CPLE_NotSupported,
                  "EHdr driver does not support %d bands.\n", nBands);
        return NULL;
    }

    if( eType != GDT_Byte && eType != GDT_Float32 && eType != GDT_UInt16
        && eType != GDT_Int16 && eType != GDT_Int32 && eType != GDT_UInt32 )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "Attempt to create ESRI .hdr labelled dataset with an illegal"
                  "data type (%s).",
                  GDALGetDataTypeName(eType) );

        return NULL;
    }

/* -------------------------------------------------------------------- */
/*      Try to create the file.                                         */
/* -------------------------------------------------------------------- */
    VSILFILE *fp = VSIFOpenL( pszFilename, "wb" );

    if( fp == NULL )
    {
        CPLError( CE_Failure, CPLE_OpenFailed,
                  "Attempt to create file `%s' failed.",
                  pszFilename );
        return NULL;
    }

/* -------------------------------------------------------------------- */
/*      Just write out a couple of bytes to establish the binary        */
/*      file, and then close it.                                        */
/* -------------------------------------------------------------------- */
    bool bOK =
        VSIFWriteL( reinterpret_cast<void *>( const_cast<char *>( "\0\0" ) ),
                    2, 1, fp ) == 1;
    if( VSIFCloseL( fp ) != 0 )
        bOK = false;
    fp = NULL;
    if( !bOK )
        return NULL;

/* -------------------------------------------------------------------- */
/*      Create the hdr filename.                                        */
/* -------------------------------------------------------------------- */
    char * const pszHdrFilename
        = CPLStrdup( CPLResetExtension( pszFilename, "hdr" ) );

/* -------------------------------------------------------------------- */
/*      Open the file.                                                  */
/* -------------------------------------------------------------------- */
    fp = VSIFOpenL( pszHdrFilename, "wt" );
    if( fp == NULL )
    {
        CPLError( CE_Failure, CPLE_OpenFailed,
                  "Attempt to create file `%s' failed.\n",
                  pszHdrFilename );
        CPLFree( pszHdrFilename );
        return NULL;
    }

/* -------------------------------------------------------------------- */
/*      Decide how many bits the file should have.                      */
/* -------------------------------------------------------------------- */
    int nBits = GDALGetDataTypeSize(eType);

    if( CSLFetchNameValue( papszParmList, "NBITS" ) != NULL )
        nBits = atoi(CSLFetchNameValue( papszParmList, "NBITS" ));

    const int nRowBytes = (nBits * nXSize + 7) / 8;

/* -------------------------------------------------------------------- */
/*      Check for signed byte.                                          */
/* -------------------------------------------------------------------- */
    const char *pszPixelType = CSLFetchNameValue( papszParmList, "PIXELTYPE" );
    if( pszPixelType == NULL )
        pszPixelType = "";

/* -------------------------------------------------------------------- */
/*      Write out the raw definition for the dataset as a whole.        */
/* -------------------------------------------------------------------- */
    bOK &= VSIFPrintfL( fp, "BYTEORDER      I\n" ) >= 0;
    bOK &= VSIFPrintfL( fp, "LAYOUT         BIL\n" ) >= 0;
    bOK &= VSIFPrintfL( fp, "NROWS          %d\n", nYSize ) >= 0;
    bOK &= VSIFPrintfL( fp, "NCOLS          %d\n", nXSize ) >= 0;
    bOK &= VSIFPrintfL( fp, "NBANDS         %d\n", nBands ) >= 0;
    bOK &= VSIFPrintfL( fp, "NBITS          %d\n", nBits ) >= 0;
    bOK &= VSIFPrintfL( fp, "BANDROWBYTES   %d\n", nRowBytes ) >= 0;
    bOK &= VSIFPrintfL( fp, "TOTALROWBYTES  %d\n", nRowBytes * nBands ) >= 0;

    if( eType == GDT_Float32 )
        bOK &= VSIFPrintfL( fp, "PIXELTYPE      FLOAT\n") >= 0;
    else if( eType == GDT_Int16 || eType == GDT_Int32 )
        bOK &= VSIFPrintfL( fp, "PIXELTYPE      SIGNEDINT\n") >= 0;
    else if( eType == GDT_Byte && EQUAL(pszPixelType,"SIGNEDBYTE") )
        bOK &= VSIFPrintfL( fp, "PIXELTYPE      SIGNEDINT\n") >= 0;
    else
        bOK &= VSIFPrintfL( fp, "PIXELTYPE      UNSIGNEDINT\n") >= 0;

/* -------------------------------------------------------------------- */
/*      Cleanup                                                         */
/* -------------------------------------------------------------------- */
    if( VSIFCloseL( fp ) != 0 )
        bOK = false;

    CPLFree( pszHdrFilename );

    if( !bOK )
        return NULL;

    return
        reinterpret_cast<GDALDataset *>( GDALOpen( pszFilename, GA_Update ) );
}

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

GDALDataset *EHdrDataset::CreateCopy( const char * pszFilename,
                                      GDALDataset * poSrcDS,
                                      int bStrict, char ** papszOptions,
                                      GDALProgressFunc pfnProgress,
                                      void * pProgressData )

{
    const int nBands = poSrcDS->GetRasterCount();
    if (nBands == 0)
    {
        CPLError( CE_Failure, CPLE_NotSupported,
                  "EHdr driver does not support source dataset without any "
                  "bands.\n" );
        return NULL;
    }

    char **papszAdjustedOptions = CSLDuplicate( papszOptions );

/* -------------------------------------------------------------------- */
/*      Ensure we pass on NBITS and PIXELTYPE structure information.    */
/* -------------------------------------------------------------------- */
    if( poSrcDS->GetRasterBand(1)->GetMetadataItem( "NBITS",
                                                    "IMAGE_STRUCTURE" ) !=NULL
        && CSLFetchNameValue( papszOptions, "NBITS" ) == NULL )
    {
        papszAdjustedOptions =
            CSLSetNameValue(
                papszAdjustedOptions,
                "NBITS",
                poSrcDS->GetRasterBand(1)->
                    GetMetadataItem("NBITS","IMAGE_STRUCTURE") );
    }

    if( poSrcDS->GetRasterBand(1)->GetMetadataItem( "PIXELTYPE",
                                                    "IMAGE_STRUCTURE" ) !=NULL
        && CSLFetchNameValue( papszOptions, "PIXELTYPE" ) == NULL )
    {
        papszAdjustedOptions =
            CSLSetNameValue(
                papszAdjustedOptions,
                "PIXELTYPE",
                poSrcDS->GetRasterBand(1)->
                    GetMetadataItem("PIXELTYPE","IMAGE_STRUCTURE") );
    }

/* -------------------------------------------------------------------- */
/*      Proceed with normal copying using the default createcopy        */
/*      operators.                                                      */
/* -------------------------------------------------------------------- */
    GDALDriver *poDriver = reinterpret_cast<GDALDriver *>(
        GDALGetDriverByName( "EHdr" ) );

    GDALDataset *poOutDS
        = poDriver->DefaultCreateCopy( pszFilename, poSrcDS, bStrict,
                                       papszAdjustedOptions,
                                       pfnProgress, pProgressData );
    CSLDestroy( papszAdjustedOptions );

    if( poOutDS != NULL )
        poOutDS->FlushCache();

    return poOutDS;
}

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

double EHdrRasterBand::GetNoDataValue( int *pbSuccess )
{
    if( pbSuccess )
        *pbSuccess = bNoDataSet;

    if( bNoDataSet )
        return dfNoData;

    return RawRasterBand::GetNoDataValue( pbSuccess );
}

/************************************************************************/
/*                           GetMinimum()                               */
/************************************************************************/

double EHdrRasterBand::GetMinimum( int *pbSuccess )
{
    if( pbSuccess != NULL )
        *pbSuccess = (minmaxmeanstddev & HAS_MIN_FLAG) != 0;

    if( minmaxmeanstddev & HAS_MIN_FLAG )
      return dfMin;

    return RawRasterBand::GetMinimum( pbSuccess );
}

/************************************************************************/
/*                           GetMaximum()                               */
/************************************************************************/

double EHdrRasterBand::GetMaximum( int *pbSuccess )
{
    if( pbSuccess != NULL )
        *pbSuccess = (minmaxmeanstddev & HAS_MAX_FLAG) != 0;

    if( minmaxmeanstddev & HAS_MAX_FLAG )
      return dfMax;

    return RawRasterBand::GetMaximum( pbSuccess );
}

/************************************************************************/
/*                           GetStatistics()                            */
/************************************************************************/

CPLErr EHdrRasterBand::GetStatistics(
    int bApproxOK, int bForce,
    double *pdfMin, double *pdfMax,
    double *pdfMean, double *pdfStdDev )
{
    if( (minmaxmeanstddev & HAS_ALL_FLAGS) == HAS_ALL_FLAGS)
    {
        if( pdfMin ) *pdfMin = dfMin;
        if( pdfMax ) *pdfMax = dfMax;
        if( pdfMean ) *pdfMean = dfMean;
        if( pdfStdDev ) *pdfStdDev = dfStdDev;
        return CE_None;
    }

    const CPLErr eErr = RawRasterBand::GetStatistics( bApproxOK, bForce,
                                                &dfMin, &dfMax,
                                                &dfMean, &dfStdDev );
    if( eErr != CE_None )
        return eErr;

    EHdrDataset* poEDS = reinterpret_cast<EHdrDataset *>( poDS );

    minmaxmeanstddev = HAS_ALL_FLAGS;

    if( poEDS->RewriteSTX() != CE_None )
        RawRasterBand::SetStatistics( dfMin, dfMax, dfMean, dfStdDev );

    if( pdfMin )
        *pdfMin = dfMin;
    if( pdfMax )
        *pdfMax = dfMax;
    if( pdfMean )
        *pdfMean = dfMean;
    if( pdfStdDev )
        *pdfStdDev = dfStdDev;

    return CE_None;
}

/************************************************************************/
/*                           SetStatistics()                            */
/************************************************************************/

CPLErr EHdrRasterBand::SetStatistics( double dfMinIn, double dfMaxIn,
                                      double dfMeanIn, double dfStdDevIn )
{
    // avoid churn if nothing is changing.
    if( dfMin == dfMinIn
        && dfMax == dfMaxIn
        && dfMean == dfMeanIn
        && dfStdDev == dfStdDevIn )
        return CE_None;

    dfMin = dfMinIn;
    dfMax = dfMaxIn;
    dfMean = dfMeanIn;
    dfStdDev = dfStdDevIn;

    // marks stats valid
    minmaxmeanstddev = HAS_ALL_FLAGS;

    EHdrDataset* poEDS = reinterpret_cast<EHdrDataset *>( poDS );

    if( poEDS->RewriteSTX() != CE_None )
        return RawRasterBand::SetStatistics(
            dfMinIn, dfMaxIn, dfMeanIn, dfStdDevIn );

    return CE_None;
}

/************************************************************************/
/*                           SetColorTable()                            */
/************************************************************************/

CPLErr EHdrRasterBand::SetColorTable( GDALColorTable *poNewCT )
{
    const CPLErr err = RawRasterBand::SetColorTable( poNewCT );
    if( err != CE_None )
        return err;

    reinterpret_cast<EHdrDataset *>( poDS )->bCLRDirty = TRUE;

    return CE_None;
}

/************************************************************************/
/*                         GDALRegister_EHdr()                          */
/************************************************************************/

void GDALRegister_EHdr()

{
    if( GDALGetDriverByName( "EHdr" ) != NULL )
        return;

    GDALDriver *poDriver = new GDALDriver();

    poDriver->SetDescription( "EHdr" );
    poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" );
    poDriver->SetMetadataItem( GDAL_DMD_LONGNAME, "ESRI .hdr Labelled" );
    poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC, "frmt_various.html#EHdr" );
    poDriver->SetMetadataItem( GDAL_DMD_CREATIONDATATYPES,
                               "Byte Int16 UInt16 Int32 UInt32 Float32" );

    poDriver->SetMetadataItem( GDAL_DMD_CREATIONOPTIONLIST,
"<CreationOptionList>"
"   <Option name='NBITS' type='int' description='Special pixel bits (1-7)'/>"
"   <Option name='PIXELTYPE' type='string' description='By setting this to SIGNEDBYTE, a new Byte file can be forced to be written as signed byte'/>"
"</CreationOptionList>" );

    poDriver->SetMetadataItem( GDAL_DCAP_VIRTUALIO, "YES" );
    poDriver->pfnOpen = EHdrDataset::Open;
    poDriver->pfnCreate = EHdrDataset::Create;
    poDriver->pfnCreateCopy = EHdrDataset::CreateCopy;

    GetGDALDriverManager()->RegisterDriver( poDriver );
}
