/******************************************************************************
 *
 * Project:  Multi-resolution Seamless Image Database (MrSID)
 * Purpose:  Read/write LizardTech's MrSID file format - Version 4+ SDK.
 * Author:   Andrey Kiselev, dron@ak4719.spb.edu
 *
 ******************************************************************************
 * Copyright (c) 2003, Andrey Kiselev <dron@ak4719.spb.edu>
 * 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.
 ****************************************************************************/

#ifdef DEBUG_BOOL
#define DO_NOT_USE_DEBUG_BOOL
#endif

#define NO_DELETE

#include "cpl_string.h"
#include "gdal_frmts.h"
#include "gdaljp2abstractdataset.h"
#include "gdaljp2metadata.h"
#include "ogr_spatialref.h"
#include <string>

#include <geo_normalize.h>
#include <geovalues.h>

CPL_CVSID("$Id: mrsiddataset.cpp 002b050d9a9ef403a732c1210784736ef97216d4 2018-04-09 21:34:55 +0200 Even Rouault $")

CPL_C_START
double GTIFAngleToDD( double dfAngle, int nUOMAngle );
void CPL_DLL LibgeotiffOneTimeInit();
CPL_C_END

// Key Macros from Makefile:
//   MRSID_ESDK: Means we have the encoding SDK (version 5 or newer required)
//   MRSID_J2K: Means we are enabling MrSID SDK JPEG2000 support.

#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunknown-pragmas"
#pragma clang diagnostic ignored "-Wdocumentation"
#endif

#include "lt_types.h"
#include "lt_base.h"
#include "lt_fileSpec.h"
#include "lt_ioFileStream.h"
#include "lt_utilStatusStrings.h"
#include "lti_geoCoord.h"
#include "lti_pixel.h"
#include "lti_navigator.h"
#include "lti_sceneBuffer.h"
#include "lti_metadataDatabase.h"
#include "lti_metadataRecord.h"
#include "lti_utils.h"
#include "lti_delegates.h"
#include "lt_utilStatus.h"
#include "MrSIDImageReader.h"

#ifdef MRSID_J2K
#  include "J2KImageReader.h"
#endif

// It seems that LT_STS_UTIL_TimeUnknown was added in version 6, also
// the first version with lti_version.h
#ifdef LT_STS_UTIL_TimeUnknown
#  include "lti_version.h"
#endif

// Are we using version 6 or newer?
#if defined(LTI_SDK_MAJOR) && LTI_SDK_MAJOR >= 6
#  define MRSID_POST5
#endif

#ifdef MRSID_ESDK
# include "MG3ImageWriter.h"
# include "MG3WriterParams.h"
# include "MG2ImageWriter.h"
# include "MG2WriterParams.h"
# ifdef MRSID_HAVE_MG4WRITE
#   include "MG4ImageWriter.h"
#   include "MG4WriterParams.h"
# endif
# ifdef MRSID_J2K
#   ifdef MRSID_POST5
#     include "JP2WriterManager.h"
#     include "JPCWriterParams.h"
#   else
#     include "J2KImageWriter.h"
#     include "J2KWriterParams.h"
#   endif
# endif
#endif /* MRSID_ESDK */

#ifdef __clang__
#pragma clang diagnostic pop
#endif

#ifdef MRSID_POST5
#  define MRSID_HAVE_GETWKT
#endif

/* getTotalBandData is deprecated by getBandData, at least starting with 8.5 */
#if defined(LTI_SDK_MAJOR) && (LTI_SDK_MAJOR > 8 || (LTI_SDK_MAJOR >= 8 && LTI_SDK_MINOR >= 5))
#  define myGetTotalBandData getBandData
#else
#  define myGetTotalBandData getTotalBandData
#endif

#include "mrsidstream.h"

LT_USE_NAMESPACE(LizardTech)

/* -------------------------------------------------------------------- */
/*      Various wrapper templates used to force new/delete to happen    */
/*      in the same heap.  See bug 1213 and MSDN knowledge base         */
/*      article 122675.                                                 */
/* -------------------------------------------------------------------- */

template <class T>
class LTIDLLPixel : public T
{
public:
   LTIDLLPixel(LTIColorSpace colorSpace,
            lt_uint16 numBands,
            LTIDataType dataType) : T(colorSpace,numBands,dataType) {}
   virtual ~LTIDLLPixel() {}
};

template <class T>
class LTIDLLReader : public T
{
public:
   LTIDLLReader(const LTFileSpec& fileSpec,
                bool useWorldFile = false) : T(fileSpec, useWorldFile) {}
   LTIDLLReader(LTIOStreamInf &oStream,
                bool useWorldFile = false) : T(oStream, useWorldFile) {}
   LTIDLLReader(LTIOStreamInf *poStream,
                LTIOStreamInf *poWorldFile = nullptr) : T(poStream, poWorldFile) {}
   virtual ~LTIDLLReader() {}
};

template <class T>
class LTIDLLNavigator : public T
{
public:
   explicit LTIDLLNavigator(const LTIImage& image ) : T(image) {}
   virtual ~LTIDLLNavigator() {}
};

template <class T>
class LTIDLLBuffer : public T
{
public:
   LTIDLLBuffer(const LTIPixel& pixelProps,
                  lt_uint32 totalNumCols,
                  lt_uint32 totalNumRows,
                  void** data ) : T(pixelProps,totalNumCols,totalNumRows,data) {}
   virtual ~LTIDLLBuffer() {}
};

template <class T>
class LTIDLLCopy : public T
{
public:
   explicit LTIDLLCopy(const T& original) : T(original) {}
   virtual ~LTIDLLCopy() {}
};

template <class T>
class LTIDLLWriter : public T
{
public:
    explicit LTIDLLWriter(LTIImageStage *image) : T(image) {}
    virtual ~LTIDLLWriter() {}
};

template <class T>
class LTIDLLDefault : public T
{
public:
    LTIDLLDefault() : T() {}
    virtual ~LTIDLLDefault() {}
};

/* -------------------------------------------------------------------- */
/*      Interface to MrSID SDK progress reporting.                      */
/* -------------------------------------------------------------------- */

class MrSIDProgress : public LTIProgressDelegate
{
public:
    MrSIDProgress(GDALProgressFunc f, void *arg) : m_f(f), m_arg(arg) {}
    virtual ~MrSIDProgress() {}
    virtual LT_STATUS setProgressStatus(float fraction)
    {
        if (!m_f)
            return LT_STS_BadContext;
        if( !m_f( fraction, nullptr, m_arg ) )
            return LT_STS_Failure;
        return LT_STS_Success;
    }
private:
    GDALProgressFunc m_f;
    void *m_arg;
};

/************************************************************************/
/* ==================================================================== */
/*                              MrSIDDataset                            */
/* ==================================================================== */
/************************************************************************/

class MrSIDDataset final: public GDALJP2AbstractDataset
{
    friend class MrSIDRasterBand;

    LTIOStreamInf       *poStream;
    LTIOFileStream      oLTIStream;
    LTIVSIStream        oVSIStream;

#if defined(LTI_SDK_MAJOR) && LTI_SDK_MAJOR >= 7
    LTIImageFilter      *poImageReader;
#else
    LTIImageReader      *poImageReader;
#endif

#ifdef MRSID_ESDK
    LTIGeoFileImageWriter *poImageWriter;
#endif

    LTIDLLNavigator<LTINavigator>  *poLTINav;
    LTIDLLCopy<LTIMetadataDatabase> *poMetadata;
    const LTIPixel      *poNDPixel;

    LTIDLLBuffer<LTISceneBuffer>  *poBuffer;
    int                 nBlockXSize;
    int                 nBlockYSize;
    int                 bPrevBlockRead;
    int                 nPrevBlockXOff, nPrevBlockYOff;

    LTIDataType         eSampleType;
    GDALDataType        eDataType;
    LTIColorSpace       eColorSpace;

    double              dfCurrentMag;

    GTIFDefn            *psDefn;

    MrSIDDataset       *poParentDS;
    int                 bIsOverview;
    int                 nOverviewCount;
    MrSIDDataset        **papoOverviewDS;

    CPLString           osMETFilename;

    CPLErr              OpenZoomLevel( lt_int32 iZoom );
    char                *SerializeMetadataRec( const LTIMetadataRecord* );
    int                 GetMetadataElement( const char *, void *, int=0 );
    void                FetchProjParms();
    void                GetGTIFDefn();
    char                *GetOGISDefn( GTIFDefn * );

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

  protected:
    virtual int         CloseDependentDatasets() override;

    virtual CPLErr      IBuildOverviews( const char *, int, int *,
                                         int, int *, GDALProgressFunc, void * ) override;

  public:
    explicit    MrSIDDataset(int bIsJPEG2000);
                ~MrSIDDataset();

    static GDALDataset  *Open( GDALOpenInfo * poOpenInfo, int bIsJP2 );

    virtual char      **GetFileList() override;

#ifdef MRSID_ESDK
    static GDALDataset  *Create( const char * pszFilename,
                                 int nXSize, int nYSize, int nBands,
                                 GDALDataType eType, char ** papszParmList );
    virtual void        FlushCache( void );
#endif
};

/************************************************************************/
/* ==================================================================== */
/*                           MrSIDRasterBand                            */
/* ==================================================================== */
/************************************************************************/

class MrSIDRasterBand final: public GDALPamRasterBand
{
    friend class MrSIDDataset;

    LTIPixel        *poPixel;

    int             nBlockSize;

    int             bNoDataSet;
    double          dfNoDataValue;

    MrSIDDataset    *poGDS;

    GDALColorInterp eBandInterp;

  public:

                MrSIDRasterBand( MrSIDDataset *, int );
                ~MrSIDRasterBand();

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

    virtual CPLErr          IReadBlock( int, int, void * ) override;
    virtual GDALColorInterp GetColorInterpretation() override;
    CPLErr                  SetColorInterpretation( GDALColorInterp eNewInterp ) override;
    virtual double          GetNoDataValue( int * ) override;
    virtual int             GetOverviewCount() override;
    virtual GDALRasterBand  *GetOverview( int ) override;

    virtual CPLErr GetStatistics( int bApproxOK, int bForce,
                                  double *pdfMin, double *pdfMax,
                                  double *pdfMean, double *pdfStdDev ) override;

#ifdef MRSID_ESDK
    virtual CPLErr          IWriteBlock( int, int, void * );
#endif
};

/************************************************************************/
/*                           MrSIDRasterBand()                          */
/************************************************************************/

MrSIDRasterBand::MrSIDRasterBand( MrSIDDataset *poDSIn, int nBandIn )
{
    this->poDS = poDSIn;
    poGDS = poDSIn;
    this->nBand = nBandIn;
    this->eDataType = poDSIn->eDataType;

/* -------------------------------------------------------------------- */
/*      Set the block sizes and buffer parameters.                      */
/* -------------------------------------------------------------------- */
    nBlockXSize = poDSIn->nBlockXSize;
    nBlockYSize = poDSIn->nBlockYSize;
//#ifdef notdef
    if( poDS->GetRasterXSize() > 2048 )
        nBlockXSize = 1024;
    if( poDS->GetRasterYSize() > 128 )
        nBlockYSize = 128;
    else
        nBlockYSize = poDS->GetRasterYSize();
//#endif

    nBlockSize = nBlockXSize * nBlockYSize;
    poPixel = new LTIDLLPixel<LTIPixel>( poDSIn->eColorSpace,
                                         static_cast<lt_uint16>(poDSIn->nBands),
                                         poDSIn->eSampleType );

/* -------------------------------------------------------------------- */
/*      Set NoData values.                                              */
/*                                                                      */
/*      This logic is disabled for now since the MrSID nodata           */
/*      semantics are different than GDAL.  In MrSID all bands must     */
/*      match the nodata value for that band in order for the pixel     */
/*      to be considered nodata, otherwise all values are valid.        */
/* -------------------------------------------------------------------- */
#ifdef notdef
     if ( poDS->poNDPixel )
     {
         switch( poDS->eSampleType )
         {
             case LTI_DATATYPE_UINT8:
             case LTI_DATATYPE_SINT8:
                 dfNoDataValue = (double)
                     poDS->poNDPixel->getSampleValueUint8( nBand - 1 );
                 break;
             case LTI_DATATYPE_UINT16:
                 dfNoDataValue = (double)
                     poDS->poNDPixel->getSampleValueUint16( nBand - 1 );
                 break;
             case LTI_DATATYPE_FLOAT32:
                 dfNoDataValue =
                     poDS->poNDPixel->getSampleValueFloat32( nBand - 1 );
                 break;
             case LTI_DATATYPE_SINT16:
                 dfNoDataValue = (double)
                     *(GInt16 *)poDS->poNDPixel->getSampleValueAddr( nBand - 1 );
                 break;
             case LTI_DATATYPE_UINT32:
                 dfNoDataValue = (double)
                     *(GUInt32 *)poDS->poNDPixel->getSampleValueAddr( nBand - 1 );
                 break;
             case LTI_DATATYPE_SINT32:
                 dfNoDataValue = (double)
                     *(GInt32 *)poDS->poNDPixel->getSampleValueAddr( nBand - 1 );
                 break;
             case LTI_DATATYPE_FLOAT64:
                 dfNoDataValue =
                     *(double *)poDS->poNDPixel->getSampleValueAddr( nBand - 1 );
                 break;

             case LTI_DATATYPE_INVALID:
                 CPLAssert( false );
                 break;
         }
         bNoDataSet = TRUE;
     }
     else
#endif
     {
        dfNoDataValue = 0.0;
        bNoDataSet = FALSE;
     }

    switch( poGDS->eColorSpace )
    {
        case LTI_COLORSPACE_RGB:
            if( nBand == 1 )
                eBandInterp = GCI_RedBand;
            else if( nBand == 2 )
                eBandInterp = GCI_GreenBand;
            else if( nBand == 3 )
                eBandInterp = GCI_BlueBand;
            else
                eBandInterp = GCI_Undefined;
            break;

#if defined(LTI_SDK_MAJOR) && LTI_SDK_MAJOR >= 8
        case LTI_COLORSPACE_RGBA:
            if( nBand == 1 )
                eBandInterp = GCI_RedBand;
            else if( nBand == 2 )
                eBandInterp = GCI_GreenBand;
            else if( nBand == 3 )
                eBandInterp = GCI_BlueBand;
            else if( nBand == 4 )
                eBandInterp = GCI_AlphaBand;
            else
                eBandInterp = GCI_Undefined;
            break;
#endif

        case LTI_COLORSPACE_CMYK:
            if( nBand == 1 )
                eBandInterp = GCI_CyanBand;
            else if( nBand == 2 )
                eBandInterp = GCI_MagentaBand;
            else if( nBand == 3 )
                eBandInterp = GCI_YellowBand;
            else if( nBand == 4 )
                eBandInterp = GCI_BlackBand;
            else
                eBandInterp = GCI_Undefined;
            break;

        case LTI_COLORSPACE_GRAYSCALE:
            eBandInterp = GCI_GrayIndex;
            break;

#if defined(LTI_SDK_MAJOR) && LTI_SDK_MAJOR >= 8
        case LTI_COLORSPACE_GRAYSCALEA:
            if( nBand == 1 )
                eBandInterp = GCI_GrayIndex;
            else if( nBand == 2 )
                eBandInterp = GCI_AlphaBand;
            else
                eBandInterp = GCI_Undefined;
            break;

        case LTI_COLORSPACE_GRAYSCALEA_PM:
            if( nBand == 1 )
                eBandInterp = GCI_GrayIndex;
            else if( nBand == 2 )
                eBandInterp = GCI_AlphaBand;
            else
                eBandInterp = GCI_Undefined;
            break;
#endif

        default:
            eBandInterp = GCI_Undefined;
            break;
    }
}

/************************************************************************/
/*                            ~MrSIDRasterBand()                        */
/************************************************************************/

MrSIDRasterBand::~MrSIDRasterBand()
{
    if ( poPixel )
        delete poPixel;
}

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

