/******************************************************************************
 * $Id: idadataset.cpp 16706 2009-04-02 03:44:07Z warmerdam $
 *
 * Project:  IDA Raster Driver
 * Purpose:  Implemenents IDA driver/dataset/rasterband.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 2004, Frank Warmerdam <warmerdam@pobox.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#include "rawdataset.h"
#include "ogr_spatialref.h"
#include "gdal_rat.h"

CPL_CVSID("$Id: idadataset.cpp 16706 2009-04-02 03:44:07Z warmerdam $");

CPL_C_START
void	GDALRegister_IDA(void);
CPL_C_END

// convert a Turbo Pascal real into a double
static double tp2c(GByte *r);

// convert a double into a Turbo Pascal real
static void c2tp(double n, GByte *r);

/************************************************************************/
/* ==================================================================== */
/*				IDADataset				*/
/* ==================================================================== */
/************************************************************************/

class IDADataset : public RawDataset
{
    friend class IDARasterBand;

    int         nImageType;
    int         nProjection;
    char        szTitle[81];
    double      dfLatCenter;
    double      dfLongCenter;
    double      dfXCenter;
    double      dfYCenter;
    double      dfDX;
    double      dfDY;
    double      dfParallel1;
    double      dfParallel2;
    int         nLower;
    int         nUpper;
    int         nMissing;
    double      dfM;
    double      dfB;
    int         nDecimals;

    FILE       *fpRaw;

    char       *pszProjection;
    double      adfGeoTransform[6];

    void        ProcessGeoref();

    GByte       abyHeader[512];
    int         bHeaderDirty;

    void        ReadColorTable();
    
  public:
    		IDADataset();
    	        ~IDADataset();
    
    virtual void FlushCache();
    virtual const char *GetProjectionRef(void);
    virtual CPLErr SetProjection( const char * );

    virtual CPLErr GetGeoTransform( double * );
    virtual CPLErr SetGeoTransform( double * );

    static GDALDataset *Open( GDALOpenInfo * );
    static GDALDataset *Create( const char * pszFilename,
                                int nXSize, int nYSize, int nBands,
                                GDALDataType eType,
                                char ** /* papszParmList */ );

};

/************************************************************************/
/* ==================================================================== */
/*			        IDARasterBand                           */
/* ==================================================================== */
/************************************************************************/

class IDARasterBand : public RawRasterBand
{
    friend class IDADataset;

    GDALRasterAttributeTable *poRAT;
    GDALColorTable       *poColorTable;

  public:
    		IDARasterBand( IDADataset *poDSIn, FILE *fpRaw, int nXSize );
    virtual     ~IDARasterBand();

    virtual const GDALRasterAttributeTable *GetDefaultRAT();
    virtual GDALColorInterp GetColorInterpretation();
    virtual GDALColorTable *GetColorTable();
    virtual double GetOffset( int *pbSuccess = NULL );
    virtual CPLErr SetOffset( double dfNewValue );
    virtual double GetScale( int *pbSuccess = NULL );
    virtual CPLErr SetScale( double dfNewValue );
    virtual double GetNoDataValue( int *pbSuccess = NULL );
};

/************************************************************************/
/*                            IDARasterBand                             */
/************************************************************************/

IDARasterBand::IDARasterBand( IDADataset *poDSIn,
                              FILE *fpRaw, int nXSize )
        : RawRasterBand( poDSIn, 1, fpRaw, 512, 1, nXSize, 
                         GDT_Byte, FALSE, FALSE )

{
    poColorTable = NULL;
    poRAT = NULL;
}

/************************************************************************/
/*                           ~IDARasterBand()                           */
/************************************************************************/

IDARasterBand::~IDARasterBand()

{
    delete poColorTable;
    delete poRAT;
}

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

double IDARasterBand::GetNoDataValue( int *pbSuccess )

{
    if( pbSuccess != NULL )
        *pbSuccess = TRUE;
    return ((IDADataset *) poDS)->nMissing;
}

/************************************************************************/
/*                             GetOffset()                              */
/************************************************************************/

double IDARasterBand::GetOffset( int *pbSuccess )

{
    if( pbSuccess != NULL )
        *pbSuccess = TRUE;
    return ((IDADataset *) poDS)->dfB;
}

