/******************************************************************************
 * $Id: argdataset.cpp 28053 2014-12-04 09:31:07Z rouault $
 *
 * Project:  Azavea Raster Grid format driver.
 * Purpose:  Implements support for reading and writing Azavea Raster Grid
 *           format.
 * Author:   David Zwarg <dzwarg@azavea.com>
 *
 ******************************************************************************
 * Copyright (c) 2012, David Zwarg <dzwarg@azavea.com>
 * Copyright (c) 2012-2013, Even Rouault <even dot rouault at mines-paris dot org>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#include "rawdataset.h"
#include "cpl_string.h"
#include <json.h>
#include <ogr_spatialref.h>

CPL_CVSID("$Id: argdataset.cpp 28053 2014-12-04 09:31:07Z rouault $");

#define MAX_FILENAME_LEN 4096

#ifndef NAN
#  ifdef HUGE_VAL
#    define NAN (HUGE_VAL * 0.0)
#  else

static float CPLNaN(void)
{
    float fNan;
    int nNan = 0x7FC00000;
    memcpy(&fNan, &nNan, 4);
    return fNan;
}

#    define NAN CPLNaN()
#  endif
#endif

/************************************************************************/
/* ==================================================================== */
/*				ARGDataset				                                */
/* ==================================================================== */
/************************************************************************/

class ARGDataset : public RawDataset
{
        VSILFILE	*fpImage;	// image data file.
    
        double	adfGeoTransform[6];
        char * pszFilename;

    public:
        ARGDataset();
        ~ARGDataset();

        CPLErr 	GetGeoTransform( double * padfTransform );
   
        static int Identify( GDALOpenInfo * );
        static GDALDataset *Open( GDALOpenInfo * );
        static GDALDataset *CreateCopy( const char *, GDALDataset *, int, 
            char **, GDALProgressFunc, void *);
        virtual char ** GetFileList(void);
}; 

/************************************************************************/
/*                            ARGDataset()                              */
/************************************************************************/

ARGDataset::ARGDataset()
{
    adfGeoTransform[0] = 0.0;
    adfGeoTransform[1] = 1.0;
    adfGeoTransform[2] = 0.0;
    adfGeoTransform[3] = 0.0;
    adfGeoTransform[4] = 0.0;
    adfGeoTransform[5] = 1.0;
    fpImage = NULL;
}

/************************************************************************/
/*                            ~ARGDataset()                             */
/************************************************************************/

ARGDataset::~ARGDataset()

{
    CPLFree(pszFilename);

    FlushCache();
    if( fpImage != NULL )
        VSIFCloseL( fpImage );
}

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

CPLErr ARGDataset::GetGeoTransform( double * padfTransform )

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

    return CE_None;
}

/************************************************************************/
/*                         GetJsonFilename()                            */
/************************************************************************/
CPLString GetJsonFilename(CPLString pszFilename) 
{
    return CPLSPrintf( "%s/%s.json", CPLGetDirname(pszFilename), CPLGetBasename(pszFilename) );
}

/************************************************************************/
/*                           GetJsonObject()                            */
/************************************************************************/
json_object * GetJsonObject(CPLString pszFilename) 
{
    json_object * pJSONObject = NULL;
    CPLString osJSONFilename = GetJsonFilename(pszFilename);

    pJSONObject = json_object_from_file((char *)osJSONFilename.c_str());
    if (pJSONObject == NULL) {
        CPLDebug("ARGDataset", "GetJsonObject(): "
            "Could not parse JSON file.");
        return NULL;
    }

    return pJSONObject;
}

/************************************************************************/
/*                          GetJsonValueStr()                           */
/************************************************************************/
const char * GetJsonValueStr(json_object * pJSONObject, CPLString pszKey) 
{
    json_object * pJSONItem = json_object_object_get(pJSONObject, pszKey.c_str());
    if (pJSONItem == NULL) {
        CPLDebug("ARGDataset", "GetJsonValueStr(): "
            "Could not find '%s' in JSON.", pszKey.c_str());
        return NULL;
    }

    return json_object_get_string(pJSONItem);
}