CPLErr MrSIDRasterBand::IReadBlock( int nBlockXOff, int nBlockYOff,
                                    void * pImage )
{
#ifdef MRSID_ESDK
    if( poGDS->eAccess == GA_Update )
    {
        CPLDebug( "MrSID",
                  "IReadBlock() - DSDK - read on updatable file fails." );
        memset( pImage, 0, nBlockSize * GDALGetDataTypeSize(eDataType) / 8 );
        return CE_None;
    }
#endif /* MRSID_ESDK */

    CPLDebug( "MrSID", "IReadBlock(%d,%d)", nBlockXOff, nBlockYOff );

    if ( !poGDS->bPrevBlockRead
         || poGDS->nPrevBlockXOff != nBlockXOff
         || poGDS->nPrevBlockYOff != nBlockYOff )
    {
        GInt32      nLine = nBlockYOff * nBlockYSize;
        GInt32      nCol = nBlockXOff * nBlockXSize;

        // XXX: The scene, passed to LTIImageStage::read() call must be
        // inside the image boundaries. So we should detect the last strip and
        // form the scene properly.
        CPLDebug( "MrSID",
                  "IReadBlock - read() %dx%d block at %d,%d.",
                  nBlockXSize, nBlockYSize, nCol, nLine );

        if(!LT_SUCCESS( poGDS->poLTINav->setSceneAsULWH(
                            nCol, nLine,
                            (nCol+nBlockXSize>poGDS->GetRasterXSize())?
                            (poGDS->GetRasterXSize()-nCol):nBlockXSize,
                            (nLine+nBlockYSize>poGDS->GetRasterYSize())?
                            (poGDS->GetRasterYSize()-nLine):nBlockYSize,
                            poGDS->dfCurrentMag) ))
        {
            CPLError( CE_Failure, CPLE_AppDefined,
                      "MrSIDRasterBand::IReadBlock(): Failed to set scene position." );
            return CE_Failure;
        }

        if ( !poGDS->poBuffer )
        {
            poGDS->poBuffer =
                new LTIDLLBuffer<LTISceneBuffer>( *poPixel, nBlockXSize, nBlockYSize, nullptr );
//            poGDS->poBuffer =
//                new LTISceneBuffer( *poPixel, nBlockXSize, nBlockYSize, nullptr );
        }

        if(!LT_SUCCESS(poGDS->poImageReader->read(poGDS->poLTINav->getScene(),
                                                  *poGDS->poBuffer)))
        {
            CPLError( CE_Failure, CPLE_AppDefined,
                      "MrSIDRasterBand::IReadBlock(): Failed to load image." );
            return CE_Failure;
        }

        poGDS->bPrevBlockRead = TRUE;
        poGDS->nPrevBlockXOff = nBlockXOff;
        poGDS->nPrevBlockYOff = nBlockYOff;
    }

    memcpy( pImage, poGDS->poBuffer->myGetTotalBandData(static_cast<lt_uint16>(nBand - 1)),
            nBlockSize * (GDALGetDataTypeSize(poGDS->eDataType) / 8) );

    return CE_None;
}

#ifdef MRSID_ESDK

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

CPLErr MrSIDRasterBand::IWriteBlock( int nBlockXOff, int nBlockYOff,
                                     void * pImage )
{
    CPLAssert( poGDS != nullptr
               && nBlockXOff >= 0
               && nBlockYOff >= 0
               && pImage != nullptr );

#ifdef DEBUG
    CPLDebug( "MrSID", "IWriteBlock(): nBlockXOff=%d, nBlockYOff=%d",
              nBlockXOff, nBlockYOff );
#endif

    LTIScene        oScene( nBlockXOff * nBlockXSize,
                            nBlockYOff * nBlockYSize,
                            nBlockXSize, nBlockYSize, 1.0);
    LTISceneBuffer  oSceneBuf( *poPixel, poGDS->nBlockXSize,
                               poGDS->nBlockYSize, &pImage );

    if( !LT_SUCCESS(poGDS->poImageWriter->writeBegin(oScene)) )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "MrSIDRasterBand::IWriteBlock(): writeBegin failed." );
        return CE_Failure;
    }

    if( !LT_SUCCESS(poGDS->poImageWriter->writeStrip(oSceneBuf, oScene)) )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "MrSIDRasterBand::IWriteBlock(): writeStrip failed." );
        return CE_Failure;
    }

    if( !LT_SUCCESS(poGDS->poImageWriter->writeEnd()) )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "MrSIDRasterBand::IWriteBlock(): writeEnd failed." );
        return CE_Failure;
    }

    return CE_None;
}

#endif /* MRSID_ESDK */

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

CPLErr MrSIDRasterBand::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)

{
/* -------------------------------------------------------------------- */
/*      Fallback to default implementation if the whole scanline        */
/*      without subsampling requested.                                  */
/* -------------------------------------------------------------------- */
    if ( nXSize == poGDS->GetRasterXSize()
         && nXSize == nBufXSize
         && nYSize == nBufYSize )
    {
        return GDALRasterBand::IRasterIO( eRWFlag, nXOff, nYOff,
                                          nXSize, nYSize, pData,
                                          nBufXSize, nBufYSize, eBufType,
                                          nPixelSpace, nLineSpace, psExtraArg );
    }

/* -------------------------------------------------------------------- */
/*      Handle via the dataset level IRasterIO()                        */
/* -------------------------------------------------------------------- */
    return poGDS->IRasterIO( eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
                             nBufXSize, nBufYSize, eBufType,
                             1, &nBand, nPixelSpace, nLineSpace, 0, psExtraArg );
}

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

GDALColorInterp MrSIDRasterBand::GetColorInterpretation()

{
    return eBandInterp;
}

/************************************************************************/
/*                       SetColorInterpretation()                       */
/*                                                                      */
/*      This would normally just be used by folks using the MrSID code  */
/*      to read JP2 streams in other formats (such as NITF) and         */
/*      providing their own color interpretation regardless of what     */
/*      MrSID might think the stream itself says.                       */
/************************************************************************/

CPLErr MrSIDRasterBand::SetColorInterpretation( GDALColorInterp eNewInterp )

{
    eBandInterp = eNewInterp;

    return CE_None;
}

/************************************************************************/
/*                           GetStatistics()                            */
/*                                                                      */
/*      We override this method so that we can force generation of      */
/*      statistics if approx ok is true since we know that a small      */
/*      overview is always available, and that computing statistics     */
/*      from it is very fast.                                           */
/************************************************************************/

CPLErr MrSIDRasterBand::GetStatistics( int bApproxOK, int bForce,
                                       double *pdfMin, double *pdfMax,
                                       double *pdfMean, double *pdfStdDev )

{
    if( bApproxOK )
        bForce = TRUE;

    return GDALPamRasterBand::GetStatistics( bApproxOK, bForce,
                                             pdfMin, pdfMax,
                                             pdfMean, pdfStdDev );
}

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

double MrSIDRasterBand::GetNoDataValue( int * pbSuccess )

{
    if( bNoDataSet )
    {
        if( pbSuccess )
            *pbSuccess = bNoDataSet;

        return dfNoDataValue;
    }

    return GDALPamRasterBand::GetNoDataValue( pbSuccess );
}

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

int MrSIDRasterBand::GetOverviewCount()

{
    return poGDS->nOverviewCount;
}

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

GDALRasterBand *MrSIDRasterBand::GetOverview( int i )

{
    if( i < 0 || i >= poGDS->nOverviewCount )
        return nullptr;
    else
        return poGDS->papoOverviewDS[i]->GetRasterBand( nBand );
}

/************************************************************************/
/*                           MrSIDDataset()                             */
/************************************************************************/

MrSIDDataset::MrSIDDataset(int bIsJPEG2000) :
    nBlockXSize(0),
    nBlockYSize(0),
    eSampleType(LTI_DATATYPE_UINT8),
    eDataType(GDT_Byte),
    eColorSpace(LTI_COLORSPACE_INVALID)
{
    poStream = nullptr;
    poImageReader = nullptr;
#ifdef MRSID_ESDK
    poImageWriter = nullptr;
#endif
    poLTINav = nullptr;
    poMetadata = nullptr;
    poNDPixel = nullptr;
    nBands = 0;

    poBuffer = nullptr;
    bPrevBlockRead = FALSE;
    nPrevBlockXOff = 0;
    nPrevBlockYOff = 0;

    psDefn = nullptr;

    dfCurrentMag = 1.0;
    bIsOverview = FALSE;
    poParentDS = this;
    nOverviewCount = 0;
    papoOverviewDS = nullptr;

    poDriver = (GDALDriver*) GDALGetDriverByName( bIsJPEG2000 ? "JP2MrSID" : "MrSID" );
}

/************************************************************************/
/*                            ~MrSIDDataset()                           */
/************************************************************************/

MrSIDDataset::~MrSIDDataset()
{
    MrSIDDataset::FlushCache();

#ifdef MRSID_ESDK
    if ( poImageWriter )
        delete poImageWriter;
#endif

    if ( poBuffer )
        delete poBuffer;
    if ( poMetadata )
        delete poMetadata;
    if ( poLTINav )
        delete poLTINav;
    if ( poImageReader && !bIsOverview )
#if defined(LTI_SDK_MAJOR) && LTI_SDK_MAJOR >= 7
    {
        poImageReader->release();
        poImageReader = nullptr;
    }
#else
        delete poImageReader;
#endif
    // points to another member, don't delete
    poStream = nullptr;

    if ( psDefn )
        delete psDefn;
    MrSIDDataset::CloseDependentDatasets();
}

/************************************************************************/
/*                      CloseDependentDatasets()                        */
/************************************************************************/

int MrSIDDataset::CloseDependentDatasets()
{
    int bRet = GDALPamDataset::CloseDependentDatasets();

    if ( papoOverviewDS )
    {
        for( int i = 0; i < nOverviewCount; i++ )
            delete papoOverviewDS[i];
        CPLFree( papoOverviewDS );
        papoOverviewDS = nullptr;
        bRet = TRUE;
    }
    return bRet;
}

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

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

{
/* -------------------------------------------------------------------- */
/*      We need various criteria to skip out to block based methods.    */
/* -------------------------------------------------------------------- */
    int bUseBlockedIO = bForceCachedIO;

    if( nYSize == 1 || nXSize * ((double) nYSize) < 100.0 )
        bUseBlockedIO = TRUE;

    if( nBufYSize == 1 || nBufXSize * ((double) nBufYSize) < 100.0 )
        bUseBlockedIO = TRUE;

    if( CPLTestBool( CPLGetConfigOption( "GDAL_ONE_BIG_READ", "NO") ) )
        bUseBlockedIO = FALSE;

    if( bUseBlockedIO )
        return GDALDataset::BlockBasedRasterIO(
            eRWFlag, nXOff, nYOff, nXSize, nYSize,
            pData, nBufXSize, nBufYSize, eBufType,
            nBandCount, panBandMap, nPixelSpace, nLineSpace, nBandSpace, psExtraArg );
    CPLDebug( "MrSID", "RasterIO() - using optimized dataset level IO." );

/* -------------------------------------------------------------------- */
/*      What is our requested window relative to the base dataset.      */
/*      We want to operate from here on as if we were operating on      */
/*      the full res band.                                              */
/* -------------------------------------------------------------------- */
    int nZoomMag = (int) ((1/dfCurrentMag) * 1.0000001);

    nXOff *= nZoomMag;
    nYOff *= nZoomMag;
    nXSize *= nZoomMag;
    nYSize *= nZoomMag;

/* -------------------------------------------------------------------- */
/*      We need to figure out the best zoom level to use for this       */
/*      request.  We apply a small fudge factor to make sure that       */
/*      request just very, very slightly larger than a zoom level do    */
/*      not force us to the next level.                                 */
/* -------------------------------------------------------------------- */
    int iOverview = 0;
    double dfZoomMag = MIN((nXSize / (double)nBufXSize),
                           (nYSize / (double)nBufYSize));

    for( nZoomMag = 1;
         nZoomMag * 2 < (dfZoomMag + 0.1)
             && iOverview < poParentDS->nOverviewCount;
         nZoomMag *= 2, iOverview++ ) {}

/* -------------------------------------------------------------------- */
/*      Work out the size of the temporary buffer and allocate it.      */
/*      The temporary buffer will generally be at a moderately          */
/*      higher resolution than the buffer of data requested.            */
/* -------------------------------------------------------------------- */
    int  nTmpPixelSize;
    LTIPixel       oPixel( eColorSpace, static_cast<lt_uint16>(nBands), eSampleType );

    LT_STATUS eLTStatus;
    unsigned int maxWidth;
    unsigned int maxHeight;

    eLTStatus = poImageReader->getDimsAtMag(1.0/nZoomMag,maxWidth,maxHeight);

    if( !LT_SUCCESS(eLTStatus)) {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "MrSIDDataset::IRasterIO(): Failed to get zoomed image dimensions.\n%s",
                  getLastStatusString( eLTStatus ) );
        return CE_Failure;
    }

    int maxWidthAtL0 = bIsOverview?poParentDS->GetRasterXSize():this->GetRasterXSize();
    int maxHeightAtL0 = bIsOverview?poParentDS->GetRasterYSize():this->GetRasterYSize();

    int sceneUlXOff = nXOff / nZoomMag;
    int sceneUlYOff = nYOff / nZoomMag;
    int sceneWidth  = (int)(nXSize * (double) maxWidth / (double)maxWidthAtL0 + 0.99);
    int sceneHeight = (int)(nYSize * (double) maxHeight / (double)maxHeightAtL0 + 0.99);

    if( (sceneUlXOff + sceneWidth) > (int) maxWidth )
        sceneWidth = maxWidth - sceneUlXOff;

    if( (sceneUlYOff + sceneHeight) > (int) maxHeight )
        sceneHeight = maxHeight - sceneUlYOff;

    LTISceneBuffer oLTIBuffer( oPixel, sceneWidth, sceneHeight, nullptr );

    nTmpPixelSize = GDALGetDataTypeSize( eDataType ) / 8;

/* -------------------------------------------------------------------- */
/*      Create navigator, and move to the requested scene area.         */
/* -------------------------------------------------------------------- */
    LTINavigator oNav( *poImageReader );

    if( !LT_SUCCESS(oNav.setSceneAsULWH( sceneUlXOff, sceneUlYOff,
                                         sceneWidth, sceneHeight,
                                         1.0 / nZoomMag )) )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "MrSIDDataset::IRasterIO(): Failed to set scene position." );

        return CE_Failure;
    }

    CPLDebug( "MrSID",
              "Dataset:IRasterIO(%d,%d %dx%d -> %dx%d -> %dx%d, zoom=%d)",
              nXOff, nYOff, nXSize, nYSize,
              sceneWidth, sceneHeight,
              nBufXSize, nBufYSize,
              nZoomMag );

    if( !oNav.isSceneValid() )
        CPLDebug( "MrSID", "LTINavigator in invalid state." );

/* -------------------------------------------------------------------- */
/*      Read into the buffer.                                           */
/* -------------------------------------------------------------------- */

    eLTStatus = poImageReader->read(oNav.getScene(),oLTIBuffer);
    if(!LT_SUCCESS(eLTStatus) )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "MrSIDRasterBand::IRasterIO(): Failed to load image.\n%s",
                  getLastStatusString( eLTStatus ) );
        return CE_Failure;
    }

/* -------------------------------------------------------------------- */
/*      If we are pulling the data at a matching resolution, try to     */
/*      do a more direct copy without subsampling.                      */
/* -------------------------------------------------------------------- */
    int         iBufLine, iBufPixel;

    if( nBufXSize == sceneWidth && nBufYSize == sceneHeight )
    {
        for( int iBand = 0; iBand < nBandCount; iBand++ )
        {
            GByte *pabySrcBand = (GByte *)
                oLTIBuffer.myGetTotalBandData( static_cast<lt_uint16>(panBandMap[iBand] - 1) );

            for( int iLine = 0; iLine < nBufYSize; iLine++ )
            {
                GDALCopyWords( pabySrcBand + iLine*nTmpPixelSize*sceneWidth,
                               eDataType, nTmpPixelSize,
                               ((GByte *)pData) + iLine*nLineSpace
                               + iBand * nBandSpace,
                               eBufType, static_cast<int>(nPixelSpace),
                               nBufXSize );
            }
        }
    }

/* -------------------------------------------------------------------- */
/*      Manually resample to our target buffer.                         */
/* -------------------------------------------------------------------- */
    else
    {
        for( iBufLine = 0; iBufLine < nBufYSize; iBufLine++ )
        {
            int iTmpLine = (int) floor(((iBufLine+0.5) / nBufYSize)*sceneHeight);

            for( iBufPixel = 0; iBufPixel < nBufXSize; iBufPixel++ )
            {
                int iTmpPixel = (int)
                    floor(((iBufPixel+0.5) / nBufXSize) * sceneWidth);

                for( int iBand = 0; iBand < nBandCount; iBand++ )
                {
                    GByte *pabySrc, *pabyDst;

                    pabyDst = ((GByte *) pData)
                        + nPixelSpace * iBufPixel
                        + nLineSpace * iBufLine
                        + nBandSpace * iBand;

                    pabySrc = (GByte *) oLTIBuffer.myGetTotalBandData(
                        static_cast<lt_uint16>(panBandMap[iBand] - 1) );
                    pabySrc += (iTmpLine * sceneWidth + iTmpPixel) * nTmpPixelSize;

                    if( eDataType == eBufType )
                        memcpy( pabyDst, pabySrc, nTmpPixelSize );
                    else
                        GDALCopyWords( pabySrc, eDataType, 0,
                                       pabyDst, eBufType, 0, 1 );
                }
            }
        }
    }

    return CE_None;
}