/************************************************************************/
/*                             SetOffset()                              */
/************************************************************************/

CPLErr IDARasterBand::SetOffset( double dfNewValue )

{
    IDADataset *poIDS = (IDADataset *) poDS;

    if( dfNewValue == poIDS->dfB )
        return CE_None;

    if( poIDS->nImageType != 200 )
    {
        CPLError( CE_Failure, CPLE_AppDefined, 
                  "Setting explicit offset only support for image type 200.");
        return CE_Failure;
    }

    poIDS->dfB = dfNewValue;
    c2tp( dfNewValue, poIDS->abyHeader + 177 );
    poIDS->bHeaderDirty = TRUE;

    return CE_None;
}

/************************************************************************/
/*                              GetScale()                              */
/************************************************************************/

double IDARasterBand::GetScale( int *pbSuccess )

{
    if( pbSuccess != NULL )
        *pbSuccess = TRUE;
    return ((IDADataset *) poDS)->dfM;
}

/************************************************************************/
/*                              SetScale()                              */
/************************************************************************/

CPLErr IDARasterBand::SetScale( double dfNewValue )

{
    IDADataset *poIDS = (IDADataset *) poDS;

    if( dfNewValue == poIDS->dfM )
        return CE_None;

    if( poIDS->nImageType != 200 )
    {
        CPLError( CE_Failure, CPLE_AppDefined, 
                  "Setting explicit scale only support for image type 200.");
        return CE_Failure;
    }

    poIDS->dfM = dfNewValue;
    c2tp( dfNewValue, poIDS->abyHeader + 171 );
    poIDS->bHeaderDirty = TRUE;

    return CE_None;
}

/************************************************************************/
/*                           GetColorTable()                            */
/************************************************************************/

GDALColorTable *IDARasterBand::GetColorTable()

{
    if( poColorTable )
        return poColorTable;
    else
        return RawRasterBand::GetColorTable();
}

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

GDALColorInterp IDARasterBand::GetColorInterpretation()

{
    if( poColorTable )
        return GCI_PaletteIndex;
    else
        return RawRasterBand::GetColorInterpretation();
}

/************************************************************************/
/*                           GetDefaultRAT()                            */
/************************************************************************/

const GDALRasterAttributeTable *IDARasterBand::GetDefaultRAT() 

{
    if( poRAT )
        return poRAT;
    else
        return RawRasterBand::GetDefaultRAT();
}

/************************************************************************/
/* ==================================================================== */
/*				IDADataset				*/
/* ==================================================================== */
/************************************************************************/

/************************************************************************/
/*                             IDADataset()                             */
/************************************************************************/

IDADataset::IDADataset()
{
    fpRaw = NULL;
    pszProjection = NULL;

    adfGeoTransform[0] = 0.0;
    adfGeoTransform[1] = 1.0;
    adfGeoTransform[2] = 0.0;
    adfGeoTransform[3] = 0.0;
    adfGeoTransform[4] = 0.0;
    adfGeoTransform[5] = 1.0;

    bHeaderDirty = FALSE;
}

/************************************************************************/
/*                            ~IDADataset()                             */
/************************************************************************/

IDADataset::~IDADataset()

{
    FlushCache();

    if( fpRaw != NULL )
        VSIFClose( fpRaw );
    CPLFree( pszProjection );
}

/************************************************************************/
/*                           ProcessGeoref()                            */
/************************************************************************/

void IDADataset::ProcessGeoref()

