/******************************************************************************
 * $Id: giodataset.cpp,v 1.13 2001/07/18 04:51:56 warmerda Exp $
 *
 * Project:  Arc/Info Binary Grid Driver
 * Purpose:  Implements GDAL interface to ArcView GRIDIO Library.
 * Author:   Frank Warmerdam, warmerda@home.com
 *
 ******************************************************************************
 * Copyright (c) 1999, Frank Warmerdam
 *
 * 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.
 *****************************************************************************
 *
 * $Log: giodataset.cpp,v $
 * Revision 1.13  2001/07/18 04:51:56  warmerda
 * added CPL_CVSID
 *
 * Revision 1.12  2001/03/13 19:39:51  warmerda
 * changed short name to GIO, added help link
 *
 * Revision 1.11  2000/03/27 13:38:57  warmerda
 * Fixed SetBand() call in Create().
 *
 * Revision 1.10  2000/03/23 23:03:08  warmerda
 * Fixed Create() method to use SetBand().
 *
 * Revision 1.9  2000/03/22 23:52:59  pgs
 * use SetBand dork
 *
 * Revision 1.8  2000/01/31 16:39:37  warmerda
 * added pfnDelete support, and removed delete hack from Create method
 *
 * Revision 1.7  2000/01/31 04:13:10  warmerda
 * Added logic in Create() to blow away old grid of same name if present.
 *
 * Revision 1.6  2000/01/31 04:08:23  warmerda
 * Fixed georeference writing.
 *
 * Revision 1.5  2000/01/31 03:55:49  warmerda
 * Added support for writing out georeferencing to newly created datsets.
 *
 * Revision 1.4  2000/01/13 19:10:53  warmerda
 * Call AccessWindowSet() _before_ creating a new layer.
 *
 * Revision 1.3  2000/01/13 17:34:18  warmerda
 * Fixed transposition of nXSize and nYSize.
 *
 * Revision 1.2  2000/01/10 17:36:07  warmerda
 * avoid error message on first CPLGetSymbol()
 *
 * Revision 1.1  2000/01/07 18:57:36  warmerda
 * New
 *
 */

#include "gdal_priv.h"

CPL_CVSID("$Id: giodataset.cpp,v 1.13 2001/07/18 04:51:56 warmerda Exp $");

#define	READONLY	1
#define	READWRITE	2
#define WRITEONLY       3
#define	ROWIO		1
#define	CELLINT		1		/* 32 bit signed integers */
#define	CELLFLOAT	2		/* 32 bit floating point numbers*/

CPL_C_START
void	GDALRegister_AIGrid2(void);

static int      nGridIOSetupCalled = FALSE;
static int      (*pfnGridIOSetup)(void) = NULL;
static int      (*pfnGridIOExit)(void) = NULL;
static int      (*pfnCellLayerOpen)(char *, int, int, int *, double *) = NULL;
static int      (*pfnDescribeGridDbl)(char *, double *, int *, double *,
                                      double *, int *, int *, int * ) = NULL;
static int      (*pfnAccessWindowSet)(double *, double, double * ) = NULL;
static int      (*pfnGetWindowRowFloat)(int, int, float *) = NULL;
static int      (*pfnPutWindowRow)(int, int, float *) = NULL;
static int      (*pfnCellLayerClose)(int) = NULL;
static int      (*pfnCellLayerCreate)(char *, int, int, int, double, 
                                      double*) = NULL;
static int      (*pfnGridDelete)(char *) = NULL;
CPL_C_END

/************************************************************************/
/*                        LoadGridIOFunctions()                         */
/************************************************************************/

static int LoadGridIOFunctions()