/************************************************************************/
/*                          IBuildOverviews()                           */
/************************************************************************/

CPLErr MrSIDDataset::IBuildOverviews( const char *, int, int *,
                                      int, int *, GDALProgressFunc,
                                      void * )
{
        CPLError( CE_Warning, CPLE_AppDefined,
                          "MrSID overviews are built-in, so building external "
                          "overviews is unnecessary. Ignoring.\n" );

        return CE_None;
}

/************************************************************************/
/*                        SerializeMetadataRec()                        */
/************************************************************************/

char *MrSIDDataset::SerializeMetadataRec( const LTIMetadataRecord *poMetadataRec )
{
    GUInt32  iNumDims = 0;
    const GUInt32  *paiDims = nullptr;
    const void     *pData = poMetadataRec->getArrayData( iNumDims, paiDims );
    GUInt32        i, j, k = 0, iLength;
    char           *pszMetadata = CPLStrdup( "" );

    for ( i = 0; i < iNumDims; i++ )
    {
        // stops on large binary data
        if ( poMetadataRec->getDataType() == LTI_METADATA_DATATYPE_UINT8
             && paiDims[i] > 1024 )
            return pszMetadata;

        for ( j = 0; j < paiDims[i]; j++ )
        {
            CPLString osTemp;

            switch( poMetadataRec->getDataType() )
            {
                case LTI_METADATA_DATATYPE_UINT8:
                case LTI_METADATA_DATATYPE_SINT8:
                    osTemp.Printf( "%d", ((GByte *)pData)[k++] );
                    break;
                case LTI_METADATA_DATATYPE_UINT16:
                    osTemp.Printf( "%u", ((GUInt16 *)pData)[k++] );
                    break;
                case LTI_METADATA_DATATYPE_SINT16:
                    osTemp.Printf( "%d", ((GInt16 *)pData)[k++] );
                    break;
                case LTI_METADATA_DATATYPE_UINT32:
                    osTemp.Printf( "%u", ((GUInt32 *)pData)[k++] );
                    break;
                case LTI_METADATA_DATATYPE_SINT32:
                    osTemp.Printf( "%d", ((GInt32 *)pData)[k++] );
                    break;
                case LTI_METADATA_DATATYPE_FLOAT32:
                    osTemp.Printf( "%f", ((float *)pData)[k++] );
                    break;
                case LTI_METADATA_DATATYPE_FLOAT64:
                    osTemp.Printf( "%f", ((double *)pData)[k++] );
                    break;
                case LTI_METADATA_DATATYPE_ASCII:
                    osTemp = ((const char **)pData)[k++];
                    break;
                default:
                    osTemp = "";
                    break;
            }

            iLength = static_cast<int>(strlen(pszMetadata) + osTemp.size() + 2);

            pszMetadata = (char *)CPLRealloc( pszMetadata, iLength );
            if ( !EQUAL( pszMetadata, "" ) )
                strncat( pszMetadata, ",", 1 );
            CPLStrlcat( pszMetadata, osTemp, iLength );
        }
    }

    return pszMetadata;
}

/************************************************************************/
/*                          GetMetadataElement()                        */
/************************************************************************/

int MrSIDDataset::GetMetadataElement( const char *pszKey, void *pValue,
                                      int iLength )
{
    if ( !poMetadata->has( pszKey ) )
        return FALSE;

    const LTIMetadataRecord *poMetadataRec = nullptr;
    poMetadata->get( pszKey, poMetadataRec );

    if ( poMetadataRec == nullptr || !poMetadataRec->isScalar() )
        return FALSE;

    // XXX: return FALSE if we have more than one element in metadata record
    int iSize;
    switch( poMetadataRec->getDataType() )
    {
         case LTI_METADATA_DATATYPE_UINT8:
         case LTI_METADATA_DATATYPE_SINT8:
             iSize = 1;
             break;
         case LTI_METADATA_DATATYPE_UINT16:
         case LTI_METADATA_DATATYPE_SINT16:
             iSize = 2;
             break;
         case LTI_METADATA_DATATYPE_UINT32:
         case LTI_METADATA_DATATYPE_SINT32:
         case LTI_METADATA_DATATYPE_FLOAT32:
             iSize = 4;
             break;
         case LTI_METADATA_DATATYPE_FLOAT64:
             iSize = 8;
             break;
         case LTI_METADATA_DATATYPE_ASCII:
             iSize = iLength;
             break;
         default:
             iSize = 0;
             break;
    }

    if ( poMetadataRec->getDataType() == LTI_METADATA_DATATYPE_ASCII )
    {
        strncpy( (char *)pValue,
                 ((const char**)poMetadataRec->getScalarData())[0], iSize );
        ((char *)pValue)[iSize - 1] = '\0';
    }
    else
        memcpy( pValue, poMetadataRec->getScalarData(), iSize );

    return TRUE;
}

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

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

    if (!osMETFilename.empty())
        papszFileList = CSLAddString(papszFileList, osMETFilename.c_str());

    return papszFileList;
}

/************************************************************************/
/*                             OpenZoomLevel()                          */
/************************************************************************/

CPLErr MrSIDDataset::OpenZoomLevel( lt_int32 iZoom )
{
/* -------------------------------------------------------------------- */
/*      Get image geometry.                                            */
/* -------------------------------------------------------------------- */
    if ( iZoom != 0 )
    {
        lt_uint32 iWidth, iHeight;
        dfCurrentMag = LTIUtils::levelToMag( iZoom );
        poImageReader->getDimsAtMag( dfCurrentMag, iWidth, iHeight );
        nRasterXSize = iWidth;
        nRasterYSize = iHeight;
    }
    else
    {
        dfCurrentMag = 1.0;
        nRasterXSize = poImageReader->getWidth();
        nRasterYSize = poImageReader->getHeight();
    }

    nBands = poImageReader->getNumBands();
    nBlockXSize = nRasterXSize;
    nBlockYSize = poImageReader->getStripHeight();

    CPLDebug( "MrSID", "Opened zoom level %d with size %dx%d.",
              iZoom, nRasterXSize, nRasterYSize );

    try
    {
        poLTINav = new LTIDLLNavigator<LTINavigator>( *poImageReader );
    }
    catch ( ... )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "MrSIDDataset::OpenZoomLevel(): "
                  "Failed to create LTINavigator object." );
        return CE_Failure;
    }

/* -------------------------------------------------------------------- */
/*  Handle sample type and color space.                                 */
/* -------------------------------------------------------------------- */
    eColorSpace = poImageReader->getColorSpace();
    eSampleType = poImageReader->getDataType();
    switch ( eSampleType )
    {
      case LTI_DATATYPE_UINT16:
        eDataType = GDT_UInt16;
        break;
      case LTI_DATATYPE_SINT16:
        eDataType = GDT_Int16;
        break;
      case LTI_DATATYPE_UINT32:
        eDataType = GDT_UInt32;
        break;
      case LTI_DATATYPE_SINT32:
        eDataType = GDT_Int32;
        break;
      case LTI_DATATYPE_FLOAT32:
        eDataType = GDT_Float32;
        break;
      case LTI_DATATYPE_FLOAT64:
        eDataType = GDT_Float64;
        break;
      case LTI_DATATYPE_UINT8:
      case LTI_DATATYPE_SINT8:
      default:
        eDataType = GDT_Byte;
        break;
    }

/* -------------------------------------------------------------------- */
/*      Read georeferencing.                                            */
/* -------------------------------------------------------------------- */
    if ( !poImageReader->isGeoCoordImplicit() )
    {
        const LTIGeoCoord& oGeo = poImageReader->getGeoCoord();
        oGeo.get( adfGeoTransform[0], adfGeoTransform[3],
                  adfGeoTransform[1], adfGeoTransform[5],
                  adfGeoTransform[2], adfGeoTransform[4] );

        adfGeoTransform[0] = adfGeoTransform[0] - adfGeoTransform[1] / 2;
        adfGeoTransform[3] = adfGeoTransform[3] - adfGeoTransform[5] / 2;
        bGeoTransformValid = TRUE;
    }
    else if( iZoom == 0 )
    {
        bGeoTransformValid =
            GDALReadWorldFile( GetDescription(), nullptr,
                               adfGeoTransform )
            || GDALReadWorldFile( GetDescription(), ".wld",
                                  adfGeoTransform );
    }

/* -------------------------------------------------------------------- */
/*      Read wkt.                                                       */
/* -------------------------------------------------------------------- */
#ifdef MRSID_HAVE_GETWKT
    if( !poImageReader->isGeoCoordImplicit() )
    {
        const LTIGeoCoord& oGeo = poImageReader->getGeoCoord();

        if( oGeo.getWKT() )
        {
            /* Workaround probable issue with GeoDSK 7 on 64bit Linux */
            if (!(pszProjection != nullptr && !STARTS_WITH_CI(pszProjection, "LOCAL_CS")
                && STARTS_WITH_CI(oGeo.getWKT(), "LOCAL_CS")))
            {
                CPLFree( pszProjection );
                pszProjection =  CPLStrdup( oGeo.getWKT() );
            }
        }
    }
#endif // HAVE_MRSID_GETWKT

/* -------------------------------------------------------------------- */
/*      Special case for https://zulu.ssc.nasa.gov/mrsid/mrsid.pl       */
/*      where LandSat .SID are accompanied by a .met file with the      */
/*      projection                                                      */
/* -------------------------------------------------------------------- */
    if (iZoom == 0 && (pszProjection == nullptr || pszProjection[0] == '\0') &&
        EQUAL(CPLGetExtension(GetDescription()), "sid"))
    {
        const char* pszMETFilename = CPLResetExtension(GetDescription(), "met");
        VSILFILE* fp = VSIFOpenL(pszMETFilename, "rb");
        if (fp)
        {
            const char* pszLine = nullptr;
            int nCountLine = 0;
            int nUTMZone = 0;
            int bWGS84 = FALSE;
            int bUnitsMeter = FALSE;
            while ( (pszLine = CPLReadLine2L(fp, 200, nullptr)) != nullptr &&
                    ++nCountLine < 1000 )
            {
                if (nCountLine == 1 && strcmp(pszLine, "::MetadataFile") != 0)
                    break;
                if (STARTS_WITH_CI(pszLine, "Projection UTM "))
                    nUTMZone = atoi(pszLine + 15);
                else if (EQUAL(pszLine, "Datum WGS84"))
                    bWGS84 = TRUE;
                else if (EQUAL(pszLine, "Units Meters"))
                    bUnitsMeter = TRUE;
            }
            VSIFCloseL(fp);

            /* Images in southern hemisphere have negative northings in the */
            /* .sdw file. A bit weird, but anyway we must use the northern */
            /* UTM SRS for consistency */
            if (nUTMZone >= 1 && nUTMZone <= 60 && bWGS84 && bUnitsMeter)
            {
                osMETFilename = pszMETFilename;

                OGRSpatialReference oSRS;
                oSRS.importFromEPSG(32600 + nUTMZone);
                CPLFree(pszProjection);
                pszProjection = nullptr;
                oSRS.exportToWkt(&pszProjection);
            }
        }
    }

/* -------------------------------------------------------------------- */
/*      Read NoData value.                                              */
/* -------------------------------------------------------------------- */
    poNDPixel = poImageReader->getNoDataPixel();

/* -------------------------------------------------------------------- */
/*      Create band information objects.                                */
/* -------------------------------------------------------------------- */
    int             iBand;

    for( iBand = 1; iBand <= nBands; iBand++ )
        SetBand( iBand, new MrSIDRasterBand( this, iBand ) );

    return CE_None;
}

/************************************************************************/
/*                         MrSIDIdentify()                              */
/*                                                                      */
/*          Identify method that only supports MrSID files.             */
/************************************************************************/

static int MrSIDIdentify( GDALOpenInfo * poOpenInfo )
{
    if( poOpenInfo->nHeaderBytes < 32 )
        return FALSE;

    if ( !STARTS_WITH_CI((const char *) poOpenInfo->pabyHeader, "msid") )
        return FALSE;

#if defined(LTI_SDK_MAJOR) && LTI_SDK_MAJOR >= 8
    lt_uint8 gen;
    bool raster;
    LT_STATUS eStat =
        MrSIDImageReaderInterface::getMrSIDGeneration(poOpenInfo->pabyHeader, gen, raster);
    if (!LT_SUCCESS(eStat) || !raster)
       return FALSE;
#endif

    return TRUE;
}

/************************************************************************/
/*                          MrSIDOpen()                                 */
/*                                                                      */
/*          Open method that only supports MrSID files.                 */
/************************************************************************/

static GDALDataset* MrSIDOpen( GDALOpenInfo *poOpenInfo )
{
    if (!MrSIDIdentify(poOpenInfo))
        return nullptr;

    return MrSIDDataset::Open( poOpenInfo, FALSE );
}

#ifdef MRSID_J2K

static const unsigned char jpc_header[] =
{0xff,0x4f};

/************************************************************************/
/*                         JP2Identify()                                */
/*                                                                      */
/*        Identify method that only supports JPEG2000 files.            */
/************************************************************************/

static int JP2Identify( GDALOpenInfo *poOpenInfo )
{
    if( poOpenInfo->nHeaderBytes < 32 )
        return FALSE;

    if( memcmp( poOpenInfo->pabyHeader, jpc_header, sizeof(jpc_header) ) == 0 )
    {
        const char *pszExtension = CPLGetExtension( poOpenInfo->pszFilename );

        if( !EQUAL(pszExtension,"jpc") && !EQUAL(pszExtension,"j2k")
            && !EQUAL(pszExtension,"jp2") && !EQUAL(pszExtension,"jpx")
            && !EQUAL(pszExtension,"j2c") && !EQUAL(pszExtension,"ntf"))
            return FALSE;
    }
    else if( !STARTS_WITH_CI((const char *) poOpenInfo->pabyHeader + 4, "jP  ") )
        return FALSE;

    return TRUE;
}

/************************************************************************/
/*                            JP2Open()                                 */
/*                                                                      */
/*      Open method that only supports JPEG2000 files.                  */
/************************************************************************/

static GDALDataset* JP2Open( GDALOpenInfo *poOpenInfo )
{
    if (!JP2Identify(poOpenInfo))
        return nullptr;

    return MrSIDDataset::Open( poOpenInfo, TRUE );
}

#endif // MRSID_J2K

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

GDALDataset *MrSIDDataset::Open( GDALOpenInfo * poOpenInfo, int bIsJP2 )
{
    if(poOpenInfo->fpL)
    {
        VSIFCloseL( poOpenInfo->fpL );
        poOpenInfo->fpL = nullptr;
    }

/* -------------------------------------------------------------------- */
/*      Make sure we have hooked CSV lookup for GDAL_DATA.              */
/* -------------------------------------------------------------------- */
    LibgeotiffOneTimeInit();

/* -------------------------------------------------------------------- */
/*      Create a corresponding GDALDataset.                             */
/* -------------------------------------------------------------------- */
    LT_STATUS       eStat;

    MrSIDDataset *poDS = new MrSIDDataset(bIsJP2);

    // try the LTIOFileStream first, since it uses filesystem caching
    eStat = poDS->oLTIStream.initialize( poOpenInfo->pszFilename, "rb" );
    if ( LT_SUCCESS(eStat) )
    {
        eStat = poDS->oLTIStream.open();
        if ( LT_SUCCESS(eStat) )
            poDS->poStream = &(poDS->oLTIStream);
    }

    // fall back on VSI for non-files
    if ( !LT_SUCCESS(eStat) || !poDS->poStream )
    {
        eStat = poDS->oVSIStream.initialize( poOpenInfo->pszFilename, "rb" );
        if ( !LT_SUCCESS(eStat) )
        {
            CPLError( CE_Failure, CPLE_AppDefined,
                      "LTIVSIStream::initialize(): "
                      "failed to open file \"%s\".\n%s",
                      poOpenInfo->pszFilename, getLastStatusString( eStat ) );
            delete poDS;
            return nullptr;
        }

        eStat = poDS->oVSIStream.open();
        if ( !LT_SUCCESS(eStat) )
        {
            CPLError( CE_Failure, CPLE_AppDefined,
                      "LTIVSIStream::open(): "
                      "failed to open file \"%s\".\n%s",
                      poOpenInfo->pszFilename, getLastStatusString( eStat ) );
            delete poDS;
            return nullptr;
        }

        poDS->poStream = &(poDS->oVSIStream);
    }

#if defined(LTI_SDK_MAJOR) && LTI_SDK_MAJOR >= 7

#ifdef MRSID_J2K
    if ( bIsJP2 )
    {
        J2KImageReader  *reader = J2KImageReader::create();
        eStat = reader->initialize( *(poDS->poStream) );
        poDS->poImageReader = reader;
    }
    else
#endif /* MRSID_J2K */
    {
        MrSIDImageReader    *reader = MrSIDImageReader::create();
        eStat = reader->initialize( poDS->poStream, nullptr );
        poDS->poImageReader = reader;
    }

#else /* LTI_SDK_MAJOR < 7 */

#ifdef MRSID_J2K
    if ( bIsJP2 )
    {
        poDS->poImageReader =
            new LTIDLLReader<J2KImageReader>( *(poDS->poStream), true );
        eStat = poDS->poImageReader->initialize();
    }
    else
#endif /* MRSID_J2K */
    {
        poDS->poImageReader =
            new LTIDLLReader<MrSIDImageReader>( poDS->poStream, nullptr );
        eStat = poDS->poImageReader->initialize();
    }

#endif /* LTI_SDK_MAJOR >= 7 */

    if ( !LT_SUCCESS(eStat) )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "LTIImageReader::initialize(): "
                  "failed to initialize reader from the stream \"%s\".\n%s",
                  poOpenInfo->pszFilename, getLastStatusString( eStat ) );
        delete poDS;
        return nullptr;
    }