{
    OGRSpatialReference oSRS;

    if( nProjection == 3 ) 
    {
        oSRS.SetWellKnownGeogCS( "WGS84" );
    }
    else if( nProjection == 4 ) 
    {
        oSRS.SetLCC( dfParallel1, dfParallel2, 
                     dfLatCenter, dfLongCenter, 
                     0.0, 0.0 );
        oSRS.SetGeogCS( "Clarke 1866", "Clarke 1866", "Clarke 1866", 
                        6378206.4, 293.97869821389662 );
    }
    else if( nProjection == 6 ) 
    {
        oSRS.SetLAEA( dfLatCenter, dfLongCenter, 0.0, 0.0 );
        oSRS.SetGeogCS( "Sphere", "Sphere", "Sphere", 
                        6370997.0, 0.0 );
    }
    else if( nProjection == 8 )
    {
        oSRS.SetACEA( dfParallel1, dfParallel2, 
                      dfLatCenter, dfLongCenter, 
                      0.0, 0.0 );
        oSRS.SetGeogCS( "Clarke 1866", "Clarke 1866", "Clarke 1866", 
                        6378206.4, 293.97869821389662 );
    }
    else if( nProjection == 9 ) 
    {
        oSRS.SetGH( dfLongCenter, 0.0, 0.0 );
        oSRS.SetGeogCS( "Sphere", "Sphere", "Sphere", 
                        6370997.0, 0.0 );
    }

    if( oSRS.GetRoot() != NULL )
    {
        CPLFree( pszProjection );
        pszProjection = NULL;

        oSRS.exportToWkt( &pszProjection );
    }

    adfGeoTransform[0] = 0 - dfDX * dfXCenter;
    adfGeoTransform[1] = dfDX;
    adfGeoTransform[2] = 0.0;
    adfGeoTransform[3] =  dfDY * dfYCenter;
    adfGeoTransform[4] = 0.0;
    adfGeoTransform[5] = -dfDY;

    if( nProjection == 3 )
    {
        adfGeoTransform[0] += dfLongCenter;
        adfGeoTransform[3] += dfLatCenter;
    }
}

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

void IDADataset::FlushCache()

{
    RawDataset::FlushCache();
    
    if( bHeaderDirty )
    {
        VSIFSeek( fpRaw, 0, SEEK_SET );
        VSIFWrite( abyHeader, 512, 1, fpRaw );
        bHeaderDirty = FALSE;
    }
}

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

CPLErr IDADataset::GetGeoTransform( double *padfGeoTransform )

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

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

CPLErr IDADataset::SetGeoTransform( double *padfGeoTransform )

{
    if( padfGeoTransform[2] != 0.0 || padfGeoTransform[4] != 0.0 )
        return GDALPamDataset::SetGeoTransform( padfGeoTransform );

    memcpy( adfGeoTransform, padfGeoTransform, sizeof(double) * 6 );
    bHeaderDirty = TRUE;

    dfDX = adfGeoTransform[1];
    dfDY = -adfGeoTransform[5];
    dfXCenter = -adfGeoTransform[0] / dfDX;
    dfYCenter = adfGeoTransform[3] / dfDY;

    c2tp( dfDX, abyHeader + 144 );
    c2tp( dfDY, abyHeader + 150 );
    c2tp( dfXCenter, abyHeader + 132 );
    c2tp( dfYCenter, abyHeader + 138 );

    return CE_None;
}

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

const char *IDADataset::GetProjectionRef()

{
    if( pszProjection )
        return pszProjection;
    else
        return "";
}

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

CPLErr IDADataset::SetProjection( const char *pszWKTIn )

{
    OGRSpatialReference oSRS;

    oSRS.importFromWkt( (char **) &pszWKTIn );

    if( !oSRS.IsGeographic() && !oSRS.IsProjected() )
        GDALPamDataset::SetProjection( pszWKTIn );

/* -------------------------------------------------------------------- */
/*      Clear projection parameters.                                    */
/* -------------------------------------------------------------------- */
    dfParallel1 = 0.0;
    dfParallel2 = 0.0;
    dfLatCenter = 0.0;
    dfLongCenter = 0.0;

/* -------------------------------------------------------------------- */
/*      Geographic.                                                     */
/* -------------------------------------------------------------------- */
    if( oSRS.IsGeographic() )
    {
        // If no change, just return. 
        if( nProjection == 3 )
            return CE_None;

        nProjection = 3;
    }

/* -------------------------------------------------------------------- */
/*      Verify we don't have a false easting or northing as these       */
/*      will be ignored for the projections we do support.              */
/* -------------------------------------------------------------------- */
    if( oSRS.GetProjParm( SRS_PP_FALSE_EASTING ) != 0.0
        || oSRS.GetProjParm( SRS_PP_FALSE_NORTHING ) != 0.0 )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "Attempt to set a projection on an IDA file with a non-zero\n"
                  "false easting and/or northing.  This is not supported." );
        return CE_Failure;
    }