/************************************************************************/
/*                          GetJsonValueDbl()                           */
/************************************************************************/
double GetJsonValueDbl(json_object * pJSONObject, CPLString pszKey) 
{
    const char *pszJSONStr = GetJsonValueStr(pJSONObject, pszKey.c_str());
    char *pszTmp;
    double fTmp;
    if (pszJSONStr == NULL) {
        return NAN;
    }
    pszTmp = (char *)pszJSONStr;
    fTmp = CPLStrtod(pszJSONStr, &pszTmp);
    if (pszTmp == pszJSONStr) {
        CPLDebug("ARGDataset", "GetJsonValueDbl(): "
            "Key value is not a numeric value: %s:%s", pszKey.c_str(), pszTmp);
        return NAN;
    }

    return fTmp;
}

/************************************************************************/
/*                           GetJsonValueInt()                          */
/************************************************************************/
int GetJsonValueInt(json_object * pJSONObject, CPLString pszKey) 
{
    double fTmp = GetJsonValueDbl(pJSONObject, pszKey.c_str());
    if (CPLIsNan(fTmp)) {
        return -1;
    }

    return (int)fTmp;
}

/************************************************************************/
/*                            GetFileList()                             */
/************************************************************************/
char ** ARGDataset::GetFileList()
{
    char **papszFileList = GDALPamDataset::GetFileList();
    CPLString osJSONFilename = GetJsonFilename(pszFilename);

    papszFileList = CSLAddString( papszFileList, osJSONFilename );

    return papszFileList;
}

/************************************************************************/
/*                              Identify()                              */
/************************************************************************/

int ARGDataset::Identify( GDALOpenInfo *poOpenInfo )
{
    json_object * pJSONObject;
    if (!EQUAL(CPLGetExtension(poOpenInfo->pszFilename), "arg")) {
        return FALSE;
    }

    pJSONObject = GetJsonObject(poOpenInfo->pszFilename);
    if (pJSONObject == NULL) {
        return FALSE;
    }

    json_object_put(pJSONObject);
    pJSONObject = NULL;

    return TRUE;
}