/* -------------------------------------------------------------------- */
/*      Read metadata.                                                  */
/* -------------------------------------------------------------------- */
    poDS->poMetadata = new LTIDLLCopy<LTIMetadataDatabase>(
        poDS->poImageReader->getMetadata() );
    const GUInt32       iNumRecs = poDS->poMetadata->getIndexCount();

    for ( GUInt32 i = 0; i < iNumRecs; i++ )
    {
        const LTIMetadataRecord *poMetadataRec = nullptr;
        if ( LT_SUCCESS(poDS->poMetadata->getDataByIndex(i, poMetadataRec)) )
        {
            char    *pszElement = poDS->SerializeMetadataRec( poMetadataRec );
            char    *pszKey = CPLStrdup( poMetadataRec->getTagName() );
            char    *pszTemp = pszKey;

            // GDAL metadata keys should not contain ':' and '=' characters.
            // We will replace them with '_'.
            do
            {
                if ( *pszTemp == ':' || *pszTemp == '=' )
                    *pszTemp = '_';
            }
            while ( *++pszTemp );

            poDS->SetMetadataItem( pszKey, pszElement );

            CPLFree( pszElement );
            CPLFree( pszKey );
        }
    }

/* -------------------------------------------------------------------- */
/*      Add MrSID version.                                              */
/* -------------------------------------------------------------------- */
#ifdef MRSID_J2K
    if( !bIsJP2 )
#endif
    {
#if defined(LTI_SDK_MAJOR) && LTI_SDK_MAJOR >= 8
        lt_uint8 gen;
        bool raster;
        MrSIDImageReaderInterface::getMrSIDGeneration(poOpenInfo->pabyHeader, gen, raster);
        poDS->SetMetadataItem( "VERSION", CPLString().Printf("MG%d%s", gen, raster ? "" : " LiDAR") );
#else
        lt_uint8 major;
        lt_uint8 minor;
        char letter;
        MrSIDImageReader* poMrSIDImageReader = (MrSIDImageReader*)poDS->poImageReader;
        poMrSIDImageReader->getVersion(major, minor, minor, letter);
        if (major < 2)
            major = 2;
        poDS->SetMetadataItem( "VERSION", CPLString().Printf("MG%d", major) );
#endif
    }

    poDS->GetGTIFDefn();

/* -------------------------------------------------------------------- */
/*      Get number of resolution levels (we will use them as overviews).*/
/* -------------------------------------------------------------------- */
#ifdef MRSID_J2K
    if( bIsJP2 )
        poDS->nOverviewCount
            = ((J2KImageReader *) (poDS->poImageReader))->getNumLevels();
    else
#endif
        poDS->nOverviewCount
            = ((MrSIDImageReader *) (poDS->poImageReader))->getNumLevels();

    if ( poDS->nOverviewCount > 0 )
    {
        lt_int32        i;

        poDS->papoOverviewDS = (MrSIDDataset **)
            CPLMalloc( poDS->nOverviewCount * (sizeof(void*)) );

        for ( i = 0; i < poDS->nOverviewCount; i++ )
        {
            poDS->papoOverviewDS[i] = new MrSIDDataset(bIsJP2);
            poDS->papoOverviewDS[i]->poImageReader = poDS->poImageReader;
            poDS->papoOverviewDS[i]->OpenZoomLevel( i + 1 );
            poDS->papoOverviewDS[i]->bIsOverview = TRUE;
            poDS->papoOverviewDS[i]->poParentDS = poDS;
        }
    }

/* -------------------------------------------------------------------- */
/*      Create object for the whole image.                              */
/* -------------------------------------------------------------------- */
    poDS->SetDescription( poOpenInfo->pszFilename );
    poDS->OpenZoomLevel( 0 );

    CPLDebug( "MrSID",
              "Opened image: width %d, height %d, bands %d",
              poDS->nRasterXSize, poDS->nRasterYSize, poDS->nBands );

    if( poDS->nBands > 1 )
        poDS->SetMetadataItem( "INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE" );

    if (bIsJP2)
    {
        poDS->LoadJP2Metadata(poOpenInfo);
    }

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

/* -------------------------------------------------------------------- */
/*      Initialize the overview manager for mask band support.          */
/* -------------------------------------------------------------------- */
    poDS->oOvManager.Initialize( poDS, poOpenInfo->pszFilename );

    return poDS;
}

/************************************************************************/
/*                    EPSGProjMethodToCTProjMethod()                    */
/*                                                                      */
/*      Convert between the EPSG enumeration for projection methods,    */
/*      and the GeoTIFF CT codes.                                       */
/*      Explicitly copied from geo_normalize.c of the GeoTIFF package   */
/************************************************************************/

static int EPSGProjMethodToCTProjMethod( int nEPSG )

{
    /* see trf_method.csv for list of EPSG codes */

    switch( nEPSG )
    {
      case 9801:
        return CT_LambertConfConic_1SP;

      case 9802:
        return CT_LambertConfConic_2SP;

      case 9803:
        return CT_LambertConfConic_2SP;  // Belgian variant not supported.

      case 9804:
        return CT_Mercator;  // 1SP and 2SP not differentiated.

      case 9805:
        return CT_Mercator;  // 1SP and 2SP not differentiated.

      case 9806:
        return CT_CassiniSoldner;

      case 9807:
        return CT_TransverseMercator;

      case 9808:
        return CT_TransvMercator_SouthOriented;

      case 9809:
        return CT_ObliqueStereographic;

      case 9810:
        return CT_PolarStereographic;

      case 9811:
        return CT_NewZealandMapGrid;

      case 9812:
        return CT_ObliqueMercator;  // Is hotine actually different?

      case 9813:
        return CT_ObliqueMercator_Laborde;

      case 9814:
        return CT_ObliqueMercator_Rosenmund;  // Swiss.

      case 9815:
        return CT_ObliqueMercator;

      case 9816: /* tunesia mining grid has no counterpart */
        return KvUserDefined;
    }

    return KvUserDefined;
}

/* EPSG Codes for projection parameters.  Unfortunately, these bear no
   relationship to the GeoTIFF codes even though the names are so similar. */

#define EPSGNatOriginLat         8801
#define EPSGNatOriginLong        8802
#define EPSGNatOriginScaleFactor 8805
#define EPSGFalseEasting         8806
#define EPSGFalseNorthing        8807
#define EPSGProjCenterLat        8811
#define EPSGProjCenterLong       8812
#define EPSGAzimuth              8813
#define EPSGAngleRectifiedToSkewedGrid 8814
#define EPSGInitialLineScaleFactor 8815
#define EPSGProjCenterEasting    8816
#define EPSGProjCenterNorthing   8817
#define EPSGPseudoStdParallelLat 8818
#define EPSGPseudoStdParallelScaleFactor 8819
#define EPSGFalseOriginLat       8821
#define EPSGFalseOriginLong      8822
#define EPSGStdParallel1Lat      8823
#define EPSGStdParallel2Lat      8824
#define EPSGFalseOriginEasting   8826
#define EPSGFalseOriginNorthing  8827
#define EPSGSphericalOriginLat   8828
#define EPSGSphericalOriginLong  8829
#define EPSGInitialLongitude     8830
#define EPSGZoneWidth            8831

/************************************************************************/
/*                            SetGTParmIds()                            */
/*                                                                      */
/*      This is hardcoded logic to set the GeoTIFF parameter            */
/*      identifiers for all the EPSG supported projections.  As the     */
/*      trf_method.csv table grows with new projections, this code      */
/*      will need to be updated.                                        */
/*      Explicitly copied from geo_normalize.c of the GeoTIFF package.  */
/************************************************************************/

static int SetGTParmIds( int nCTProjection,
                         int *panProjParmId,
                         int *panEPSGCodes )

{
    int anWorkingDummy[7];

    if( panEPSGCodes == nullptr )
        panEPSGCodes = anWorkingDummy;
    if( panProjParmId == nullptr )
        panProjParmId = anWorkingDummy;

    memset( panEPSGCodes, 0, sizeof(int) * 7 );

    /* psDefn->nParms = 7; */

    switch( nCTProjection )
    {
      case CT_CassiniSoldner:
      case CT_NewZealandMapGrid:
        panProjParmId[0] = ProjNatOriginLatGeoKey;
        panProjParmId[1] = ProjNatOriginLongGeoKey;
        panProjParmId[5] = ProjFalseEastingGeoKey;
        panProjParmId[6] = ProjFalseNorthingGeoKey;

        panEPSGCodes[0] = EPSGNatOriginLat;
        panEPSGCodes[1] = EPSGNatOriginLong;
        panEPSGCodes[5] = EPSGFalseEasting;
        panEPSGCodes[6] = EPSGFalseNorthing;
        return TRUE;

      case CT_ObliqueMercator:
        panProjParmId[0] = ProjCenterLatGeoKey;
        panProjParmId[1] = ProjCenterLongGeoKey;
        panProjParmId[2] = ProjAzimuthAngleGeoKey;
        panProjParmId[3] = ProjRectifiedGridAngleGeoKey;
        panProjParmId[4] = ProjScaleAtCenterGeoKey;
        panProjParmId[5] = ProjFalseEastingGeoKey;
        panProjParmId[6] = ProjFalseNorthingGeoKey;

        panEPSGCodes[0] = EPSGProjCenterLat;
        panEPSGCodes[1] = EPSGProjCenterLong;
        panEPSGCodes[2] = EPSGAzimuth;
        panEPSGCodes[3] = EPSGAngleRectifiedToSkewedGrid;
        panEPSGCodes[4] = EPSGInitialLineScaleFactor;
        panEPSGCodes[5] = EPSGProjCenterEasting;
        panEPSGCodes[6] = EPSGProjCenterNorthing;
        return TRUE;

      case CT_ObliqueMercator_Laborde:
        panProjParmId[0] = ProjCenterLatGeoKey;
        panProjParmId[1] = ProjCenterLongGeoKey;
        panProjParmId[2] = ProjAzimuthAngleGeoKey;
        panProjParmId[4] = ProjScaleAtCenterGeoKey;
        panProjParmId[5] = ProjFalseEastingGeoKey;
        panProjParmId[6] = ProjFalseNorthingGeoKey;

        panEPSGCodes[0] = EPSGProjCenterLat;
        panEPSGCodes[1] = EPSGProjCenterLong;
        panEPSGCodes[2] = EPSGAzimuth;
        panEPSGCodes[4] = EPSGInitialLineScaleFactor;
        panEPSGCodes[5] = EPSGProjCenterEasting;
        panEPSGCodes[6] = EPSGProjCenterNorthing;
        return TRUE;

      case CT_LambertConfConic_1SP:
      case CT_Mercator:
      case CT_ObliqueStereographic:
      case CT_PolarStereographic:
      case CT_TransverseMercator:
      case CT_TransvMercator_SouthOriented:
        panProjParmId[0] = ProjNatOriginLatGeoKey;
        panProjParmId[1] = ProjNatOriginLongGeoKey;
        panProjParmId[4] = ProjScaleAtNatOriginGeoKey;
        panProjParmId[5] = ProjFalseEastingGeoKey;
        panProjParmId[6] = ProjFalseNorthingGeoKey;

        panEPSGCodes[0] = EPSGNatOriginLat;
        panEPSGCodes[1] = EPSGNatOriginLong;
        panEPSGCodes[4] = EPSGNatOriginScaleFactor;
        panEPSGCodes[5] = EPSGFalseEasting;
        panEPSGCodes[6] = EPSGFalseNorthing;
        return TRUE;

      case CT_LambertConfConic_2SP:
        panProjParmId[0] = ProjFalseOriginLatGeoKey;
        panProjParmId[1] = ProjFalseOriginLongGeoKey;
        panProjParmId[2] = ProjStdParallel1GeoKey;
        panProjParmId[3] = ProjStdParallel2GeoKey;
        panProjParmId[5] = ProjFalseEastingGeoKey;
        panProjParmId[6] = ProjFalseNorthingGeoKey;

        panEPSGCodes[0] = EPSGFalseOriginLat;
        panEPSGCodes[1] = EPSGFalseOriginLong;
        panEPSGCodes[2] = EPSGStdParallel1Lat;
        panEPSGCodes[3] = EPSGStdParallel2Lat;
        panEPSGCodes[5] = EPSGFalseOriginEasting;
        panEPSGCodes[6] = EPSGFalseOriginNorthing;
        return TRUE;

      case CT_SwissObliqueCylindrical:
        panProjParmId[0] = ProjCenterLatGeoKey;
        panProjParmId[1] = ProjCenterLongGeoKey;
        panProjParmId[5] = ProjFalseEastingGeoKey;
        panProjParmId[6] = ProjFalseNorthingGeoKey;

        /* EPSG codes? */
        return TRUE;

      default:
        return FALSE;
    }
}

static const char * const papszDatumEquiv[] =
{
    "Militar_Geographische_Institut",
    "Militar_Geographische_Institute",
    "World_Geodetic_System_1984",
    "WGS_1984",
    "WGS_72_Transit_Broadcast_Ephemeris",
    "WGS_1972_Transit_Broadcast_Ephemeris",
    "World_Geodetic_System_1972",
    "WGS_1972",
    "European_Terrestrial_Reference_System_89",
    "European_Reference_System_1989",
    nullptr
};

/************************************************************************/
/*                          WKTMassageDatum()                           */
/*                                                                      */
/*      Massage an EPSG datum name into WMT format.  Also transform     */
/*      specific exception cases into WKT versions.                     */
/*      Explicitly copied from the gt_wkt_srs.cpp.                      */
/************************************************************************/

static void WKTMassageDatum( char ** ppszDatum )

{
    int         i, j;
    char        *pszDatum = *ppszDatum;

    if (pszDatum[0] == '\0')
        return;

/* -------------------------------------------------------------------- */
/*      Translate non-alphanumeric values to underscores.               */
/* -------------------------------------------------------------------- */
    for( i = 0; pszDatum[i] != '\0'; i++ )
    {
        if( !(pszDatum[i] >= 'A' && pszDatum[i] <= 'Z')
            && !(pszDatum[i] >= 'a' && pszDatum[i] <= 'z')
            && !(pszDatum[i] >= '0' && pszDatum[i] <= '9') )
        {
            pszDatum[i] = '_';
        }
    }

/* -------------------------------------------------------------------- */
/*      Remove repeated and trailing underscores.                       */
/* -------------------------------------------------------------------- */
    for( i = 1, j = 0; pszDatum[i] != '\0'; i++ )
    {
        if( pszDatum[j] == '_' && pszDatum[i] == '_' )
            continue;

        pszDatum[++j] = pszDatum[i];
    }
    if( pszDatum[j] == '_' )
        pszDatum[j] = '\0';
    else
        pszDatum[j+1] = '\0';

/* -------------------------------------------------------------------- */
/*      Search for datum equivalences.  Specific massaged names get     */
/*      mapped to OpenGIS specified names.                              */
/* -------------------------------------------------------------------- */
    for( i = 0; papszDatumEquiv[i] != nullptr; i += 2 )
    {
        if( EQUAL(*ppszDatum,papszDatumEquiv[i]) )
        {
            CPLFree( *ppszDatum );
            *ppszDatum = CPLStrdup( papszDatumEquiv[i+1] );
            return;
        }
    }
}

/************************************************************************/
/*                           FetchProjParms()                           */
/*                                                                      */
/*      Fetch the projection parameters for a particular projection     */
/*      from MrSID metadata, and fill the GTIFDefn structure out        */
/*      with them.                                                      */
/*      Copied from geo_normalize.c of the GeoTIFF package.             */
/************************************************************************/