/* -------------------------------------------------------------------- */
/*      Lambert Conformal Conic.  Note that we don't support false      */
/*      eastings or nothings.                                           */
/* -------------------------------------------------------------------- */
    const char *pszProjection = oSRS.GetAttrValue( "PROJECTION" );

    if( pszProjection == NULL )
    {
        /* do nothing - presumably geographic  */;
    }
    else if( EQUAL(pszProjection,SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP) )
    {
        nProjection = 4;
        dfParallel1 = oSRS.GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1,0.0);
        dfParallel2 = oSRS.GetNormProjParm(SRS_PP_STANDARD_PARALLEL_2,0.0);
        dfLatCenter = oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN,0.0);
        dfLongCenter = oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN,0.0);
    }
    else if( EQUAL(pszProjection,SRS_PT_LAMBERT_AZIMUTHAL_EQUAL_AREA) )
    {
        nProjection = 6;
        dfLatCenter = oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN,0.0);
        dfLongCenter = oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN,0.0);
    }
    else if( EQUAL(pszProjection,SRS_PT_ALBERS_CONIC_EQUAL_AREA) )
    {
        nProjection = 8;
        dfParallel1 = oSRS.GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1,0.0);
        dfParallel2 = oSRS.GetNormProjParm(SRS_PP_STANDARD_PARALLEL_2,0.0);
        dfLatCenter = oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN,0.0);
        dfLongCenter = oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN,0.0);
    }
    else if( EQUAL(pszProjection,SRS_PT_GOODE_HOMOLOSINE) )
    {
        nProjection = 9;
        dfLongCenter = oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN,0.0);
    }
    else
    {
        return GDALPamDataset::SetProjection( pszWKTIn );
    }

/* -------------------------------------------------------------------- */
/*      Update header and mark it as dirty.                             */
/* -------------------------------------------------------------------- */
    bHeaderDirty = TRUE;

    abyHeader[23] = nProjection;
    c2tp( dfLatCenter, abyHeader + 120 );
    c2tp( dfLongCenter, abyHeader + 126 );
    c2tp( dfParallel1, abyHeader + 156 );
    c2tp( dfParallel2, abyHeader + 162 );

    return CE_None;
}

/************************************************************************/
/*                           ReadColorTable()                           */
/************************************************************************/

void IDADataset::ReadColorTable()