/************************************************************************/
/*                                Open()                                */
/************************************************************************/
GDALDataset *ARGDataset::Open( GDALOpenInfo * poOpenInfo )
{
    json_object * pJSONObject;
    const char * pszJSONStr;
    char * pszLayer;
    /***** items from the json metadata *****/
    GDALDataType eType = GDT_Unknown;
    double fXmin = 0.0;
    double fYmin = 0.0;
    double fXmax = 0.0;
    double fYmax = 0.0;
    double fCellwidth = 1.0;
    double fCellheight = 1.0;
    double fXSkew = 0.0;
    double fYSkew = 0.0;
    int nRows = 0;
    int nCols = 0;
    int nSrs = 3857;
    /***** items from the json metadata *****/
    int nPixelOffset = 0;
    double fNoDataValue = NAN;

    char * pszWKT = NULL;
    OGRSpatialReference oSRS;
    OGRErr nErr = OGRERR_NONE;

    if ( !Identify( poOpenInfo ) )
        return NULL;

/* -------------------------------------------------------------------- */
/*      Check metadata settings in JSON.                                */
/* -------------------------------------------------------------------- */

    pJSONObject = GetJsonObject(poOpenInfo->pszFilename);

    if (pJSONObject == NULL) {
        CPLError(CE_Failure, CPLE_AppDefined, "Error parsing JSON.");
        return NULL;
    }

    // get the type (always 'arg')
    pszJSONStr = GetJsonValueStr(pJSONObject, "type");
    if (pszJSONStr == NULL ) {
        CPLError(CE_Failure, CPLE_AppDefined,
            "The ARG 'type' is missing from the JSON file.");
        json_object_put(pJSONObject);
        pJSONObject = NULL;
        return NULL;
    }
    else if (!EQUAL(pszJSONStr, "arg")) {
        CPLError(CE_Failure, CPLE_AppDefined,
            "The ARG 'type' is not recognized: '%s'.", pszJSONStr);
        json_object_put(pJSONObject);
        pJSONObject = NULL;
        return NULL;
    }

    // get the datatype
    pszJSONStr = GetJsonValueStr(pJSONObject, "datatype");
    if (pszJSONStr == NULL) {
        CPLError(CE_Failure, CPLE_AppDefined,
            "The ARG 'datatype' is missing from the JSON file.");
        json_object_put(pJSONObject);
        pJSONObject = NULL;
        return NULL;
    }
    else if (EQUAL(pszJSONStr, "int8")) {
        CPLDebug("ARGDataset", "Open(): "
            "int8 data is not supported in GDAL -- mapped to uint8");
        eType = GDT_Byte; 
        nPixelOffset = 1;
        fNoDataValue = 128;
    }
    else if (EQUAL(pszJSONStr, "int16")) {
        eType = GDT_Int16;
        nPixelOffset = 2;
        fNoDataValue = -32767;
    }
    else if (EQUAL(pszJSONStr, "int32")) {
        eType = GDT_Int32;
        nPixelOffset = 4;
        fNoDataValue = -2e31;
    }
    else if (EQUAL(pszJSONStr, "uint8")) {
        eType = GDT_Byte; 
        nPixelOffset = 1;
        fNoDataValue = 255;
    }
    else if (EQUAL(pszJSONStr, "uint16")) {
        eType = GDT_UInt16;
        nPixelOffset = 2;
        fNoDataValue = 65535;
    }
    else if (EQUAL(pszJSONStr, "uint32")) {
        eType = GDT_UInt32;
        nPixelOffset = 4;
        fNoDataValue = -2e31;
    }
    else if (EQUAL(pszJSONStr, "float32")) {
        eType = GDT_Float32;
        nPixelOffset = 4;
        fNoDataValue = NAN;
    }
    else if (EQUAL(pszJSONStr, "float64")) { 
        eType = GDT_Float64;
        nPixelOffset = 8;
        fNoDataValue = NAN;
    }
    else {
        if (EQUAL(pszJSONStr, "int64") ||
            EQUAL(pszJSONStr, "uint64")) {
            CPLError(CE_Failure, CPLE_AppDefined,
                "The ARG 'datatype' is unsupported in GDAL: '%s'.", pszJSONStr);
        }
        else {
            CPLError(CE_Failure, CPLE_AppDefined,
                "The ARG 'datatype' is unknown: '%s'.", pszJSONStr);
        }
        json_object_put(pJSONObject);
        pJSONObject = NULL;
        return NULL;
    }

    // get the xmin of the bounding box
    fXmin = GetJsonValueDbl(pJSONObject, "xmin");
    if (CPLIsNan(fXmin)) {
        CPLError(CE_Failure, CPLE_AppDefined,
            "The ARG 'xmin' is missing or invalid.");
        json_object_put(pJSONObject);
        pJSONObject = NULL;
        return NULL;
    }
    
    // get the ymin of the bounding box
    fYmin = GetJsonValueDbl(pJSONObject, "ymin");
    if (CPLIsNan(fYmin)) {
        CPLError(CE_Failure, CPLE_AppDefined,
            "The ARG 'ymin' is missing or invalid.");
        json_object_put(pJSONObject);
        pJSONObject = NULL;
        return NULL;
    }

    // get the xmax of the bounding box
    fXmax = GetJsonValueDbl(pJSONObject, "xmax");
    if (CPLIsNan(fXmax)) {
        CPLError(CE_Failure, CPLE_AppDefined,
            "The ARG 'xmax' is missing or invalid.");
        json_object_put(pJSONObject);
        pJSONObject = NULL;
        return NULL;
    }

    // get the ymax of the bounding box
    fYmax = GetJsonValueDbl(pJSONObject, "ymax");
    if (CPLIsNan(fYmax)) {
        CPLError(CE_Failure, CPLE_AppDefined,
            "The ARG 'ymax' is missing or invalid.");
        json_object_put(pJSONObject);
        pJSONObject = NULL;
        return NULL;
    }

    // get the cell width
    fCellwidth = GetJsonValueDbl(pJSONObject, "cellwidth");
    if (CPLIsNan(fCellwidth)) {
        CPLError(CE_Failure, CPLE_AppDefined,
            "The ARG 'cellwidth' is missing or invalid.");
        json_object_put(pJSONObject);
        pJSONObject = NULL;
        return NULL;
    }

    // get the cell height
    fCellheight = GetJsonValueDbl(pJSONObject, "cellheight");
    if (CPLIsNan(fCellheight)) {
        CPLError(CE_Failure, CPLE_AppDefined,
            "The ARG 'cellheight' is missing or invalid.");
        json_object_put(pJSONObject);
        pJSONObject = NULL;
        return NULL;
    }

    fXSkew = GetJsonValueDbl(pJSONObject, "xskew");
    if (CPLIsNan(fXSkew)) {
        // not an error -- default to 0.0
        fXSkew = 0.0f;
    }

    fYSkew = GetJsonValueDbl(pJSONObject, "yskew");
    if (CPLIsNan(fYSkew)) {
        // not an error -- default to 0.0
        fYSkew = 0.0f;
    }

    // get the rows
    nRows = GetJsonValueInt(pJSONObject, "rows");
    if (nRows < 0) {
        CPLError(CE_Failure, CPLE_AppDefined,
            "The ARG 'rows' is missing or invalid.");
        json_object_put(pJSONObject);
        pJSONObject = NULL;
        return NULL;
    }

    // get the columns
    nCols = GetJsonValueInt(pJSONObject, "cols");
    if (nCols < 0) {
        CPLError(CE_Failure, CPLE_AppDefined,
            "The ARG 'cols' is missing or invalid.");
        json_object_put(pJSONObject);
        pJSONObject = NULL;
        return NULL;
    }

    nSrs = GetJsonValueInt(pJSONObject, "epsg");
    if (nSrs < 0) {
        // not an error -- default to web mercator
        nSrs = 3857;
    }

    nErr = oSRS.importFromEPSG(nSrs);
    if (nErr != OGRERR_NONE) {
        nErr = oSRS.importFromEPSG(3857);

        if (nErr == OGRERR_NONE) {
            CPLDebug("ARGDataset", "Open(): "
                "The EPSG provided did not import cleanly. Defaulting to EPSG:3857");
        }
        else {
            CPLError(CE_Failure, CPLE_AppDefined,
                "The 'epsg' value did not transate to a known spatial reference."
                " Please check the 'epsg' value and try again.");

            json_object_put(pJSONObject);
            pJSONObject = NULL;

            return NULL;
        }
    }

    nErr = oSRS.exportToWkt(&pszWKT);
    if (nErr != OGRERR_NONE) {
        CPLError(CE_Failure, CPLE_AppDefined,
            "The spatial reference is known, but could not be set on the "
            "dataset. Please check the 'epsg' value and try again.");

        json_object_put(pJSONObject);
        pJSONObject = NULL;

        return NULL;
    }

    // get the layer (always the file basename)
    pszJSONStr = GetJsonValueStr(pJSONObject, "layer");
    if (pszJSONStr == NULL) {
        CPLError(CE_Failure, CPLE_AppDefined,
            "The ARG 'layer' is missing from the JSON file.");
        json_object_put(pJSONObject);
        pJSONObject = NULL;
        return NULL;
    }

    pszLayer = CPLStrdup(pszJSONStr);

    // done with the json object now
    json_object_put(pJSONObject);
    pJSONObject = NULL;

/* -------------------------------------------------------------------- */
/*      Create a corresponding GDALDataset.                             */
/* -------------------------------------------------------------------- */
    ARGDataset *poDS;

    poDS = new ARGDataset();

    poDS->pszFilename = CPLStrdup(poOpenInfo->pszFilename);
    poDS->SetMetadataItem("LAYER",pszLayer,NULL);
    poDS->nRasterXSize = nCols;
    poDS->nRasterYSize = nRows;
    poDS->SetProjection( pszWKT );

    // done with the projection string
    CPLFree(pszWKT);
    CPLFree(pszLayer);

/* -------------------------------------------------------------------- */
/*      Assume ownership of the file handled from the GDALOpenInfo.     */
/* -------------------------------------------------------------------- */
    poDS->fpImage = VSIFOpenL(poOpenInfo->pszFilename, "rb");
    if (poDS->fpImage == NULL)
    {
        delete poDS;
        CPLError(CE_Failure, CPLE_AppDefined,
            "Could not open dataset '%s'", poOpenInfo->pszFilename);
        return NULL;
    }

    poDS->adfGeoTransform[0] = fXmin;
    poDS->adfGeoTransform[1] = fCellwidth;
    poDS->adfGeoTransform[2] = fXSkew;
    poDS->adfGeoTransform[3] = fYmax;
    poDS->adfGeoTransform[4] = fYSkew;
    poDS->adfGeoTransform[5] = -fCellheight;
    
/* -------------------------------------------------------------------- */
/*      Create band information objects.                                */
/* -------------------------------------------------------------------- */
    RawRasterBand *poBand;

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

    poBand = new RawRasterBand( poDS, 1, poDS->fpImage,
                                0, nPixelOffset, nPixelOffset * nCols,
                                eType, bNative, TRUE );
    poDS->SetBand( 1, poBand );

    poBand->SetNoDataValue( fNoDataValue );

/* -------------------------------------------------------------------- */
/*      Initialize any PAM information.                                 */
/* -------------------------------------------------------------------- */
    poDS->SetDescription( poOpenInfo->pszFilename );
    poDS->TryLoadXML();
    
/* -------------------------------------------------------------------- */
/*      Check for overviews.                                            */
/* -------------------------------------------------------------------- */
    poDS->oOvManager.Initialize( poDS, poOpenInfo->pszFilename );

    return( poDS );
}