void MrSIDDataset::FetchProjParms()
{
    double dfNatOriginLong = 0.0, dfNatOriginLat = 0.0, dfRectGridAngle = 0.0;
    double dfFalseEasting = 0.0, dfFalseNorthing = 0.0, dfNatOriginScale = 1.0;
    double dfStdParallel1 = 0.0, dfStdParallel2 = 0.0, dfAzimuth = 0.0;

/* -------------------------------------------------------------------- */
/*      Get the false easting, and northing if available.               */
/* -------------------------------------------------------------------- */
    if( !GetMetadataElement( "GEOTIFF_NUM::3082::ProjFalseEastingGeoKey",
                             &dfFalseEasting )
        && !GetMetadataElement( "GEOTIFF_NUM::3090:ProjCenterEastingGeoKey",
                                &dfFalseEasting ) )
        dfFalseEasting = 0.0;

    if( !GetMetadataElement( "GEOTIFF_NUM::3083::ProjFalseNorthingGeoKey",
                             &dfFalseNorthing )
        && !GetMetadataElement( "GEOTIFF_NUM::3091::ProjCenterNorthingGeoKey",
                                &dfFalseNorthing ) )
        dfFalseNorthing = 0.0;

    switch( psDefn->CTProjection )
    {
/* -------------------------------------------------------------------- */
      case CT_Stereographic:
/* -------------------------------------------------------------------- */
        if( GetMetadataElement( "GEOTIFF_NUM::3080::ProjNatOriginLongGeoKey",
                                &dfNatOriginLong ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3084::ProjFalseOriginLongGeoKey",
                                   &dfNatOriginLong ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3088::ProjCenterLongGeoKey",
                                   &dfNatOriginLong ) == 0 )
            dfNatOriginLong = 0.0;

        if( GetMetadataElement( "GEOTIFF_NUM::3081::ProjNatOriginLatGeoKey",
                                &dfNatOriginLat ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3085::ProjFalseOriginLatGeoKey",
                                   &dfNatOriginLat ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3089::ProjCenterLatGeoKey",
                                   &dfNatOriginLat ) == 0 )
            dfNatOriginLat = 0.0;

        if( GetMetadataElement( "GEOTIFF_NUM::3092::ProjScaleAtNatOriginGeoKey",
                                &dfNatOriginScale ) == 0 )
            dfNatOriginScale = 1.0;

        /* notdef: should transform to decimal degrees at this point */

        psDefn->ProjParm[0] = dfNatOriginLat;
        psDefn->ProjParmId[0] = ProjCenterLatGeoKey;
        psDefn->ProjParm[1] = dfNatOriginLong;
        psDefn->ProjParmId[1] = ProjCenterLongGeoKey;
        psDefn->ProjParm[4] = dfNatOriginScale;
        psDefn->ProjParmId[4] = ProjScaleAtNatOriginGeoKey;
        psDefn->ProjParm[5] = dfFalseEasting;
        psDefn->ProjParmId[5] = ProjFalseEastingGeoKey;
        psDefn->ProjParm[6] = dfFalseNorthing;
        psDefn->ProjParmId[6] = ProjFalseNorthingGeoKey;

        psDefn->nParms = 7;
        break;

/* -------------------------------------------------------------------- */
      case CT_LambertConfConic_1SP:
      case CT_Mercator:
      case CT_ObliqueStereographic:
      case CT_TransverseMercator:
      case CT_TransvMercator_SouthOriented:
/* -------------------------------------------------------------------- */
        if( GetMetadataElement( "GEOTIFF_NUM::3080::ProjNatOriginLongGeoKey",
                                &dfNatOriginLong ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3084::ProjFalseOriginLongGeoKey",
                                   &dfNatOriginLong ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3088::ProjCenterLongGeoKey",
                                   &dfNatOriginLong ) == 0 )
            dfNatOriginLong = 0.0;

        if( GetMetadataElement( "GEOTIFF_NUM::3081::ProjNatOriginLatGeoKey",
                                &dfNatOriginLat ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3085::ProjFalseOriginLatGeoKey",
                                   &dfNatOriginLat ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3089::ProjCenterLatGeoKey",
                                   &dfNatOriginLat ) == 0 )
            dfNatOriginLat = 0.0;

        if( GetMetadataElement( "GEOTIFF_NUM::3092::ProjScaleAtNatOriginGeoKey",
                                &dfNatOriginScale ) == 0 )
            dfNatOriginScale = 1.0;

        /* notdef: should transform to decimal degrees at this point */

        psDefn->ProjParm[0] = dfNatOriginLat;
        psDefn->ProjParmId[0] = ProjNatOriginLatGeoKey;
        psDefn->ProjParm[1] = dfNatOriginLong;
        psDefn->ProjParmId[1] = ProjNatOriginLongGeoKey;
        psDefn->ProjParm[4] = dfNatOriginScale;
        psDefn->ProjParmId[4] = ProjScaleAtNatOriginGeoKey;
        psDefn->ProjParm[5] = dfFalseEasting;
        psDefn->ProjParmId[5] = ProjFalseEastingGeoKey;
        psDefn->ProjParm[6] = dfFalseNorthing;
        psDefn->ProjParmId[6] = ProjFalseNorthingGeoKey;

        psDefn->nParms = 7;
        break;

/* -------------------------------------------------------------------- */
      case CT_ObliqueMercator: /* hotine */
/* -------------------------------------------------------------------- */
        if( GetMetadataElement( "GEOTIFF_NUM::3080::ProjNatOriginLongGeoKey",
                                &dfNatOriginLong ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3084::ProjFalseOriginLongGeoKey",
                                   &dfNatOriginLong ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3088::ProjCenterLongGeoKey",
                                   &dfNatOriginLong ) == 0 )
            dfNatOriginLong = 0.0;

        if( GetMetadataElement( "GEOTIFF_NUM::3081::ProjNatOriginLatGeoKey",
                                &dfNatOriginLat ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3085::ProjFalseOriginLatGeoKey",
                                   &dfNatOriginLat ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3089::ProjCenterLatGeoKey",
                                   &dfNatOriginLat ) == 0 )
            dfNatOriginLat = 0.0;

        if( GetMetadataElement( "GEOTIFF_NUM::3094::ProjAzimuthAngleGeoKey",
                                &dfAzimuth ) == 0 )
            dfAzimuth = 0.0;

        if( GetMetadataElement( "GEOTIFF_NUM::3096::ProjRectifiedGridAngleGeoKey",
                                &dfRectGridAngle ) == 0 )
            dfRectGridAngle = 90.0;

        if( GetMetadataElement( "GEOTIFF_NUM::3092::ProjScaleAtNatOriginGeoKey",
                                &dfNatOriginScale ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3093::ProjScaleAtCenterGeoKey",
                                   &dfNatOriginScale ) == 0 )
            dfNatOriginScale = 1.0;

        /* notdef: should transform to decimal degrees at this point */

        psDefn->ProjParm[0] = dfNatOriginLat;
        psDefn->ProjParmId[0] = ProjCenterLatGeoKey;
        psDefn->ProjParm[1] = dfNatOriginLong;
        psDefn->ProjParmId[1] = ProjCenterLongGeoKey;
        psDefn->ProjParm[2] = dfAzimuth;
        psDefn->ProjParmId[2] = ProjAzimuthAngleGeoKey;
        psDefn->ProjParm[3] = dfRectGridAngle;
        psDefn->ProjParmId[3] = ProjRectifiedGridAngleGeoKey;
        psDefn->ProjParm[4] = dfNatOriginScale;
        psDefn->ProjParmId[4] = ProjScaleAtCenterGeoKey;
        psDefn->ProjParm[5] = dfFalseEasting;
        psDefn->ProjParmId[5] = ProjFalseEastingGeoKey;
        psDefn->ProjParm[6] = dfFalseNorthing;
        psDefn->ProjParmId[6] = ProjFalseNorthingGeoKey;

        psDefn->nParms = 7;
        break;

/* -------------------------------------------------------------------- */
      case CT_CassiniSoldner:
      case CT_Polyconic:
/* -------------------------------------------------------------------- */
        if( GetMetadataElement( "GEOTIFF_NUM::3080::ProjNatOriginLongGeoKey",
                                &dfNatOriginLong ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3084::ProjFalseOriginLongGeoKey",
                                   &dfNatOriginLong ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3088::ProjCenterLongGeoKey",
                                   &dfNatOriginLong ) == 0 )
            dfNatOriginLong = 0.0;

        if( GetMetadataElement( "GEOTIFF_NUM::3081::ProjNatOriginLatGeoKey",
                                &dfNatOriginLat ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3085::ProjFalseOriginLatGeoKey",
                                   &dfNatOriginLat ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3089::ProjCenterLatGeoKey",
                                   &dfNatOriginLat ) == 0 )
            dfNatOriginLat = 0.0;

        if( GetMetadataElement( "GEOTIFF_NUM::3092::ProjScaleAtNatOriginGeoKey",
                                &dfNatOriginScale ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3093::ProjScaleAtCenterGeoKey",
                                   &dfNatOriginScale ) == 0 )
            dfNatOriginScale = 1.0;

        /* notdef: should transform to decimal degrees at this point */

        psDefn->ProjParm[0] = dfNatOriginLat;
        psDefn->ProjParmId[0] = ProjNatOriginLatGeoKey;
        psDefn->ProjParm[1] = dfNatOriginLong;
        psDefn->ProjParmId[1] = ProjNatOriginLongGeoKey;
        psDefn->ProjParm[4] = dfNatOriginScale;
        psDefn->ProjParmId[4] = ProjScaleAtNatOriginGeoKey;
        psDefn->ProjParm[5] = dfFalseEasting;
        psDefn->ProjParmId[5] = ProjFalseEastingGeoKey;
        psDefn->ProjParm[6] = dfFalseNorthing;
        psDefn->ProjParmId[6] = ProjFalseNorthingGeoKey;

        psDefn->nParms = 7;
        break;

/* -------------------------------------------------------------------- */
      case CT_AzimuthalEquidistant:
      case CT_MillerCylindrical:
      case CT_Equirectangular:
      case CT_Gnomonic:
      case CT_LambertAzimEqualArea:
      case CT_Orthographic:
/* -------------------------------------------------------------------- */
        if( GetMetadataElement( "GEOTIFF_NUM::3080::ProjNatOriginLongGeoKey",
                                &dfNatOriginLong ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3084::ProjFalseOriginLongGeoKey",
                                   &dfNatOriginLong ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3088::ProjCenterLongGeoKey",
                                   &dfNatOriginLong ) == 0 )
            dfNatOriginLong = 0.0;

        if( GetMetadataElement( "GEOTIFF_NUM::3081::ProjNatOriginLatGeoKey",
                                &dfNatOriginLat ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3085::ProjFalseOriginLatGeoKey",
                                   &dfNatOriginLat ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3089::ProjCenterLatGeoKey",
                                   &dfNatOriginLat ) == 0 )
            dfNatOriginLat = 0.0;

        /* notdef: should transform to decimal degrees at this point */

        psDefn->ProjParm[0] = dfNatOriginLat;
        psDefn->ProjParmId[0] = ProjCenterLatGeoKey;
        psDefn->ProjParm[1] = dfNatOriginLong;
        psDefn->ProjParmId[1] = ProjCenterLongGeoKey;
        psDefn->ProjParm[5] = dfFalseEasting;
        psDefn->ProjParmId[5] = ProjFalseEastingGeoKey;
        psDefn->ProjParm[6] = dfFalseNorthing;
        psDefn->ProjParmId[6] = ProjFalseNorthingGeoKey;

        psDefn->nParms = 7;
        break;

/* -------------------------------------------------------------------- */
      case CT_Robinson:
      case CT_Sinusoidal:
      case CT_VanDerGrinten:
/* -------------------------------------------------------------------- */
        if( GetMetadataElement( "GEOTIFF_NUM::3080::ProjNatOriginLongGeoKey",
                                &dfNatOriginLong ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3084::ProjFalseOriginLongGeoKey",
                                   &dfNatOriginLong ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3088::ProjCenterLongGeoKey",
                                   &dfNatOriginLong ) == 0 )
            dfNatOriginLong = 0.0;

        /* notdef: should transform to decimal degrees at this point */

        psDefn->ProjParm[1] = dfNatOriginLong;
        psDefn->ProjParmId[1] = ProjCenterLongGeoKey;
        psDefn->ProjParm[5] = dfFalseEasting;
        psDefn->ProjParmId[5] = ProjFalseEastingGeoKey;
        psDefn->ProjParm[6] = dfFalseNorthing;
        psDefn->ProjParmId[6] = ProjFalseNorthingGeoKey;

        psDefn->nParms = 7;
        break;

/* -------------------------------------------------------------------- */
      case CT_PolarStereographic:
/* -------------------------------------------------------------------- */
        if( GetMetadataElement( "GEOTIFF_NUM::3095::ProjStraightVertPoleLongGeoKey",
                                &dfNatOriginLong ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3080::ProjNatOriginLongGeoKey",
                                   &dfNatOriginLong ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3084::ProjFalseOriginLongGeoKey",
                                   &dfNatOriginLong ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3088::ProjCenterLongGeoKey",
                                   &dfNatOriginLong ) == 0 )
            dfNatOriginLong = 0.0;

        if( GetMetadataElement( "GEOTIFF_NUM::3081::ProjNatOriginLatGeoKey",
                                &dfNatOriginLat ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3085::ProjFalseOriginLatGeoKey",
                                   &dfNatOriginLat ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3089::ProjCenterLatGeoKey",
                                   &dfNatOriginLat ) == 0 )
            dfNatOriginLat = 0.0;

        if( GetMetadataElement( "GEOTIFF_NUM::3092::ProjScaleAtNatOriginGeoKey",
                                &dfNatOriginScale ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3093::ProjScaleAtCenterGeoKey",
                                   &dfNatOriginScale ) == 0 )
            dfNatOriginScale = 1.0;

        /* notdef: should transform to decimal degrees at this point */

        psDefn->ProjParm[0] = dfNatOriginLat;
        psDefn->ProjParmId[0] = ProjNatOriginLatGeoKey;
        psDefn->ProjParm[1] = dfNatOriginLong;
        psDefn->ProjParmId[1] = ProjStraightVertPoleLongGeoKey;
        psDefn->ProjParm[4] = dfNatOriginScale;
        psDefn->ProjParmId[4] = ProjScaleAtNatOriginGeoKey;
        psDefn->ProjParm[5] = dfFalseEasting;
        psDefn->ProjParmId[5] = ProjFalseEastingGeoKey;
        psDefn->ProjParm[6] = dfFalseNorthing;
        psDefn->ProjParmId[6] = ProjFalseNorthingGeoKey;

        psDefn->nParms = 7;
        break;

/* -------------------------------------------------------------------- */
      case CT_LambertConfConic_2SP:
/* -------------------------------------------------------------------- */
        if( GetMetadataElement( "GEOTIFF_NUM::3078::ProjStdParallel1GeoKey",
                                &dfStdParallel1 ) == 0 )
            dfStdParallel1 = 0.0;

        if( GetMetadataElement( "GEOTIFF_NUM::3079::ProjStdParallel2GeoKey",
                                &dfStdParallel2 ) == 0 )
            dfStdParallel1 = 0.0;

        if( GetMetadataElement( "GEOTIFF_NUM::3080::ProjNatOriginLongGeoKey",
                                &dfNatOriginLong ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3084::ProjFalseOriginLongGeoKey",
                                   &dfNatOriginLong ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3088::ProjCenterLongGeoKey",
                                   &dfNatOriginLong ) == 0 )
            dfNatOriginLong = 0.0;

        if( GetMetadataElement( "GEOTIFF_NUM::3081::ProjNatOriginLatGeoKey",
                                &dfNatOriginLat ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3085::ProjFalseOriginLatGeoKey",
                                   &dfNatOriginLat ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3089::ProjCenterLatGeoKey",
                                   &dfNatOriginLat ) == 0 )
            dfNatOriginLat = 0.0;

        /* notdef: should transform to decimal degrees at this point */

        psDefn->ProjParm[0] = dfNatOriginLat;
        psDefn->ProjParmId[0] = ProjFalseOriginLatGeoKey;
        psDefn->ProjParm[1] = dfNatOriginLong;
        psDefn->ProjParmId[1] = ProjFalseOriginLongGeoKey;
        psDefn->ProjParm[2] = dfStdParallel1;
        psDefn->ProjParmId[2] = ProjStdParallel1GeoKey;
        psDefn->ProjParm[3] = dfStdParallel2;
        psDefn->ProjParmId[3] = ProjStdParallel2GeoKey;
        psDefn->ProjParm[5] = dfFalseEasting;
        psDefn->ProjParmId[5] = ProjFalseEastingGeoKey;
        psDefn->ProjParm[6] = dfFalseNorthing;
        psDefn->ProjParmId[6] = ProjFalseNorthingGeoKey;

        psDefn->nParms = 7;
        break;

/* -------------------------------------------------------------------- */
      case CT_AlbersEqualArea:
      case CT_EquidistantConic:
/* -------------------------------------------------------------------- */
        if( GetMetadataElement( "GEOTIFF_NUM::3078::ProjStdParallel1GeoKey",
                                &dfStdParallel1 ) == 0 )
            dfStdParallel1 = 0.0;

        if( GetMetadataElement( "GEOTIFF_NUM::3079::ProjStdParallel2GeoKey",
                                &dfStdParallel2 ) == 0 )
            dfStdParallel1 = 0.0;

        if( GetMetadataElement( "GEOTIFF_NUM::3080::ProjNatOriginLongGeoKey",
                                &dfNatOriginLong ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3084::ProjFalseOriginLongGeoKey",
                                   &dfNatOriginLong ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3088::ProjCenterLongGeoKey",
                                   &dfNatOriginLong ) == 0 )
            dfNatOriginLong = 0.0;

        if( GetMetadataElement( "GEOTIFF_NUM::3081::ProjNatOriginLatGeoKey",
                                &dfNatOriginLat ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3085::ProjFalseOriginLatGeoKey",
                                   &dfNatOriginLat ) == 0
            && GetMetadataElement( "GEOTIFF_NUM::3089::ProjCenterLatGeoKey",
                                   &dfNatOriginLat ) == 0 )
            dfNatOriginLat = 0.0;

        /* notdef: should transform to decimal degrees at this point */

        psDefn->ProjParm[0] = dfStdParallel1;
        psDefn->ProjParmId[0] = ProjStdParallel1GeoKey;
        psDefn->ProjParm[1] = dfStdParallel2;
        psDefn->ProjParmId[1] = ProjStdParallel2GeoKey;
        psDefn->ProjParm[2] = dfNatOriginLat;
        psDefn->ProjParmId[2] = ProjNatOriginLatGeoKey;
        psDefn->ProjParm[3] = dfNatOriginLong;
        psDefn->ProjParmId[3] = ProjNatOriginLongGeoKey;
        psDefn->ProjParm[5] = dfFalseEasting;
        psDefn->ProjParmId[5] = ProjFalseEastingGeoKey;
        psDefn->ProjParm[6] = dfFalseNorthing;
        psDefn->ProjParmId[6] = ProjFalseNorthingGeoKey;

        psDefn->nParms = 7;
        break;
    }
}