{
/* -------------------------------------------------------------------- */
/*      Decide what .clr file to look for and try to open.              */
/* -------------------------------------------------------------------- */
    CPLString osCLRFilename;

    osCLRFilename = CPLGetConfigOption( "IDA_COLOR_FILE", "" );
    if( strlen(osCLRFilename) == 0 )
        osCLRFilename = CPLResetExtension(GetDescription(), "clr" );


    FILE *fp = VSIFOpen( osCLRFilename, "r" );
    if( fp == NULL )
    {
        osCLRFilename = CPLResetExtension(osCLRFilename, "CLR" );
        fp = VSIFOpen( osCLRFilename, "r" );
    }

    if( fp == NULL )
        return;

/* -------------------------------------------------------------------- */
/*      Skip first line, with the column titles.                        */
/* -------------------------------------------------------------------- */
    CPLReadLine( fp );

/* -------------------------------------------------------------------- */
/*      Create a RAT to populate.                                       */
/* -------------------------------------------------------------------- */
    GDALRasterAttributeTable *poRAT = new GDALRasterAttributeTable();

    poRAT->CreateColumn( "FROM", GFT_Integer, GFU_Min );
    poRAT->CreateColumn( "TO", GFT_Integer, GFU_Max );
    poRAT->CreateColumn( "RED", GFT_Integer, GFU_Red );
    poRAT->CreateColumn( "GREEN", GFT_Integer, GFU_Green );
    poRAT->CreateColumn( "BLUE", GFT_Integer, GFU_Blue );
    poRAT->CreateColumn( "LEGEND", GFT_String, GFU_Name );

/* -------------------------------------------------------------------- */
/*      Apply lines.                                                    */
/* -------------------------------------------------------------------- */
    const char *pszLine = CPLReadLine( fp );
    int iRow = 0;

    while( pszLine != NULL )
    {
        char **papszTokens = 
            CSLTokenizeStringComplex( pszLine, " \t", FALSE, FALSE );
        
        if( CSLCount( papszTokens ) >= 5 )
        {
            poRAT->SetValue( iRow, 0, atoi(papszTokens[0]) );
            poRAT->SetValue( iRow, 1, atoi(papszTokens[1]) );
            poRAT->SetValue( iRow, 2, atoi(papszTokens[2]) );
            poRAT->SetValue( iRow, 3, atoi(papszTokens[3]) );
            poRAT->SetValue( iRow, 4, atoi(papszTokens[4]) );

            // find name, first nonspace after 5th token. 
            const char *pszName = pszLine;

            // skip from
            while( *pszName == ' ' || *pszName == '\t' )
                pszName++;
            while( *pszName != ' ' && *pszName != '\t' && *pszName != '\0' )
                pszName++;
            
            // skip to
            while( *pszName == ' ' || *pszName == '\t' )
                pszName++;
            while( *pszName != ' ' && *pszName != '\t' && *pszName != '\0' )
                pszName++;
            
            // skip red
            while( *pszName == ' ' || *pszName == '\t' )
                pszName++;
            while( *pszName != ' ' && *pszName != '\t' && *pszName != '\0' )
                pszName++;
            
            // skip green
            while( *pszName == ' ' || *pszName == '\t' )
                pszName++;
            while( *pszName != ' ' && *pszName != '\t' && *pszName != '\0' )
                pszName++;
            
            // skip blue
            while( *pszName == ' ' || *pszName == '\t' )
                pszName++;
            while( *pszName != ' ' && *pszName != '\t' && *pszName != '\0' )
                pszName++;

            // skip pre-name white space
            while( *pszName == ' ' || *pszName == '\t' )
                pszName++;

            poRAT->SetValue( iRow, 5, pszName );
            
            iRow++;
        }

        CSLDestroy( papszTokens );
        pszLine = CPLReadLine( fp );
    }

/* -------------------------------------------------------------------- */
/*      Attach RAT to band.                                             */
/* -------------------------------------------------------------------- */
    ((IDARasterBand *) GetRasterBand( 1 ))->poRAT = poRAT;

/* -------------------------------------------------------------------- */
/*      Build a conventional color table from this.                     */
/* -------------------------------------------------------------------- */
    ((IDARasterBand *) GetRasterBand( 1 ))->poColorTable = 
        poRAT->TranslateToColorTable();
}

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

GDALDataset *IDADataset::Open( GDALOpenInfo * poOpenInfo )