/************************************************************************/
/*                          CreateCopy()                                */
/************************************************************************/
GDALDataset * ARGDataset::CreateCopy( const char * pszFilename,
                                      GDALDataset * poSrcDS,
                                      CPL_UNUSED int bStrict,
                                      CPL_UNUSED char ** papszOptions,
                                      CPL_UNUSED GDALProgressFunc pfnProgress,
                                      CPL_UNUSED void * pProgressData )
{
    int nBands = poSrcDS->GetRasterCount();
    int nXSize = poSrcDS->GetRasterXSize();
    int nYSize = poSrcDS->GetRasterYSize();
    int nXBlockSize, nYBlockSize, nPixelOffset = 0;
    GDALDataType eType;
    CPLString osJSONFilename;
    CPLString pszDataType;
    json_object * poJSONObject = NULL;
    double adfTransform[6];
    GDALRasterBand * poSrcBand = NULL;
    RawRasterBand * poDstBand = NULL;
    VSILFILE * fpImage = NULL;
    void * pabyData;
    OGRSpatialReference oSRS;
    char * pszWKT = NULL;
    char ** pszTokens = NULL;
    const char * pszLayer = NULL;
    int nSrs = 0;
    OGRErr nErr = OGRERR_NONE;
    CPLErr eErr;

    if( nBands != 1 )
    {
        CPLError( CE_Failure, CPLE_NotSupported, 
              "ARG driver doesn't support %d bands.  Must be 1 band.", nBands );
        return NULL;
    }

    eType = poSrcDS->GetRasterBand(1)->GetRasterDataType();
    if( eType == GDT_Unknown || 
        eType == GDT_CInt16 || 
        eType == GDT_CInt32 ||
        eType == GDT_CFloat32 || 
        eType == GDT_CFloat64 )
    {
        CPLError( CE_Failure, CPLE_NotSupported, 
                  "ARG driver doesn't support data type %s.",
                  GDALGetDataTypeName(eType) );
        return NULL;
    }
    else if (eType == GDT_Int16) {
        pszDataType = "int16";
        nPixelOffset = 2;
    }
    else if (eType == GDT_Int32) {
        pszDataType = "int32";
        nPixelOffset = 4;
    }
    else if (eType == GDT_Byte) {
        pszDataType = "uint8";
        nPixelOffset = 1;
    }
    else if (eType == GDT_UInt16) {
        pszDataType = "uint16";
        nPixelOffset = 2;
    }
    else if (eType == GDT_UInt32) {
        pszDataType = "uint32";
        nPixelOffset = 4;
    }
    else if (eType == GDT_Float32) {
        pszDataType = "float32";
        nPixelOffset = 4;
    }
    else if (eType == GDT_Float64) {
        pszDataType = "float64";
        nPixelOffset = 8;
    }

    poSrcDS->GetGeoTransform( adfTransform );

    pszWKT = (char *)poSrcDS->GetProjectionRef();
    nErr = oSRS.importFromWkt(&pszWKT);
    if (nErr != OGRERR_NONE) {
        CPLError( CE_Failure, CPLE_NotSupported,
              "Cannot import spatial reference WKT from source dataset.");
        return NULL;
    }

    if (oSRS.GetAuthorityCode("PROJCS") != NULL) {
        nSrs = atoi(oSRS.GetAuthorityCode("PROJCS"));
    }
    else if (oSRS.GetAuthorityCode("GEOGCS") != NULL) {
        nSrs = atoi(oSRS.GetAuthorityCode("GEOGCS"));
    }
    else {
        // could not determine projected or geographic code
        // default to EPSG:3857 if no code could be found
        nSrs = 3857;
    }

    /********************************************************************/
    /* Create JSON companion file.                                      */
    /********************************************************************/
    osJSONFilename = GetJsonFilename(pszFilename);

    poJSONObject = json_object_new_object();

    pszTokens = poSrcDS->GetMetadata();
    pszLayer = CSLFetchNameValue(pszTokens, "LAYER");

    if ( pszLayer == NULL) {
        // Set the layer
        json_object_object_add(poJSONObject, "layer", json_object_new_string(
            CPLGetBasename(osJSONFilename)
        ));
    }
    else {
        // Set the layer
        json_object_object_add(poJSONObject, "layer", json_object_new_string(
            pszLayer
        ));
    }

    // Set the type
    json_object_object_add(poJSONObject, "type", json_object_new_string("arg"));
    // Set the datatype
    json_object_object_add(poJSONObject, "datatype", json_object_new_string(pszDataType));
    // Set the number of rows
    json_object_object_add(poJSONObject, "rows", json_object_new_int(nYSize));
    // Set the number of columns
    json_object_object_add(poJSONObject, "cols", json_object_new_int(nXSize));
    // Set the xmin
    json_object_object_add(poJSONObject, "xmin", json_object_new_double(adfTransform[0]));
    // Set the ymax
    json_object_object_add(poJSONObject, "ymax", json_object_new_double(adfTransform[3]));
    // Set the cellwidth
    json_object_object_add(poJSONObject, "cellwidth", json_object_new_double(adfTransform[1]));
    // Set the cellheight
    json_object_object_add(poJSONObject, "cellheight", json_object_new_double(-adfTransform[5]));
    // Set the xmax
    json_object_object_add(poJSONObject, "xmax", json_object_new_double(adfTransform[0] + nXSize * adfTransform[1]));
    // Set the ymin
    json_object_object_add(poJSONObject, "ymin", json_object_new_double(adfTransform[3] + nYSize * adfTransform[5]));
    // Set the xskew
    json_object_object_add(poJSONObject, "xskew", json_object_new_double(adfTransform[2]));
    // Set the yskew
    json_object_object_add(poJSONObject, "yskew", json_object_new_double(adfTransform[4]));
    if (nSrs > 0) {
        // Set the epsg
        json_object_object_add(poJSONObject, "epsg", json_object_new_int(nSrs));
    }

    if (json_object_to_file((char *)osJSONFilename.c_str(), poJSONObject) < 0) {
        CPLError( CE_Failure, CPLE_NotSupported, 
                  "ARG driver can't write companion file.");

        json_object_put(poJSONObject);
        poJSONObject = NULL;

        return NULL;
    }

    json_object_put(poJSONObject);
    poJSONObject = NULL;

    fpImage = VSIFOpenL(pszFilename, "wb");
    if (fpImage == NULL)
    {
        CPLError( CE_Failure, CPLE_NotSupported, 
              "ARG driver can't create data file %s.", pszFilename);

        // remove JSON file
        VSIUnlink( osJSONFilename.c_str() );

        return NULL;
    }

    // only 1 raster band
    poSrcBand = poSrcDS->GetRasterBand( 1 );

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

    poDstBand = new RawRasterBand( fpImage, 0, nPixelOffset,
                                   nPixelOffset * nXSize, eType, bNative,
                                   nXSize, nYSize, TRUE, FALSE);

    poSrcBand->GetBlockSize(&nXBlockSize, &nYBlockSize);

    pabyData = CPLMalloc(nXBlockSize * nPixelOffset);

    // convert any blocks into scanlines
    for (int nYBlock = 0; nYBlock * nYBlockSize < nYSize; nYBlock++) {
        for (int nYScanline = 0; nYScanline < nYBlockSize; nYScanline++) {
            if ((nYScanline+1) + nYBlock * nYBlockSize > poSrcBand->GetYSize() ) {
                continue;
            }

            for (int nXBlock = 0; nXBlock * nXBlockSize < nXSize; nXBlock++) {
                int nXValid;

                if( (nXBlock+1) * nXBlockSize > poSrcBand->GetXSize() )
                    nXValid = poSrcBand->GetXSize() - nXBlock * nXBlockSize;
                else
                    nXValid = nXBlockSize;

                eErr = poSrcBand->RasterIO(GF_Read, nXBlock * nXBlockSize, 
                    nYBlock * nYBlockSize + nYScanline, nXValid, 1, pabyData, nXBlockSize, 
                    1, eType, 0, 0, NULL);

                if (eErr != CE_None) {
                    CPLError(CE_Failure, CPLE_AppDefined, "Error reading.");

                    CPLFree( pabyData );
                    delete poDstBand;
                    VSIFCloseL( fpImage );

                    return NULL;
                }

                eErr = poDstBand->RasterIO(GF_Write, nXBlock * nXBlockSize, 
                    nYBlock * nYBlockSize + nYScanline, nXValid, 1, pabyData, nXBlockSize, 
                    1, eType, 0, 0, NULL);

                if (eErr != CE_None) {
                    CPLError(CE_Failure, CPLE_AppDefined, "Error writing.");

                    CPLFree( pabyData );
                    delete poDstBand;
                    VSIFCloseL( fpImage );

                    return NULL;
                }
            }
        }
    }

    CPLFree( pabyData );
    delete poDstBand;
    VSIFCloseL( fpImage );

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

/************************************************************************/
/*                          GDALRegister_ARG()                          */
/************************************************************************/

void GDALRegister_ARG()
{
    GDALDriver	*poDriver;

    if( GDALGetDriverByName( "ARG" ) == NULL )
    {
        poDriver = new GDALDriver();
        
        poDriver->SetDescription( "ARG" );
        poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" );
        poDriver->SetMetadataItem( GDAL_DMD_LONGNAME, 
                                   "Azavea Raster Grid format" );
        poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC, 
                                   "frmt_various.html#ARG" );
        poDriver->SetMetadataItem( GDAL_DCAP_VIRTUALIO, "YES" );

        poDriver->pfnIdentify = ARGDataset::Identify;
        poDriver->pfnOpen = ARGDataset::Open;
        poDriver->pfnCreateCopy = ARGDataset::CreateCopy;

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