/************************************************************************/
/*                            GetGTIFDefn()                             */
/*      This function borrowed from the GTIFGetDefn() function.         */
/*      See geo_normalize.c from the GeoTIFF package.                   */
/************************************************************************/

void MrSIDDataset::GetGTIFDefn()
{
    double      dfInvFlattening;

/* -------------------------------------------------------------------- */
/*      Make sure we have hooked CSV lookup for GDAL_DATA.              */
/* -------------------------------------------------------------------- */
    LibgeotiffOneTimeInit();

/* -------------------------------------------------------------------- */
/*      Initially we default all the information we can.                */
/* -------------------------------------------------------------------- */
    psDefn = new( GTIFDefn );
    psDefn->Model = KvUserDefined;
    psDefn->PCS = KvUserDefined;
    psDefn->GCS = KvUserDefined;
    psDefn->UOMLength = KvUserDefined;
    psDefn->UOMLengthInMeters = 1.0;
    psDefn->UOMAngle = KvUserDefined;
    psDefn->UOMAngleInDegrees = 1.0;
    psDefn->Datum = KvUserDefined;
    psDefn->Ellipsoid = KvUserDefined;
    psDefn->SemiMajor = 0.0;
    psDefn->SemiMinor = 0.0;
    psDefn->PM = KvUserDefined;
    psDefn->PMLongToGreenwich = 0.0;

    psDefn->ProjCode = KvUserDefined;
    psDefn->Projection = KvUserDefined;
    psDefn->CTProjection = KvUserDefined;

    psDefn->nParms = 0;
    for( int i = 0; i < MAX_GTIF_PROJPARMS; i++ )
    {
        psDefn->ProjParm[i] = 0.0;
        psDefn->ProjParmId[i] = 0;
    }

    psDefn->MapSys = KvUserDefined;
    psDefn->Zone = 0;

/* -------------------------------------------------------------------- */
/*      Try to get the overall model type.                              */
/* -------------------------------------------------------------------- */
    GetMetadataElement( "GEOTIFF_NUM::1024::GTModelTypeGeoKey",
                        &(psDefn->Model) );

/* -------------------------------------------------------------------- */
/*      Try to get a PCS.                                               */
/* -------------------------------------------------------------------- */
    if( GetMetadataElement( "GEOTIFF_NUM::3072::ProjectedCSTypeGeoKey",
                            &(psDefn->PCS) )
        && psDefn->PCS != KvUserDefined )
    {
        /*
         * Translate this into useful information.
         */
        GTIFGetPCSInfo( psDefn->PCS, nullptr, &(psDefn->ProjCode),
                        &(psDefn->UOMLength), &(psDefn->GCS) );
    }

/* -------------------------------------------------------------------- */
/*       If we have the PCS code, but didn't find it in the CSV files   */
/*      (likely because we can't find them) we will try some ``jiffy    */
/*      rules'' for UTM and state plane.                                */
/* -------------------------------------------------------------------- */
    if( psDefn->PCS != KvUserDefined && psDefn->ProjCode == KvUserDefined )
    {
        int     nMapSys, nZone;
        int     nGCS = psDefn->GCS;

        nMapSys = GTIFPCSToMapSys( psDefn->PCS, &nGCS, &nZone );
        if( nMapSys != KvUserDefined )
        {
            psDefn->ProjCode = (short) GTIFMapSysToProj( nMapSys, nZone );
            psDefn->GCS = (short) nGCS;
        }
    }

/* -------------------------------------------------------------------- */
/*      If the Proj_ code is specified directly, use that.              */
/* -------------------------------------------------------------------- */
    if( psDefn->ProjCode == KvUserDefined )
        GetMetadataElement( "GEOTIFF_NUM::3074::ProjectionGeoKey",
                            &(psDefn->ProjCode) );

    if( psDefn->ProjCode != KvUserDefined )
    {
        /*
         * We have an underlying projection transformation value.  Look
         * this up.  For a PCS of ``WGS 84 / UTM 11'' the transformation
         * would be Transverse Mercator, with a particular set of options.
         * The nProjTRFCode itself would correspond to the name
         * ``UTM zone 11N'', and doesn't include datum info.
         */
        GTIFGetProjTRFInfo( psDefn->ProjCode, nullptr, &(psDefn->Projection),
                            psDefn->ProjParm );

        /*
         * Set the GeoTIFF identity of the parameters.
         */
        psDefn->CTProjection = (short)
            EPSGProjMethodToCTProjMethod( psDefn->Projection );

        SetGTParmIds( psDefn->CTProjection, psDefn->ProjParmId, nullptr);
        psDefn->nParms = 7;
    }

/* -------------------------------------------------------------------- */
/*      Try to get a GCS.  If found, it will override any implied by    */
/*      the PCS.                                                        */
/* -------------------------------------------------------------------- */
    GetMetadataElement( "GEOTIFF_NUM::2048::GeographicTypeGeoKey",
                        &(psDefn->GCS) );

/* -------------------------------------------------------------------- */
/*      Derive the datum, and prime meridian from the GCS.              */
/* -------------------------------------------------------------------- */
    if( psDefn->GCS != KvUserDefined )
    {
        GTIFGetGCSInfo( psDefn->GCS, nullptr, &(psDefn->Datum), &(psDefn->PM),
                        &(psDefn->UOMAngle) );
    }

/* -------------------------------------------------------------------- */
/*      Handle the GCS angular units.  GeogAngularUnitsGeoKey           */
/*      overrides the GCS or PCS setting.                               */
/* -------------------------------------------------------------------- */
    GetMetadataElement( "GEOTIFF_NUM::2054::GeogAngularUnitsGeoKey",
                        &(psDefn->UOMAngle) );
    if( psDefn->UOMAngle != KvUserDefined )
    {
        GTIFGetUOMAngleInfo( psDefn->UOMAngle, nullptr,
                             &(psDefn->UOMAngleInDegrees) );
    }

/* -------------------------------------------------------------------- */
/*      Check for a datum setting, and then use the datum to derive     */
/*      an ellipsoid.                                                   */
/* -------------------------------------------------------------------- */
    GetMetadataElement( "GEOTIFF_NUM::2050::GeogGeodeticDatumGeoKey",
                        &(psDefn->Datum) );

    if( psDefn->Datum != KvUserDefined )
    {
        GTIFGetDatumInfo( psDefn->Datum, nullptr, &(psDefn->Ellipsoid) );
    }

/* -------------------------------------------------------------------- */
/*      Check for an explicit ellipsoid.  Use the ellipsoid to          */
/*      derive the ellipsoid characteristics, if possible.              */
/* -------------------------------------------------------------------- */
    GetMetadataElement( "GEOTIFF_NUM::2056::GeogEllipsoidGeoKey",
                        &(psDefn->Ellipsoid) );

    if( psDefn->Ellipsoid != KvUserDefined )
    {
        GTIFGetEllipsoidInfo( psDefn->Ellipsoid, nullptr,
                              &(psDefn->SemiMajor), &(psDefn->SemiMinor) );
    }

/* -------------------------------------------------------------------- */
/*      Check for overridden ellipsoid parameters.  It would be nice    */
/*      to warn if they conflict with provided information, but for     */
/*      now we just override.                                           */
/* -------------------------------------------------------------------- */
    GetMetadataElement( "GEOTIFF_NUM::2057::GeogSemiMajorAxisGeoKey",
                        &(psDefn->SemiMajor) );
    GetMetadataElement( "GEOTIFF_NUM::2058::GeogSemiMinorAxisGeoKey",
                        &(psDefn->SemiMinor) );

    if( GetMetadataElement( "GEOTIFF_NUM::2059::GeogInvFlatteningGeoKey",
                            &dfInvFlattening ) == 1 )
    {
        if( dfInvFlattening != 0.0 )
            psDefn->SemiMinor = OSRCalcSemiMinorFromInvFlattening(psDefn->SemiMajor, dfInvFlattening);
    }

/* -------------------------------------------------------------------- */
/*      Get the prime meridian info.                                    */
/* -------------------------------------------------------------------- */
    GetMetadataElement( "GEOTIFF_NUM::2051::GeogPrimeMeridianGeoKey",
                        &(psDefn->PM) );

    if( psDefn->PM != KvUserDefined )
    {
        GTIFGetPMInfo( psDefn->PM, nullptr, &(psDefn->PMLongToGreenwich) );
    }
    else
    {
        GetMetadataElement( "GEOTIFF_NUM::2061::GeogPrimeMeridianLongGeoKey",
                            &(psDefn->PMLongToGreenwich) );

        psDefn->PMLongToGreenwich =
            GTIFAngleToDD( psDefn->PMLongToGreenwich,
                           psDefn->UOMAngle );
    }

/* -------------------------------------------------------------------- */
/*      Have the projection units of measure been overridden?  We       */
/*      should likely be doing something about angular units too,       */
/*      but these are very rarely not decimal degrees for actual        */
/*      file coordinates.                                               */
/* -------------------------------------------------------------------- */
    GetMetadataElement( "GEOTIFF_NUM::3076::ProjLinearUnitsGeoKey",
                        &(psDefn->UOMLength) );

    if( psDefn->UOMLength != KvUserDefined )
    {
        GTIFGetUOMLengthInfo( psDefn->UOMLength, nullptr,
                              &(psDefn->UOMLengthInMeters) );
    }

/* -------------------------------------------------------------------- */
/*      Handle a variety of user defined transform types.               */
/* -------------------------------------------------------------------- */
    if( GetMetadataElement( "GEOTIFF_NUM::3075::ProjCoordTransGeoKey",
                            &(psDefn->CTProjection) ) )
    {
        FetchProjParms();
    }

/* -------------------------------------------------------------------- */
/*      Try to set the zoned map system information.                    */
/* -------------------------------------------------------------------- */
    psDefn->MapSys = GTIFProjToMapSys( psDefn->ProjCode, &(psDefn->Zone) );

/* -------------------------------------------------------------------- */
/*      If this is UTM, and we were unable to extract the projection    */
/*      parameters from the CSV file, just set them directly now,       */
/*      since it is pretty easy, and a common case.                     */
/* -------------------------------------------------------------------- */
    if( (psDefn->MapSys == MapSys_UTM_North
         || psDefn->MapSys == MapSys_UTM_South)
        && psDefn->CTProjection == KvUserDefined )
    {
        psDefn->CTProjection = CT_TransverseMercator;
        psDefn->nParms = 7;
        psDefn->ProjParmId[0] = ProjNatOriginLatGeoKey;
        psDefn->ProjParm[0] = 0.0;

        psDefn->ProjParmId[1] = ProjNatOriginLongGeoKey;
        psDefn->ProjParm[1] = psDefn->Zone*6 - 183.0;

        psDefn->ProjParmId[4] = ProjScaleAtNatOriginGeoKey;
        psDefn->ProjParm[4] = 0.9996;

        psDefn->ProjParmId[5] = ProjFalseEastingGeoKey;
        psDefn->ProjParm[5] = 500000.0;

        psDefn->ProjParmId[6] = ProjFalseNorthingGeoKey;

        if( psDefn->MapSys == MapSys_UTM_North )
            psDefn->ProjParm[6] = 0.0;
        else
            psDefn->ProjParm[6] = 10000000.0;
    }

    if ( pszProjection )
        CPLFree( pszProjection );
    pszProjection = GetOGISDefn( psDefn );
}

/************************************************************************/
/*                       GTIFToCPLRecyleString()                        */
/*                                                                      */
/*      This changes a string from the libgeotiff heap to the GDAL      */
/*      heap.                                                           */
/************************************************************************/

static void GTIFToCPLRecycleString( char **ppszTarget )

{
    if( *ppszTarget == nullptr )
        return;

    char *pszTempString = CPLStrdup(*ppszTarget);
    GTIFFreeMemory( *ppszTarget );
    *ppszTarget = pszTempString;
}

/************************************************************************/
/*                          GetOGISDefn()                               */
/*  Copied from the gt_wkt_srs.cpp.                                     */
/************************************************************************/