{
/* -------------------------------------------------------------------- */
/*      Is this an IDA file?                                            */
/* -------------------------------------------------------------------- */
    int      nXSize, nYSize;
    GIntBig  nExpectedFileSize, nActualFileSize;

    if( poOpenInfo->fp == NULL )
        return NULL;

    if( poOpenInfo->nHeaderBytes < 512 )
        return NULL;

    // projection legal? 
    if( poOpenInfo->pabyHeader[23] > 10 )
        return NULL;

    // imagetype legal? 
    if( (poOpenInfo->pabyHeader[22] > 14 
         && poOpenInfo->pabyHeader[22] < 100)
        || (poOpenInfo->pabyHeader[22] > 114 
            && poOpenInfo->pabyHeader[22] != 200 ) )
        return NULL;

    nXSize = poOpenInfo->pabyHeader[30] + poOpenInfo->pabyHeader[31] * 256;
    nYSize = poOpenInfo->pabyHeader[32] + poOpenInfo->pabyHeader[33] * 256;

    if( nXSize == 0 || nYSize == 0 )
        return NULL;

    // The file just be exactly the image size + header size in length.
    nExpectedFileSize = nXSize * nYSize + 512;
    
    VSIFSeek( poOpenInfo->fp, 0, SEEK_END );
    nActualFileSize = VSIFTell( poOpenInfo->fp );
    VSIRewind( poOpenInfo->fp );
    
    if( nActualFileSize != nExpectedFileSize )
        return NULL;
    
/* -------------------------------------------------------------------- */
/*      Create the dataset.                                             */
/* -------------------------------------------------------------------- */
    IDADataset *poDS = new IDADataset();				

    memcpy( poDS->abyHeader, poOpenInfo->pabyHeader, 512 );
        
/* -------------------------------------------------------------------- */
/*      Parse various values out of the header.                         */
/* -------------------------------------------------------------------- */
    poDS->nImageType = poOpenInfo->pabyHeader[22];
    poDS->nProjection = poOpenInfo->pabyHeader[23];

    poDS->nRasterYSize = poOpenInfo->pabyHeader[30] 
        + poOpenInfo->pabyHeader[31] * 256;
    poDS->nRasterXSize = poOpenInfo->pabyHeader[32] 
        + poOpenInfo->pabyHeader[33] * 256;

    strncpy( poDS->szTitle, (const char *) poOpenInfo->pabyHeader+38, 80 );
    poDS->szTitle[80] = '\0';

    int nLastTitleChar = strlen(poDS->szTitle)-1;
    while( nLastTitleChar > -1 
           && (poDS->szTitle[nLastTitleChar] == 10 
               || poDS->szTitle[nLastTitleChar] == 13 
               || poDS->szTitle[nLastTitleChar] == ' ') ) 
        poDS->szTitle[nLastTitleChar--] = '\0';

    poDS->dfLatCenter = tp2c( poOpenInfo->pabyHeader + 120 );
    poDS->dfLongCenter = tp2c( poOpenInfo->pabyHeader + 126 );
    poDS->dfXCenter = tp2c( poOpenInfo->pabyHeader + 132 );
    poDS->dfYCenter = tp2c( poOpenInfo->pabyHeader + 138 );
    poDS->dfDX = tp2c( poOpenInfo->pabyHeader + 144 );
    poDS->dfDY = tp2c( poOpenInfo->pabyHeader + 150 );
    poDS->dfParallel1 = tp2c( poOpenInfo->pabyHeader + 156 );
    poDS->dfParallel2 = tp2c( poOpenInfo->pabyHeader + 162 );

    poDS->ProcessGeoref();

    poDS->SetMetadataItem( "TITLE", poDS->szTitle );

/* -------------------------------------------------------------------- */
/*      Handle various image types.                                     */
/* -------------------------------------------------------------------- */

/*
GENERIC = 0
FEW S NDVI = 1
EROS NDVI = 6
ARTEMIS CUTOFF = 10
ARTEMIS RECODE = 11
ARTEMIS NDVI = 12
ARTEMIS FEWS = 13
ARTEMIS NEWNASA = 14
GENERIC DIFF = 100
FEW S NDVI DIFF = 101
EROS NDVI DIFF = 106
ARTEMIS CUTOFF DIFF = 110
ARTEMIS RECODE DIFF = 111
ARTEMIS NDVI DIFF = 112
ARTEMIS FEWS DIFF = 113
ARTEMIS NEWNASA DIFF = 114
CALCULATED =200
*/
 
    poDS->nMissing = 0;

    switch( poDS->nImageType )
    {
      case 1:
        poDS->SetMetadataItem( "IMAGETYPE", "1, FEWS NDVI" );
        poDS->dfM = 1/256.0;
        poDS->dfB = -82/256.0;
        break;

      case 6:
        poDS->SetMetadataItem( "IMAGETYPE", "6, EROS NDVI" );
        poDS->dfM = 1/100.0;
        poDS->dfB = -100/100.0;
        break;

      case 10:
        poDS->SetMetadataItem( "IMAGETYPE", "10, ARTEMIS CUTOFF" );
        poDS->dfM = 1.0;
        poDS->dfB = 0.0;
        poDS->nMissing = 254;
        break;

      case 11:
        poDS->SetMetadataItem( "IMAGETYPE", "11, ARTEMIS RECODE" );
        poDS->dfM = 4.0;
        poDS->dfB = 0.0;
        poDS->nMissing = 254;
        break;

      case 12: /* ANDVI */
        poDS->SetMetadataItem( "IMAGETYPE", "12, ARTEMIS NDVI" );
        poDS->dfM = 4/500.0;
        poDS->dfB = -3/500.0 - 1.0;
        poDS->nMissing = 254;
        break;

      case 13: /* AFEWS */
        poDS->SetMetadataItem( "IMAGETYPE", "13, ARTEMIS FEWS" );
        poDS->dfM = 1/256.0;
        poDS->dfB = -82/256.0;
        poDS->nMissing = 254;
        break;

      case 14: /* NEWNASA */
        poDS->SetMetadataItem( "IMAGETYPE", "13, ARTEMIS NEWNASA" );
        poDS->dfM = 0.75/250.0;
        poDS->dfB = 0.0;
        poDS->nMissing = 254;
        break;

      case 101: /* NDVI_DIFF (FEW S) */
        poDS->dfM = 1/128.0;
        poDS->dfB = -1.0;
        poDS->nMissing = 0;
        break;

      case 106: /* EROS_DIFF */
        poDS->dfM = 1/50.0;
        poDS->dfB = -128/50.0;
        poDS->nMissing = 0;
        break;

      case 110: /* CUTOFF_DIFF */
        poDS->dfM = 2.0;
        poDS->dfB = -128*2;
        poDS->nMissing = 254;
        break;

      case 111: /* RECODE_DIFF */
        poDS->dfM = 8;
        poDS->dfB = -128*8;
        poDS->nMissing = 254;
        break;

      case 112: /* ANDVI_DIFF */
        poDS->dfM = 8/1000.0;
        poDS->dfB = (-128*8)/1000.0;
        poDS->nMissing = 254;
        break;

      case 113: /* AFEWS_DIFF */
        poDS->dfM = 1/128.0;
        poDS->dfB = -1;
        poDS->nMissing = 254;
        break;

      case 114: /* NEWNASA_DIFF */
        poDS->dfM = 0.75/125.0;
        poDS->dfB = -128*poDS->dfM;
        poDS->nMissing = 254;
        break;

      case 200:
        /* we use the values from the header */
        poDS->dfM = tp2c( poOpenInfo->pabyHeader + 171 );
        poDS->dfB = tp2c( poOpenInfo->pabyHeader + 177 );
        poDS->nMissing = poOpenInfo->pabyHeader[170];
        break;

      default:
        poDS->dfM = 1.0;
        poDS->dfB = 0.0;
        break;
    }

/* -------------------------------------------------------------------- */
/*      Create the band.                                                */
/* -------------------------------------------------------------------- */
    if( poOpenInfo->eAccess == GA_ReadOnly )
    {
        poDS->fpRaw = poOpenInfo->fp;
        poOpenInfo->fp = NULL;
    }
    else
    {
        poDS->fpRaw = VSIFOpen( poOpenInfo->pszFilename, "rb+" );
        poDS->eAccess = GA_Update;
        if( poDS->fpRaw == NULL )
        {
            CPLError( CE_Failure, CPLE_OpenFailed, 
                      "Failed to open %s for write access.", 
                      poOpenInfo->pszFilename );
            return NULL;
        }
    }

    poDS->SetBand( 1, new IDARasterBand( poDS, poDS->fpRaw, 
                                         poDS->nRasterXSize ) );

/* -------------------------------------------------------------------- */
/*      Check for a color table.                                        */
/* -------------------------------------------------------------------- */
    poDS->SetDescription( poOpenInfo->pszFilename );
    poDS->ReadColorTable();

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

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

    return( poDS );
}