{
    static int      bInitialized = FALSE;
    
    if( bInitialized )
        return pfnGridIOSetup != NULL;

    bInitialized = TRUE;

    CPLPushErrorHandler( CPLQuietErrorHandler );
    pfnGridIOSetup = (int (*)(void)) 
        CPLGetSymbol( "avgridio.dll", "GridIOSetup" );
    CPLPopErrorHandler();

    if( pfnGridIOSetup == NULL )
        return FALSE;

    pfnGridIOExit = (int (*)(void)) 
        CPLGetSymbol( "avgridio.dll", "GridIOExit" );

    pfnCellLayerOpen = (int (*)(char *, int, int, int*, double*))
        CPLGetSymbol( "avgridio.dll", "CellLayerOpen" );
    pfnCellLayerCreate = (int (*)(char *, int, int, int, double, double*))
        CPLGetSymbol( "avgridio.dll", "CellLayerCreate" );
    pfnDescribeGridDbl = 
        (int (*)(char*,double*,int*,double*,double*,int*,int*,int*))
        CPLGetSymbol( "avgridio.dll", "DescribeGridDbl" );
    pfnAccessWindowSet = (int (*)(double*,double,double*))
        CPLGetSymbol( "avgridio.dll", "AccessWindowSet" );
    pfnGetWindowRowFloat = (int (*)(int,int,float*))
        CPLGetSymbol( "avgridio.dll", "GetWindowRowFloat" );
    pfnPutWindowRow = (int (*)(int,int,float*))
        CPLGetSymbol( "avgridio.dll", "PutWindowRow" );
    pfnCellLayerClose = (int (*)(int))
        CPLGetSymbol( "avgridio.dll", "CellLayerClose" );
    pfnGridDelete = (int (*)(char*))
        CPLGetSymbol( "avgridio.dll", "GridDelete" );

    if( pfnCellLayerOpen == NULL || pfnDescribeGridDbl == NULL
        || pfnAccessWindowSet == NULL || pfnGetWindowRowFloat == NULL
        || pfnCellLayerClose == NULL || pfnGridDelete == NULL )
        pfnGridIOSetup = NULL;

    return pfnGridIOSetup != NULL;
}

/************************************************************************/
/* ==================================================================== */
/*				GIODataset				*/
/* ==================================================================== */
/************************************************************************/

class GIORasterBand;

class CPL_DLL GIODataset : public GDALDataset
{
    friend	GIORasterBand;

    char       *pszPath;

    int         bCreated;
    int         nGridChannel;
    int         nCellType;
    double      dfCellSize;
    double      adfGeoTransform[6];

    void        WriteGeoreference();

  public:
                GIODataset();
                ~GIODataset();

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

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

/************************************************************************/
/* ==================================================================== */
/*                            GIORasterBand                             */
/* ==================================================================== */
/************************************************************************/

class GIORasterBand : public GDALRasterBand
{
    friend	GIODataset;

  public:

                   GIORasterBand( GIODataset *, int );
                  ~GIORasterBand();

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

};

static GDALDriver	*poGIODriver = NULL;

/************************************************************************/
/*                           GIORasterBand()                            */
/************************************************************************/

GIORasterBand::GIORasterBand( GIODataset *poDS, int nBand )

{
    this->poDS = poDS;
    this->nBand = nBand;

    nBlockXSize = poDS->nRasterXSize;
    nBlockYSize = 1;

    eDataType = GDT_Float32;
}

/************************************************************************/
/*                           ~GIORasterBand()                           */
/************************************************************************/

GIORasterBand::~GIORasterBand()

{
}

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

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

{
    GIODataset	*poODS = (GIODataset *) poDS;

    if( poODS->bCreated )
        memset( pImage, sizeof(float) * poODS->nRasterXSize, 0 );
    else
        pfnGetWindowRowFloat( poODS->nGridChannel, nBlockYOff, (float*)pImage);

    return CE_None;
}

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

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

{
    GIODataset	*poODS = (GIODataset *) poDS;
    
    pfnPutWindowRow( poODS->nGridChannel, nBlockYOff, (float *) pImage);
    
    return CE_None;
}

/************************************************************************/
/* ==================================================================== */
/*                            GIODataset                               */
/* ==================================================================== */
/************************************************************************/


/************************************************************************/
/*                            GIODataset()                            */
/************************************************************************/

GIODataset::GIODataset()

{
    nGridChannel = -1;
}

/************************************************************************/
/*                           ~GIODataset()                            */
/************************************************************************/

GIODataset::~GIODataset()

{
    FlushCache();
    if( nGridChannel != -1 )
        pfnCellLayerClose( nGridChannel );

    if( bCreated )
        WriteGeoreference();

    CPLFree( pszPath );
}

/************************************************************************/
/*                         WriteGeoreference()                          */
/************************************************************************/

void GIODataset::WriteGeoreference()

{
/* -------------------------------------------------------------------- */
/*      If this is a created dataset, we will write the                 */
/*      georeferencing out directly.  We do this by hand, since         */
/*      using the ESRI API we can only do this as part of the           */
/*      creation.  First we update the bounds file.                     */
/* -------------------------------------------------------------------- */
    FILE      *fp;
    double    adfFileBounds[4];

    fp = VSIFOpen( CPLFormFilename( pszPath, "dblbnd", "adf" ), "r+b" );
    if( fp == NULL )
    {
        CPLError( CE_Failure, CPLE_OpenFailed,
                  "Unable to open dbfbnd.adf to update georeferencing." );
        return;
    }

    adfFileBounds[0] = adfGeoTransform[0];
    adfFileBounds[1] = adfGeoTransform[3]+adfGeoTransform[5]*GetRasterYSize();
    adfFileBounds[2] = adfGeoTransform[0]+adfGeoTransform[1]*GetRasterXSize();
    adfFileBounds[3] = adfGeoTransform[3];

#ifdef CPL_LSB
    CPL_SWAPDOUBLE( adfFileBounds+0 );
    CPL_SWAPDOUBLE( adfFileBounds+1 );
    CPL_SWAPDOUBLE( adfFileBounds+2 );
    CPL_SWAPDOUBLE( adfFileBounds+3 );
#endif
    
    VSIFWrite( adfFileBounds, 1, 32, fp );
    VSIFClose( fp );

/* -------------------------------------------------------------------- */
/*      Now update the cellsize in the header file.                     */
/* -------------------------------------------------------------------- */
    double      adfCellSizes[2];

    fp = VSIFOpen( CPLFormFilename( pszPath, "hdr", "adf" ), "r+b" );
    if( fp == NULL )
    {
        CPLError( CE_Failure, CPLE_OpenFailed,
                  "Unable to open hdr.adf to update georeferencing." );
        return;
    }
    
    adfCellSizes[0] = adfGeoTransform[1];
    adfCellSizes[1] = ABS(adfGeoTransform[5]);
                          
#ifdef CPL_LSB
    CPL_SWAPDOUBLE( adfCellSizes+0 );
    CPL_SWAPDOUBLE( adfCellSizes+1 );
#endif

    VSIFSeek( fp, 256, SEEK_SET );
    VSIFWrite( adfCellSizes, 1, 16, fp );

    VSIFClose( fp );
}

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

GDALDataset *GIODataset::Open( GDALOpenInfo * poOpenInfo )

{
    int            nChannel, nReturn;
    
/* -------------------------------------------------------------------- */
/*      If the pass name ends in .adf assume a file within the          */
/*      coverage has been selected, and strip that off the coverage     */
/*      name.                                                           */
/* -------------------------------------------------------------------- */
    char            *pszCoverName;

    pszCoverName = CPLStrdup( poOpenInfo->pszFilename );
    if( EQUAL(pszCoverName+strlen(pszCoverName)-4, ".adf") )
    {
        int      i;

        for( i = strlen(pszCoverName)-1; i > 0; i-- )
        {
            if( pszCoverName[i] == '\\' || pszCoverName[i] == '/' )
            {
                pszCoverName[i] = '\0';
                break;
            }
        }
    }

/* -------------------------------------------------------------------- */
/*      Verify that the resulting name is directory path.               */
/* -------------------------------------------------------------------- */
    VSIStatBuf      sStat;

    if( VSIStat( pszCoverName, &sStat ) != 0 || !VSI_ISDIR(sStat.st_mode) )
    {
        CPLFree( pszCoverName );
        return NULL;
    }

/* -------------------------------------------------------------------- */
/*      Call GridIOSetup(), if not called already.                      */
/* -------------------------------------------------------------------- */
    if( !nGridIOSetupCalled )
    {
        if( pfnGridIOSetup() != 1 )
            return NULL;

        nGridIOSetupCalled = TRUE;
    }
    
/* -------------------------------------------------------------------- */
/*      Try to fetch description information for grid.                  */
/* -------------------------------------------------------------------- */
    int            nCellType, nClasses, nRecordLength;
    int            anGridSize[2];
    double         adfBox[4], adfStatistics[10], dfCellSize;

    anGridSize[0] = -1;
    anGridSize[1] = -1;
    nReturn = pfnDescribeGridDbl( pszCoverName, 
                                  &dfCellSize, anGridSize, adfBox, 
                                  adfStatistics, &nCellType, 
                                  &nClasses, &nRecordLength );

    if( nReturn < 1 && anGridSize[0] == -1 )
    {
        return NULL;
    }

/* -------------------------------------------------------------------- */
/*      Open the file.                                                  */
/* -------------------------------------------------------------------- */
    nChannel = pfnCellLayerOpen( pszCoverName, 
                                 READONLY, ROWIO, &nCellType, &dfCellSize );

    if( nChannel < 0 )
        return NULL;
    
/* -------------------------------------------------------------------- */
/*      Create a corresponding GDALDataset.                             */
/* -------------------------------------------------------------------- */
    GIODataset 	*poDS;

    poDS = new GIODataset();

    poDS->pszPath = pszCoverName;
    poDS->nGridChannel = nChannel;
    poDS->poDriver = poGIODriver;

/* -------------------------------------------------------------------- */
/*      Establish raster info.                                          */
/* -------------------------------------------------------------------- */
    poDS->nRasterXSize = anGridSize[1];
    poDS->nRasterYSize = anGridSize[0];
    poDS->nBands = 1;
    poDS->bCreated = FALSE;

    poDS->adfGeoTransform[0] = adfBox[0];
    poDS->adfGeoTransform[1] = dfCellSize;
    poDS->adfGeoTransform[2] = 0.0;
    poDS->adfGeoTransform[3] = adfBox[3];
    poDS->adfGeoTransform[4] = 0.0;
    poDS->adfGeoTransform[5] = -dfCellSize;

    poDS->nCellType = nCellType;

/* -------------------------------------------------------------------- */
/*      Set the access window.                                          */
/* -------------------------------------------------------------------- */
    double      adfAdjustedBox[4];

    pfnAccessWindowSet( adfBox, dfCellSize, adfAdjustedBox ); 

/* -------------------------------------------------------------------- */
/*      Create band information objects.                                */
/* -------------------------------------------------------------------- */
    poDS->papoBands = (GDALRasterBand **)VSICalloc(sizeof(GDALRasterBand *),
                                                   poDS->nBands);
	poDS->SetBand( 1, new GIORasterBand( poDS, 1 ));

    return( poDS );
}


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

CPLErr GIODataset::GetGeoTransform( double * padfTransform )

{
    memcpy( padfTransform, adfGeoTransform, sizeof(double)*6 );

    return( CE_None );
}

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

CPLErr GIODataset::SetGeoTransform( double * padfTransform )

{
    if( padfTransform[2] != 0.0 || padfTransform[4] != 0.0 )
        return CE_Failure;

    memcpy( adfGeoTransform, padfTransform, sizeof(double)*6 );

    return( CE_None );
}

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

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

{
    double            adfBox[4];
    int               nChannel;
    
/* -------------------------------------------------------------------- */
/*      Do some rudimentary argument checking.                          */
/* -------------------------------------------------------------------- */
    if( nBands != 1 )
    {
        CPLError( CE_Failure, CPLE_AppDefined, 
                  "AIGrid2 driver only supports one band datasets, not\n"
                  "%d bands as requested for %s.\n", 
                  nBands, pszFilename );

        return NULL;
    }

    if( eType != GDT_Float32 )
    {
        CPLError( CE_Failure, CPLE_AppDefined, 
                  "AIGrid2 driver only supports Float32 datasets, not\n"
                  "%s as requested for %s.\n", 
                  GDALGetDataTypeName(eType), pszFilename );

        return NULL;
    }

/* -------------------------------------------------------------------- */
/*      Call GridIOSetup(), if not called already.                      */
/* -------------------------------------------------------------------- */
    if( !nGridIOSetupCalled )
    {
        if( pfnGridIOSetup() != 1 )
            return NULL;

        nGridIOSetupCalled = TRUE;
    }

/* -------------------------------------------------------------------- */
/*      Set the access window.                                          */
/* -------------------------------------------------------------------- */
    double      adfAdjustedBox[4];
    
    adfBox[0] = -0.5;
    adfBox[1] = -0.5;
    adfBox[2] = nXSize-0.5;
    adfBox[3] = nYSize-0.5;

    pfnAccessWindowSet( adfBox, 1.0, adfAdjustedBox ); 
    
/* -------------------------------------------------------------------- */
/*      Create the file.                                                */
/* -------------------------------------------------------------------- */
    nChannel = pfnCellLayerCreate( (char *) pszFilename, 
                                   WRITEONLY, ROWIO, CELLFLOAT,
                                   1.0, adfBox );

    if( nChannel < 0 )
    {
        CPLError( CE_Failure, CPLE_AppDefined, 
                  "CellLayerCreate() failed, unable to create grid:\n%s",
                  pszFilename );
        return NULL;
    }
    
/* -------------------------------------------------------------------- */
/*      Create a corresponding GDALDataset.                             */
/* -------------------------------------------------------------------- */
    GIODataset 	*poDS;

    poDS = new GIODataset();

    poDS->pszPath = CPLStrdup( pszFilename );
    poDS->nGridChannel = nChannel;
    poDS->poDriver = poGIODriver;
    poDS->bCreated = TRUE;

/* -------------------------------------------------------------------- */
/*      Establish raster info.                                          */
/* -------------------------------------------------------------------- */
    poDS->nRasterXSize = nXSize;
    poDS->nRasterYSize = nYSize;
    poDS->nBands = 1;

    poDS->adfGeoTransform[0] = adfBox[0];
    poDS->adfGeoTransform[1] = 1.0;
    poDS->adfGeoTransform[2] = 0.0;
    poDS->adfGeoTransform[3] = adfBox[3];
    poDS->adfGeoTransform[4] = 0.0;
    poDS->adfGeoTransform[5] = -1.0;

    poDS->nCellType = CELLFLOAT;

/* -------------------------------------------------------------------- */
/*      Create band information objects.                                */
/* -------------------------------------------------------------------- */
    poDS->SetBand( 1, new GIORasterBand( poDS, 1 ) );

    return poDS;
}

/************************************************************************/
/*                               Delete()                               */
/************************************************************************/

CPLErr GIODataset::Delete( const char * pszGridName )

{
    VSIStatBuf      sStat;

    if( !nGridIOSetupCalled )
    {
        if( pfnGridIOSetup() != 1 )
            return CE_Failure;

        nGridIOSetupCalled = TRUE;
    }
    
    if( VSIStat( pszGridName, &sStat ) != 0 || !VSI_ISDIR( sStat.st_mode ) )
    {
        CPLError( CE_Failure, CPLE_AppDefined, 
                  "%s is not a grid directory.\n", 
                  pszGridName );

        return CE_Failure;
    }
    
    if( pfnGridDelete != NULL )
        pfnGridDelete( (char *) pszGridName );
    
    return CE_None;
}


/************************************************************************/
/*                        GDALRegister_AIGrid2()                        */
/************************************************************************/

void GDALRegister_AIGrid2()

{
    GDALDriver	*poDriver;

    if( poGIODriver == NULL && LoadGridIOFunctions() )
    {
        
        poGIODriver = poDriver = new GDALDriver();
        
        poDriver->pszShortName = "GIO";
        poDriver->pszLongName = "Arc/Info Binary Grid (avgridio.dll)";
        poDriver->pszHelpTopic = "frmt_various.html#GIO";
        
        poDriver->pfnOpen = GIODataset::Open;
        poDriver->pfnCreate = GIODataset::Create;
        poDriver->pfnDelete = GIODataset::Delete;

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