char *MrSIDDataset::GetOGISDefn( GTIFDefn *psDefnIn )
{
    OGRSpatialReference oSRS;

    if( psDefnIn->Model != ModelTypeProjected
        && psDefnIn->Model != ModelTypeGeographic )
        return CPLStrdup("");

/* -------------------------------------------------------------------- */
/*      If this is a projected SRS we set the PROJCS keyword first      */
/*      to ensure that the GEOGCS will be a child.                      */
/* -------------------------------------------------------------------- */
    if( psDefnIn->Model == ModelTypeProjected )
    {
        int     bPCSNameSet = FALSE;

        if( psDefnIn->PCS != KvUserDefined )
        {
            char *pszPCSName = nullptr;

            if( GTIFGetPCSInfo( psDefnIn->PCS, &pszPCSName, nullptr, nullptr, nullptr ) )
                bPCSNameSet = TRUE;

            oSRS.SetNode( "PROJCS", bPCSNameSet ? pszPCSName : "unnamed" );
            if( bPCSNameSet )
                GTIFFreeMemory( pszPCSName );

            oSRS.SetAuthority( "PROJCS", "EPSG", psDefnIn->PCS );
        }
        else
        {
            char szPCSName[200];
            strcpy( szPCSName, "unnamed" );
            if ( GetMetadataElement( "GEOTIFF_NUM::1026::GTCitationGeoKey",
                                     szPCSName, sizeof(szPCSName) ) )
                oSRS.SetNode( "PROJCS", szPCSName );
        }
    }

/* ==================================================================== */
/*      Setup the GeogCS                                                */
/* ==================================================================== */
    char        *pszGeogName = nullptr;
    char        *pszDatumName = nullptr;
    char        *pszPMName = nullptr;
    char        *pszSpheroidName = nullptr;
    char        *pszAngularUnits = nullptr;
    double      dfInvFlattening, dfSemiMajor;
    char        szGCSName[200];

    if( GetMetadataElement( "GEOTIFF_NUM::2049::GeogCitationGeoKey",
                            szGCSName, sizeof(szGCSName) ) )
        pszGeogName = CPLStrdup(szGCSName);
    else
    {
        GTIFGetGCSInfo( psDefnIn->GCS, &pszGeogName, nullptr, nullptr, nullptr );
        GTIFToCPLRecycleString(&pszGeogName);
    }
    GTIFGetDatumInfo( psDefnIn->Datum, &pszDatumName, nullptr );
    GTIFToCPLRecycleString(&pszDatumName);
    GTIFGetPMInfo( psDefnIn->PM, &pszPMName, nullptr );
    GTIFToCPLRecycleString(&pszPMName);
    GTIFGetEllipsoidInfo( psDefnIn->Ellipsoid, &pszSpheroidName, nullptr, nullptr );
    GTIFToCPLRecycleString(&pszSpheroidName);

    GTIFGetUOMAngleInfo( psDefnIn->UOMAngle, &pszAngularUnits, nullptr );
    GTIFToCPLRecycleString(&pszAngularUnits);
    if( pszAngularUnits == nullptr )
        pszAngularUnits = CPLStrdup("unknown");

    if( pszDatumName != nullptr )
        WKTMassageDatum( &pszDatumName );

    dfSemiMajor = psDefnIn->SemiMajor;
    if( psDefnIn->SemiMajor == 0.0 )
    {
        pszSpheroidName = CPLStrdup("unretrievable - using WGS84");
        dfSemiMajor = SRS_WGS84_SEMIMAJOR;
        dfInvFlattening = SRS_WGS84_INVFLATTENING;
    }
    else
        dfInvFlattening = OSRCalcInvFlattening(psDefnIn->SemiMajor,psDefnIn->SemiMinor);

    oSRS.SetGeogCS( pszGeogName, pszDatumName,
                    pszSpheroidName, dfSemiMajor, dfInvFlattening,
                    pszPMName,
                    psDefnIn->PMLongToGreenwich / psDefnIn->UOMAngleInDegrees,
                    pszAngularUnits,
                    psDefnIn->UOMAngleInDegrees * 0.0174532925199433 );

    if( psDefnIn->GCS != KvUserDefined )
        oSRS.SetAuthority( "GEOGCS", "EPSG", psDefnIn->GCS );

    if( psDefnIn->Datum != KvUserDefined )
        oSRS.SetAuthority( "DATUM", "EPSG", psDefnIn->Datum );

    if( psDefnIn->Ellipsoid != KvUserDefined )
        oSRS.SetAuthority( "SPHEROID", "EPSG", psDefnIn->Ellipsoid );

    CPLFree( pszGeogName );
    CPLFree( pszDatumName );
    CPLFree( pszPMName );
    CPLFree( pszSpheroidName );
    CPLFree( pszAngularUnits );

/* ==================================================================== */
/*      Handle projection parameters.                                   */
/* ==================================================================== */
    if( psDefnIn->Model == ModelTypeProjected )
    {
/* -------------------------------------------------------------------- */
/*      Make a local copy of parms, and convert back into the           */
/*      angular units of the GEOGCS and the linear units of the         */
/*      projection.                                                     */
/* -------------------------------------------------------------------- */
        double          adfParm[10];
        int             i;

        for( i = 0; i < MIN(10,psDefnIn->nParms); i++ )
            adfParm[i] = psDefnIn->ProjParm[i];
        for( ; i < 10; i++)
            adfParm[i] = 0;

        adfParm[0] /= psDefnIn->UOMAngleInDegrees;
        adfParm[1] /= psDefnIn->UOMAngleInDegrees;
        adfParm[2] /= psDefnIn->UOMAngleInDegrees;
        adfParm[3] /= psDefnIn->UOMAngleInDegrees;

        adfParm[5] /= psDefnIn->UOMLengthInMeters;
        adfParm[6] /= psDefnIn->UOMLengthInMeters;

/* -------------------------------------------------------------------- */
/*      Translation the fundamental projection.                         */
/* -------------------------------------------------------------------- */
        switch( psDefnIn->CTProjection )
        {
          case CT_TransverseMercator:
            oSRS.SetTM( adfParm[0], adfParm[1],
                        adfParm[4],
                        adfParm[5], adfParm[6] );
            break;

          case CT_TransvMercator_SouthOriented:
            oSRS.SetTMSO( adfParm[0], adfParm[1],
                          adfParm[4],
                          adfParm[5], adfParm[6] );
            break;

          case CT_Mercator:
            oSRS.SetMercator( adfParm[0], adfParm[1],
                              adfParm[4],
                              adfParm[5], adfParm[6] );
            break;

          case CT_ObliqueStereographic:
            oSRS.SetOS( adfParm[0], adfParm[1],
                        adfParm[4],
                        adfParm[5], adfParm[6] );
            break;

          case CT_Stereographic:
            oSRS.SetOS( adfParm[0], adfParm[1],
                        adfParm[4],
                        adfParm[5], adfParm[6] );
            break;

          case CT_ObliqueMercator: /* hotine */
            oSRS.SetHOM( adfParm[0], adfParm[1],
                         adfParm[2], adfParm[3],
                         adfParm[4],
                         adfParm[5], adfParm[6] );
            break;

          case CT_EquidistantConic:
            oSRS.SetEC( adfParm[0], adfParm[1],
                        adfParm[2], adfParm[3],
                        adfParm[5], adfParm[6] );
            break;

          case CT_CassiniSoldner:
            oSRS.SetCS( adfParm[0], adfParm[1],
                        adfParm[5], adfParm[6] );
            break;

          case CT_Polyconic:
            oSRS.SetPolyconic( adfParm[0], adfParm[1],
                               adfParm[5], adfParm[6] );
            break;

          case CT_AzimuthalEquidistant:
            oSRS.SetAE( adfParm[0], adfParm[1],
                        adfParm[5], adfParm[6] );
            break;

          case CT_MillerCylindrical:
            oSRS.SetMC( adfParm[0], adfParm[1],
                        adfParm[5], adfParm[6] );
            break;

          case CT_Equirectangular:
            oSRS.SetEquirectangular( adfParm[0], adfParm[1],
                                     adfParm[5], adfParm[6] );
            break;

          case CT_Gnomonic:
            oSRS.SetGnomonic( adfParm[0], adfParm[1],
                              adfParm[5], adfParm[6] );
            break;

          case CT_LambertAzimEqualArea:
            oSRS.SetLAEA( adfParm[0], adfParm[1],
                          adfParm[5], adfParm[6] );
            break;

          case CT_Orthographic:
            oSRS.SetOrthographic( adfParm[0], adfParm[1],
                                  adfParm[5], adfParm[6] );
            break;

          case CT_Robinson:
            oSRS.SetRobinson( adfParm[1],
                              adfParm[5], adfParm[6] );
            break;

          case CT_Sinusoidal:
            oSRS.SetSinusoidal( adfParm[1],
                                adfParm[5], adfParm[6] );
            break;

          case CT_VanDerGrinten:
            oSRS.SetVDG( adfParm[1],
                         adfParm[5], adfParm[6] );
            break;

          case CT_PolarStereographic:
            oSRS.SetPS( adfParm[0], adfParm[1],
                        adfParm[4],
                        adfParm[5], adfParm[6] );
            break;

          case CT_LambertConfConic_2SP:
            oSRS.SetLCC( adfParm[2], adfParm[3],
                         adfParm[0], adfParm[1],
                         adfParm[5], adfParm[6] );
            break;

          case CT_LambertConfConic_1SP:
            oSRS.SetLCC1SP( adfParm[0], adfParm[1],
                            adfParm[4],
                            adfParm[5], adfParm[6] );
            break;

          case CT_AlbersEqualArea:
            oSRS.SetACEA( adfParm[0], adfParm[1],
                          adfParm[2], adfParm[3],
                          adfParm[5], adfParm[6] );
            break;

          case CT_NewZealandMapGrid:
            oSRS.SetNZMG( adfParm[0], adfParm[1],
                          adfParm[5], adfParm[6] );
            break;
        }

/* -------------------------------------------------------------------- */
/*      Set projection units.                                           */
/* -------------------------------------------------------------------- */
        char    *pszUnitsName = nullptr;

        GTIFGetUOMLengthInfo( psDefnIn->UOMLength, &pszUnitsName, nullptr );

        if( pszUnitsName != nullptr && psDefnIn->UOMLength != KvUserDefined )
        {
            oSRS.SetLinearUnits( pszUnitsName, psDefnIn->UOMLengthInMeters );
            oSRS.SetAuthority( "PROJCS|UNIT", "EPSG", psDefnIn->UOMLength );
        }
        else
            oSRS.SetLinearUnits( "unknown", psDefnIn->UOMLengthInMeters );

        GTIFFreeMemory( pszUnitsName );
    }

/* -------------------------------------------------------------------- */
/*      Return the WKT serialization of the object.                     */
/* -------------------------------------------------------------------- */
    oSRS.FixupOrdering();

    char *pszWKT = nullptr;
    if( oSRS.exportToWkt( &pszWKT ) == OGRERR_NONE )
        return pszWKT;
    else
        return nullptr;
}

#ifdef MRSID_ESDK

/************************************************************************/
/* ==================================================================== */
/*                        MrSIDDummyImageReader                         */
/*                                                                      */
/*  This is a helper class to wrap GDAL calls in MrSID interface.       */
/* ==================================================================== */
/************************************************************************/

class MrSIDDummyImageReader : public LTIImageReader
{
  public:

    MrSIDDummyImageReader( GDALDataset *poSrcDS );
    ~MrSIDDummyImageReader();
    LT_STATUS           initialize();
    lt_int64            getPhysicalFileSize(void) const { return 0; };

  private:
    GDALDataset         *poDS;
    GDALDataType        eDataType;
    LTIDataType         eSampleType;
    const LTIPixel      *poPixel;

    double              adfGeoTransform[6];

    virtual LT_STATUS   decodeStrip( LTISceneBuffer& stripBuffer,
                                     const LTIScene& stripScene );
    virtual LT_STATUS   decodeBegin( const LTIScene& )
                            { return LT_STS_Success; };
    virtual LT_STATUS   decodeEnd() { return LT_STS_Success; };
};

/************************************************************************/
/*                        MrSIDDummyImageReader()                       */
/************************************************************************/

MrSIDDummyImageReader::MrSIDDummyImageReader( GDALDataset *poSrcDS ) :
                                            LTIImageReader(), poDS(poSrcDS)
{
    poPixel = nullptr;
}

/************************************************************************/
/*                        ~MrSIDDummyImageReader()                      */
/************************************************************************/

MrSIDDummyImageReader::~MrSIDDummyImageReader()
{
    if ( poPixel )
        delete poPixel;
}

/************************************************************************/
/*                             initialize()                             */
/************************************************************************/

LT_STATUS MrSIDDummyImageReader::initialize()
{
    LT_STATUS eStat = LT_STS_Uninit;
#if defined(LTI_SDK_MAJOR) && LTI_SDK_MAJOR >= 6
    if ( !LT_SUCCESS(eStat = LTIImageReader::init()) )
        return eStat;
#else
    if ( !LT_SUCCESS(eStat = LTIImageReader::initialize()) )
        return eStat;
#endif

    lt_uint16 nBands = (lt_uint16)poDS->GetRasterCount();
    LTIColorSpace eColorSpace = LTI_COLORSPACE_RGB;
    switch ( nBands )
    {
        case 1:
            eColorSpace = LTI_COLORSPACE_GRAYSCALE;
            break;
        case 3:
            eColorSpace = LTI_COLORSPACE_RGB;
            break;
        default:
            eColorSpace = LTI_COLORSPACE_MULTISPECTRAL;
            break;
    }

    eDataType = poDS->GetRasterBand(1)->GetRasterDataType();
    switch ( eDataType )
    {
        case GDT_UInt16:
            eSampleType = LTI_DATATYPE_UINT16;
            break;
        case GDT_Int16:
            eSampleType = LTI_DATATYPE_SINT16;
            break;
        case GDT_UInt32:
            eSampleType = LTI_DATATYPE_UINT32;
            break;
        case GDT_Int32:
            eSampleType = LTI_DATATYPE_SINT32;
            break;
        case GDT_Float32:
            eSampleType = LTI_DATATYPE_FLOAT32;
            break;
        case GDT_Float64:
            eSampleType = LTI_DATATYPE_FLOAT64;
            break;
        case GDT_Byte:
        default:
            eSampleType = LTI_DATATYPE_UINT8;
            break;
    }

    poPixel = new LTIDLLPixel<LTIPixel>( eColorSpace, nBands, eSampleType );
    if ( !LT_SUCCESS(setPixelProps(*poPixel)) )
        return LT_STS_Failure;

    if ( !LT_SUCCESS(setDimensions(poDS->GetRasterXSize(),
                                   poDS->GetRasterYSize())) )
        return LT_STS_Failure;

    if ( poDS->GetGeoTransform( adfGeoTransform ) == CE_None )
    {
#ifdef MRSID_SDK_40
        LTIGeoCoord oGeo( adfGeoTransform[0] + adfGeoTransform[1] / 2,
                          adfGeoTransform[3] + adfGeoTransform[5] / 2,
                          adfGeoTransform[1], adfGeoTransform[5],
                          adfGeoTransform[2], adfGeoTransform[4], nullptr,
                          poDS->GetProjectionRef() );
#else
        LTIGeoCoord oGeo( adfGeoTransform[0] + adfGeoTransform[1] / 2,
                          adfGeoTransform[3] + adfGeoTransform[5] / 2,
                          adfGeoTransform[1], adfGeoTransform[5],
                          adfGeoTransform[2], adfGeoTransform[4],
                          poDS->GetProjectionRef() );
#endif
        if ( !LT_SUCCESS(setGeoCoord( oGeo )) )
            return LT_STS_Failure;
    }

    /*int     bSuccess;
    double  dfNoDataValue = poDS->GetNoDataValue( &bSuccess );
    if ( bSuccess )
    {
        LTIPixel    oNoDataPixel( *poPixel );
        lt_uint16   iBand;

        for (iBand = 0; iBand < (lt_uint16)poDS->GetRasterCount(); iBand++)
            oNoDataPixel.setSampleValueFloat32( iBand, dfNoDataValue );
        if ( !LT_SUCCESS(setNoDataPixel( &oNoDataPixel )) )
            return LT_STS_Failure;
    }*/

    setDefaultDynamicRange();
#if !defined(LTI_SDK_MAJOR) || LTI_SDK_MAJOR < 8
    setClassicalMetadata();
#endif

    return LT_STS_Success;
}

/************************************************************************/
/*                             decodeStrip()                            */
/************************************************************************/

LT_STATUS MrSIDDummyImageReader::decodeStrip(LTISceneBuffer& stripData,
                                             const LTIScene& stripScene)

{
    const lt_int32  nXOff = stripScene.getUpperLeftCol();
    const lt_int32  nYOff = stripScene.getUpperLeftRow();
    const lt_int32  nBufXSize = stripScene.getNumCols();
    const lt_int32  nBufYSize = stripScene.getNumRows();
    const lt_int32  nDataBufXSize = stripData.getTotalNumCols();
    const lt_int32  nDataBufYSize = stripData.getTotalNumRows();
    const lt_uint16 nBands = poPixel->getNumBands();

    void *pData = CPLMalloc(nDataBufXSize * nDataBufYSize * poPixel->getNumBytes());
    if ( !pData )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "MrSIDDummyImageReader::decodeStrip(): "
                  "Cannot allocate enough space for scene buffer" );
        return LT_STS_Failure;
    }

    poDS->RasterIO( GF_Read, nXOff, nYOff, nBufXSize, nBufYSize,
                    pData, nBufXSize, nBufYSize, eDataType, nBands, nullptr,
                    0, 0, 0, nullptr );

    stripData.importDataBSQ( pData );
    CPLFree( pData );
    return LT_STS_Success;
}

/************************************************************************/
/*                             FlushCache()                             */
/************************************************************************/

void MrSIDDataset::FlushCache()

{
    GDALDataset::FlushCache();
}

/************************************************************************/
/*                          MrSIDCreateCopy()                           */
/************************************************************************/

static GDALDataset *
MrSIDCreateCopy( const char * pszFilename, GDALDataset *poSrcDS,
                 int bStrict, char ** papszOptions,
                 GDALProgressFunc pfnProgress, void * pProgressData )