/************************************************************************/
/*                                tp2c()                                */
/*                                                                      */
/*      convert a Turbo Pascal real into a double                       */
/************************************************************************/

static double tp2c(GByte *r)
{
  double mant;
  int sign, exp, i;

  // handle 0 case
  if (r[0] == 0)
    return 0.0;

  // extract sign: bit 7 of byte 5
  sign = r[5] & 0x80 ? -1 : 1;

  // extract mantissa from first bit of byte 1 to bit 7 of byte 5
  mant = 0;
  for (i = 1; i < 5; i++)
    mant = (r[i] + mant) / 256;
  mant = (mant + (r[5] & 0x7F)) / 128 + 1;

   // extract exponent
  exp = r[0] - 129;

  // compute the damned number
  return sign * ldexp(mant, exp);
}

/************************************************************************/
/*                                c2tp()                                */
/*                                                                      */
/*      convert a double into a Turbo Pascal real                       */
/************************************************************************/

static void c2tp(double x, GByte *r)
{
  double mant, temp;
  int negative, exp, i;

  // handle 0 case
  if (x == 0.0)
  {
    for (i = 0; i < 6; r[i++] = 0);
    return;
  }

  // compute mantissa, sign and exponent
  mant = frexp(x, &exp) * 2 - 1;
  exp--;
  negative = 0;
  if (mant < 0)
  {
    mant = -mant;
    negative = 1;
  }
  // stuff mantissa into Turbo Pascal real
  mant = modf(mant * 128, &temp);
  r[5] = (unsigned char) temp;
  for (i = 4; i >= 1; i--)
  {
    mant = modf(mant * 256, &temp);
    r[i] = (unsigned char) temp;
  }
  // add sign
  if (negative)
    r[5] |= 0x80;

  // put exponent
  r[0] = exp + 129;
}

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

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