{
    const char* pszVersion = CSLFetchNameValue(papszOptions, "VERSION");
#ifdef MRSID_HAVE_MG4WRITE
    int iVersion = pszVersion ? atoi(pszVersion) : 4;
#else
    int iVersion = pszVersion ? atoi(pszVersion) : 3;
#endif
    LT_STATUS eStat = LT_STS_Uninit;

#ifdef DEBUG
    bool bMeter = false;
#else
    bool bMeter = true;
#endif

    if (poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr)
    {
        CPLError( (bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported,
                  "MrSID driver ignores color table. "
                  "The source raster band will be considered as grey level.\n"
                  "Consider using color table expansion (-expand option in gdal_translate)\n");
        if (bStrict)
            return nullptr;
    }

    MrSIDProgress oProgressDelegate(pfnProgress, pProgressData);
    if( LT_FAILURE( eStat = oProgressDelegate.setProgressStatus(0) ) )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "MrSIDProgress.setProgressStatus failed.\n%s",
                  getLastStatusString( eStat ) );
        return nullptr;
    }

    // Create the file.
    MrSIDDummyImageReader oImageReader( poSrcDS );
    if( LT_FAILURE( eStat = oImageReader.initialize() ) )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "MrSIDDummyImageReader.Initialize failed.\n%s",
                  getLastStatusString( eStat ) );
        return nullptr;
    }

    LTIGeoFileImageWriter *poImageWriter = nullptr;
    switch (iVersion)
    {
    case 2: {
        // Output Mrsid Version 2 file.
#if defined(LTI_SDK_MAJOR) && LTI_SDK_MAJOR >= 8
        LTIDLLDefault<MG2ImageWriter> *poMG2ImageWriter;
        poMG2ImageWriter = new LTIDLLDefault<MG2ImageWriter>;
        eStat = poMG2ImageWriter->initialize(&oImageReader);
#else
        LTIDLLWriter<MG2ImageWriter> *poMG2ImageWriter;
        poMG2ImageWriter = new LTIDLLWriter<MG2ImageWriter>(&oImageReader);
        eStat = poMG2ImageWriter->initialize();
#endif
        if( LT_FAILURE( eStat ) )
        {
            delete poMG2ImageWriter;
            CPLError( CE_Failure, CPLE_AppDefined,
                      "MG2ImageWriter.initialize() failed.\n%s",
                      getLastStatusString( eStat ) );
            return nullptr;
        }

#if defined(LTI_SDK_MAJOR) && LTI_SDK_MAJOR >= 8
        eStat = poMG2ImageWriter->setEncodingApplication("MrSID Driver",
                                                         GDALVersionInfo("--version"));
        if( LT_FAILURE( eStat ) )
        {
            delete poMG2ImageWriter;
            CPLError( CE_Failure, CPLE_AppDefined,
                      "MG2ImageWriter.setEncodingApplication() failed.\n%s",
                      getLastStatusString( eStat ) );
            return nullptr;
        }
#endif

        poMG2ImageWriter->setUsageMeterEnabled(bMeter);

        poMG2ImageWriter->params().setBlockSize(poMG2ImageWriter->params().getBlockSize());

        // check for compression option
        const char* pszValue = CSLFetchNameValue(papszOptions, "COMPRESSION");
        if( pszValue != nullptr )
            poMG2ImageWriter->params().setCompressionRatio( (float)CPLAtof(pszValue) );

        poImageWriter = poMG2ImageWriter;

        break; }
    case 3: {
        // Output Mrsid Version 3 file.
#if defined(LTI_SDK_MAJOR) && LTI_SDK_MAJOR >= 8
        LTIDLLDefault<MG3ImageWriter> *poMG3ImageWriter;
        poMG3ImageWriter = new LTIDLLDefault<MG3ImageWriter>;
        eStat = poMG3ImageWriter->initialize(&oImageReader);
#else
        LTIDLLWriter<MG3ImageWriter> *poMG3ImageWriter;
        poMG3ImageWriter = new LTIDLLWriter<MG3ImageWriter>(&oImageReader);
        eStat = poMG3ImageWriter->initialize();
#endif
        if( LT_FAILURE( eStat ) )
        {
            delete poMG3ImageWriter;
            CPLError( CE_Failure, CPLE_AppDefined,
                      "MG3ImageWriter.initialize() failed.\n%s",
                      getLastStatusString( eStat ) );
            return nullptr;
        }

#if defined(LTI_SDK_MAJOR) && LTI_SDK_MAJOR >= 8
        eStat = poMG3ImageWriter->setEncodingApplication("MrSID Driver",
                                                         GDALVersionInfo("--version"));
        if( LT_FAILURE( eStat ) )
        {
            delete poMG3ImageWriter;
            CPLError( CE_Failure, CPLE_AppDefined,
                      "MG3ImageWriter.setEncodingApplication() failed.\n%s",
                      getLastStatusString( eStat ) );
            return nullptr;
        }
#endif

        // usage meter should only be disabled for debugging
        poMG3ImageWriter->setUsageMeterEnabled(bMeter);

#if !defined(LTI_SDK_MAJOR) || LTI_SDK_MAJOR < 8
        // Set 64-bit Interface for large files.
        poMG3ImageWriter->setFileStream64(true);
#endif

        // set 2 pass optimizer option
        if( CSLFetchNameValue(papszOptions, "TWOPASS") != nullptr )
            poMG3ImageWriter->params().setTwoPassOptimizer( true );

        // set filesize in KB
        const char* pszValue = CSLFetchNameValue(papszOptions, "FILESIZE");
        if( pszValue != nullptr )
            poMG3ImageWriter->params().setTargetFilesize( atoi(pszValue) );

        poImageWriter = poMG3ImageWriter;

        break; }
#ifdef MRSID_HAVE_MG4WRITE
    case 4: {
        // Output Mrsid Version 4 file.
        LTIDLLDefault<MG4ImageWriter> *poMG4ImageWriter;
        poMG4ImageWriter = new LTIDLLDefault<MG4ImageWriter>;
        eStat = poMG4ImageWriter->initialize(&oImageReader, nullptr, nullptr);
        if( LT_FAILURE( eStat ) )
        {
            delete poMG4ImageWriter;
            CPLError( CE_Failure, CPLE_AppDefined,
                      "MG3ImageWriter.initialize() failed.\n%s",
                      getLastStatusString( eStat ) );
            return nullptr;
        }

        eStat = poMG4ImageWriter->setEncodingApplication("MrSID Driver",
                                                         GDALVersionInfo("--version"));
        if( LT_FAILURE( eStat ) )
        {
            delete poMG4ImageWriter;
            CPLError( CE_Failure, CPLE_AppDefined,
                      "MG3ImageWriter.setEncodingApplication() failed.\n%s",
                      getLastStatusString( eStat ) );
            return nullptr;
        }

        // usage meter should only be disabled for debugging
        poMG4ImageWriter->setUsageMeterEnabled(bMeter);

        // set 2 pass optimizer option
        if( CSLFetchNameValue(papszOptions, "TWOPASS") != nullptr )
            poMG4ImageWriter->params().setTwoPassOptimizer( true );

        // set filesize in KB
        const char* pszValue = CSLFetchNameValue(papszOptions, "FILESIZE");
        if( pszValue != nullptr )
            poMG4ImageWriter->params().setTargetFilesize( atoi(pszValue) );

        poImageWriter = poMG4ImageWriter;

        break; }
#endif /* MRSID_HAVE_MG4WRITE */
    default:
        CPLError( CE_Failure, CPLE_AppDefined,
                  "Invalid MrSID generation specified (VERSION=%s).",
                  pszVersion );
        return nullptr;
    }

    // set output filename
    poImageWriter->setOutputFileSpec( pszFilename );

    // set progress delegate
    poImageWriter->setProgressDelegate(&oProgressDelegate);

    // set defaults
    poImageWriter->setStripHeight(poImageWriter->getStripHeight());

    // set MrSID world file
    if( CSLFetchNameValue(papszOptions, "WORLDFILE") != nullptr )
        poImageWriter->setWorldFileSupport( true );

    // write the scene
    int nXSize = poSrcDS->GetRasterXSize();
    int nYSize = poSrcDS->GetRasterYSize();
    const LTIScene oScene( 0, 0, nXSize, nYSize, 1.0 );
    if( LT_FAILURE( eStat = poImageWriter->write( oScene ) ) )
    {
        delete poImageWriter;
        CPLError( CE_Failure, CPLE_AppDefined,
                  "MG2ImageWriter.write() failed.\n%s",
                  getLastStatusString( eStat ) );
        return nullptr;
    }

    delete poImageWriter;
/* -------------------------------------------------------------------- */
/*      Re-open dataset, and copy any auxiliary pam information.         */
/* -------------------------------------------------------------------- */
    GDALPamDataset *poDS = (GDALPamDataset *)
        GDALOpen( pszFilename, GA_ReadOnly );

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

    return poDS;
}

#ifdef MRSID_J2K
/************************************************************************/
/*                           JP2CreateCopy()                            */
/************************************************************************/

static GDALDataset *
JP2CreateCopy( const char * pszFilename, GDALDataset *poSrcDS,
                 int bStrict, char ** papszOptions,
                 GDALProgressFunc pfnProgress, void * pProgressData )

{
#ifdef DEBUG
    bool bMeter = false;
#else
    bool bMeter = true;
#endif

    int nXSize = poSrcDS->GetRasterXSize();
    int nYSize = poSrcDS->GetRasterYSize();
    LT_STATUS  eStat;

    if (poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr)
    {
        CPLError( (bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported,
                  "MrSID driver ignores color table. "
                  "The source raster band will be considered as grey level.\n"
                  "Consider using color table expansion (-expand option in gdal_translate)\n");
        if (bStrict)
            return nullptr;
    }

    MrSIDProgress oProgressDelegate(pfnProgress, pProgressData);
    if( LT_FAILURE( eStat = oProgressDelegate.setProgressStatus(0) ) )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "MrSIDProgress.setProgressStatus failed.\n%s",
                  getLastStatusString( eStat ) );
        return nullptr;
    }

    // Create the file.
    MrSIDDummyImageReader oImageReader( poSrcDS );
    eStat = oImageReader.initialize();
    if( eStat != LT_STS_Success )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "MrSIDDummyImageReader.Initialize failed.\n%s",
                  getLastStatusString( eStat ) );
        return nullptr;
    }

#if !defined(MRSID_POST5)
    J2KImageWriter oImageWriter(&oImageReader);
    eStat = oImageWriter.initialize();
#elif !defined(LTI_SDK_MAJOR) || LTI_SDK_MAJOR < 8
    JP2WriterManager oImageWriter(&oImageReader);
    eStat = oImageWriter.initialize();
#else
    JP2WriterManager oImageWriter;
    eStat = oImageWriter.initialize(&oImageReader);
#endif
    if( eStat != LT_STS_Success )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "J2KImageWriter.Initialize failed.\n%s",
                  getLastStatusString( eStat ) );
        return nullptr;
    }

#if !defined(LTI_SDK_MAJOR) || LTI_SDK_MAJOR < 8
    // Set 64-bit Interface for large files.
    oImageWriter.setFileStream64(true);
#endif

    oImageWriter.setUsageMeterEnabled(bMeter);

    // set output filename
    oImageWriter.setOutputFileSpec( pszFilename );

    // set progress delegate
    oImageWriter.setProgressDelegate(&oProgressDelegate);

    // Set defaults
    //oImageWriter.setStripHeight(oImageWriter.getStripHeight());

    // set MrSID world file
    if( CSLFetchNameValue(papszOptions, "WORLDFILE") != nullptr )
        oImageWriter.setWorldFileSupport( true );

    // check for compression option
    const char* pszValue = CSLFetchNameValue(papszOptions, "COMPRESSION");
    if( pszValue != nullptr )
        oImageWriter.params().setCompressionRatio( (float)CPLAtof(pszValue) );

    pszValue = CSLFetchNameValue(papszOptions, "XMLPROFILE");
    if( pszValue != nullptr )
    {
        LTFileSpec xmlprofile(pszValue);
        eStat = oImageWriter.params().readProfile(xmlprofile);
        if( eStat != LT_STS_Success )
        {
            CPLError( CE_Failure, CPLE_AppDefined,
                      "JPCWriterParams.readProfile failed.\n%s",
                      getLastStatusString( eStat ) );
            return nullptr;
        }
    }

    // write the scene
    const LTIScene oScene( 0, 0, nXSize, nYSize, 1.0 );
    eStat = oImageWriter.write( oScene );
    if( eStat != LT_STS_Success )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "J2KImageWriter.write() failed.\n%s",
                  getLastStatusString( eStat ) );
        return nullptr;
    }

/* -------------------------------------------------------------------- */
/*      Re-open dataset, and copy any auxiliary pam information.         */
/* -------------------------------------------------------------------- */
    GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
    GDALPamDataset *poDS = (GDALPamDataset*) JP2Open(&oOpenInfo);

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

    return poDS;
}
#endif /* MRSID_J2K */
#endif /* MRSID_ESDK */

/************************************************************************/
/*                        GDALRegister_MrSID()                          */
/************************************************************************/

void GDALRegister_MrSID()

{
    if( !GDAL_CHECK_VERSION( "MrSID driver" ) )
        return;

/* -------------------------------------------------------------------- */
/*      MrSID driver.                                                   */
/* -------------------------------------------------------------------- */
    if( GDALGetDriverByName( "MrSID" ) != nullptr )
        return;

    GDALDriver *poDriver = new GDALDriver();

    poDriver->SetDescription( "MrSID" );
    poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" );
    poDriver->SetMetadataItem( GDAL_DMD_LONGNAME,
                               "Multi-resolution Seamless Image Database "
                               "(MrSID)" );
    poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC, "frmt_mrsid.html" );
    poDriver->SetMetadataItem( GDAL_DMD_EXTENSION, "sid" );

#ifdef MRSID_ESDK
    poDriver->SetMetadataItem( GDAL_DMD_CREATIONDATATYPES,
                               "Byte Int16 UInt16 Int32 UInt32 "
                               "Float32 Float64" );
    poDriver->SetMetadataItem( GDAL_DMD_CREATIONOPTIONLIST,
"<CreationOptionList>"
// Version 2 Options
"   <Option name='COMPRESSION' type='double' description='Set compression ratio (0.0 default is meant to be lossless)'/>"
// Version 3 Options
"   <Option name='TWOPASS' type='int' description='Use twopass optimizer algorithm'/>"
"   <Option name='FILESIZE' type='int' description='Set target file size (0 implies lossless compression)'/>"
// Version 2 and 3 Option
"   <Option name='WORLDFILE' type='boolean' description='Write out world file'/>"
// Version Type
"   <Option name='VERSION' type='int' description='Valid versions are 2 and 3, default = 3'/>"
"</CreationOptionList>" );

    poDriver->pfnCreateCopy = MrSIDCreateCopy;

#else
    // In read-only mode, we support VirtualIO. I don't think this is the case
    // for MrSIDCreateCopy().
    poDriver->SetMetadataItem( GDAL_DCAP_VIRTUALIO, "YES" );
#endif
    poDriver->pfnIdentify = MrSIDIdentify;
    poDriver->pfnOpen = MrSIDOpen;

    GetGDALDriverManager()->RegisterDriver( poDriver );

/* -------------------------------------------------------------------- */
/*      JP2MRSID driver.                                                */
/* -------------------------------------------------------------------- */
#ifdef MRSID_J2K
    poDriver = new GDALDriver();

    poDriver->SetDescription( "JP2MrSID" );
    poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" );
    poDriver->SetMetadataItem( GDAL_DMD_LONGNAME, "MrSID JPEG2000" );
    poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC, "frmt_jp2mrsid.html" );
    poDriver->SetMetadataItem( GDAL_DMD_EXTENSION, "jp2" );

#ifdef MRSID_ESDK
    poDriver->SetMetadataItem( GDAL_DMD_CREATIONDATATYPES,
                               "Byte Int16 UInt16" );
    poDriver->SetMetadataItem( GDAL_DMD_CREATIONOPTIONLIST,
"<CreationOptionList>"
"   <Option name='COMPRESSION' type='double' description='Set compression ratio (0.0 default is meant to be lossless)'/>"
"   <Option name='WORLDFILE' type='boolean' description='Write out world file'/>"
"   <Option name='XMLPROFILE' type='string' description='Use named xml profile file'/>"
"</CreationOptionList>" );

    poDriver->pfnCreateCopy = JP2CreateCopy;
#else
        /* In read-only mode, we support VirtualIO. I don't think this is the case */
        /* for JP2CreateCopy() */
    poDriver->SetMetadataItem( GDAL_DCAP_VIRTUALIO, "YES" );
#endif
    poDriver->pfnIdentify = JP2Identify;
    poDriver->pfnOpen = JP2Open;

    GetGDALDriverManager()->RegisterDriver( poDriver );
#endif /* def MRSID_J2K */
}

#if defined(MRSID_USE_TIFFSYMS_WORKAROUND)
extern "C" {

/* This is not pretty but I am not sure how else to get the plugin to build
 * against the ESDK.  ESDK symbol dependencies bring in __TIFFmemcpy and
 * __gtiff_size, which are not exported from gdal.dll.  Rather than link these
 * symbols from the ESDK distribution of GDAL, or link in the entire gdal.lib
 * statically, it seemed safer and smaller to bring in just the objects that
 * wouldsatisfy these symbols from the enclosing GDAL build.  However, doing
 * so pulls in a few more dependencies.  /Gy and /OPT:REF did not seem to help
 * things, so I have implemented no-op versions of these symbols since they
 * do not actually get called.  If the MrSID ESDK ever comes to require the
 * actual versions of these functions, we'll hope duplicate symbol errors will
 * bring attention back to this problem.
 */
void TIFFClientOpen() {}
void TIFFError() {}
void TIFFGetField() {}
void TIFFSetField() {}

}
#endif