{
/* -------------------------------------------------------------------- */
/*      Verify input options.                                           */
/* -------------------------------------------------------------------- */
    if( eType != GDT_Byte || nBands != 1 )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "Only 1 band, Byte datasets supported for IDA format." );

        return NULL;
    }

/* -------------------------------------------------------------------- */
/*      Try to create the file.                                         */
/* -------------------------------------------------------------------- */
    FILE	*fp;

    fp = VSIFOpen( pszFilename, "wb" );

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

/* -------------------------------------------------------------------- */
/*      Prepare formatted header.                                       */
/* -------------------------------------------------------------------- */
    GByte abyHeader[512];
    
    memset( abyHeader, 0, sizeof(abyHeader) );
    
    abyHeader[22] = 200; /* image type - CALCULATED */
    abyHeader[23] = 0; /* projection - NONE */
    abyHeader[30] = nYSize % 256;
    abyHeader[31] = nYSize / 256;
    abyHeader[32] = nXSize % 256;
    abyHeader[33] = nXSize / 256;

    abyHeader[170] = 255; /* missing = 255 */
    c2tp( 1.0, abyHeader + 171 ); /* slope = 1.0 */
    c2tp( 0.0, abyHeader + 177 ); /* offset = 0 */
    abyHeader[168] = 0; // lower limit
    abyHeader[169] = 254; // upper limit

    // pixel size = 1.0
    c2tp( 1.0, abyHeader + 144 );
    c2tp( 1.0, abyHeader + 150 );

    if( VSIFWrite( abyHeader, 1, 512, fp ) != 512 )
    {
        CPLError( CE_Failure, CPLE_AppDefined, 
                  "IO error writing %s.\n%s", 
                  pszFilename, VSIStrerror( errno ) );
        VSIFClose( fp );
        return NULL;
    }

/* -------------------------------------------------------------------- */
/*      Now we need to extend the file to just the right number of      */
/*      bytes for the data we have to ensure it will open again         */
/*      properly.                                                       */
/* -------------------------------------------------------------------- */
    if( VSIFSeek( fp, nXSize * nYSize - 1, SEEK_CUR ) != 0
        || VSIFWrite( abyHeader, 1, 1, fp ) != 1 )
    {
        CPLError( CE_Failure, CPLE_AppDefined, 
                  "IO error writing %s.\n%s", 
                  pszFilename, VSIStrerror( errno ) );
        VSIFClose( fp );
        return NULL;
    }

    VSIFClose( fp );

    return (GDALDataset *) GDALOpen( pszFilename, GA_Update );
}

/************************************************************************/
/*                         GDALRegister_IDA()                          */
/************************************************************************/

void GDALRegister_IDA()

{
    GDALDriver	*poDriver;

    if( GDALGetDriverByName( "IDA" ) == NULL )
    {
        poDriver = new GDALDriver();
        
        poDriver->SetDescription( "IDA" );
        poDriver->SetMetadataItem( GDAL_DMD_LONGNAME, 
                                   "Image Data and Analysis" );
        poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC, 
                                   "frmt_various.html#IDA" );
        poDriver->SetMetadataItem( GDAL_DMD_CREATIONDATATYPES, "Byte" );

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

        GetGDALDriverManager()->RegisterDriver( poDriver );
    }
}

