/******************************************************************************
 *
 * Project:  GRASS Driver
 * Purpose:  Implement GRASS raster read/write support
 * Author:   Frank Warmerdam, warmerda@home.com
 *
 ******************************************************************************
 * Copyright (c) 2000, Frank Warmerdam
 * Copyright (c) 2007-2009, 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 <libgrass.h>

#include "cpl_string.h"
#include "gdal_frmts.h"
#include "gdal_priv.h"
#include "ogr_spatialref.h"

CPL_CVSID("$Id: grassdataset.cpp 36501 2016-11-25 14:09:24Z rouault $");

/************************************************************************/
/*                         Grass2CPLErrorHook()                         */
/************************************************************************/

int Grass2CPLErrorHook( char * pszMessage, int bFatal )

{
    if( !bFatal )
        CPLDebug( "libgrass", "%s", pszMessage );
    else
        CPLError( CE_Fatal, CPLE_AppDefined, "libgrass: %s", pszMessage );

    return 0;
}

/************************************************************************/
/* ==================================================================== */
/*                              GRASSDataset                            */
/* ==================================================================== */
/************************************************************************/

class GRASSRasterBand;

class GRASSDataset : public GDALDataset
{
    friend class GRASSRasterBand;

    char        *pszProjection;

    double      adfGeoTransform[6];

  public:
                 GRASSDataset();
                 ~GRASSDataset();

    virtual const char *GetProjectionRef(void) override;
    virtual CPLErr GetGeoTransform( double * ) override;

    static GDALDataset *Open( GDALOpenInfo * );
};

/************************************************************************/
/* ==================================================================== */
/*                            GRASSRasterBand                           */
/* ==================================================================== */
/************************************************************************/

class GRASSRasterBand : public GDALRasterBand
{
    friend class GRASSDataset;

    int         hCell;
    int         nGRSType;

    GDALColorTable *poCT;

    int         bHaveMinMax;
    double      dfCellMin;
    double      dfCellMax;

    double      dfNoData;

  public:

                   GRASSRasterBand( GRASSDataset *, int,
                                    const char *, const char * );
    virtual        ~GRASSRasterBand();

    virtual CPLErr IReadBlock( int, int, void * ) override;
    virtual GDALColorInterp GetColorInterpretation() override;
    virtual GDALColorTable *GetColorTable() override;
    virtual double GetMinimum( int *pbSuccess = NULL ) override;
    virtual double GetMaximum( int *pbSuccess = NULL ) override;
    virtual double GetNoDataValue( int *pbSuccess = NULL ) override;
};

/************************************************************************/
/*                          GRASSRasterBand()                           */
/************************************************************************/

GRASSRasterBand::GRASSRasterBand( GRASSDataset *poDS, int nBand,
                                  const char * pszMapset,
                                  const char * pszCellName )

{
    struct Cell_head sCellInfo;

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

    G_get_cellhd( (char *) pszCellName, (char *) pszMapset, &sCellInfo );
    nGRSType = G_raster_map_type( (char *) pszCellName, (char *) pszMapset );

/* -------------------------------------------------------------------- */
/*      Get min/max values.                                             */
/* -------------------------------------------------------------------- */
    struct FPRange sRange;

    if( G_read_fp_range( (char *) pszCellName, (char *) pszMapset,
                         &sRange ) == -1 )
    {
        bHaveMinMax = FALSE;
    }
    else
    {
        bHaveMinMax = TRUE;
        G_get_fp_range_min_max( &sRange, &dfCellMin, &dfCellMax );
    }

/* -------------------------------------------------------------------- */
/*      Setup band type, and preferred nodata value.                    */
/* -------------------------------------------------------------------- */
    dfNoData = 0.0;
    if( nGRSType == CELL_TYPE && sCellInfo.format == 0 )
    {
        if( bHaveMinMax && dfCellMin < 1.0 && dfCellMax > 254.0 )
        {
            this->eDataType = GDT_UInt16;
            dfNoData = 256.0;
        }
        else
        {
            this->eDataType = GDT_Byte;
            if( dfCellMax < 255.0 )
                dfNoData = 255.0;
            else
                dfNoData = 0.0;
        }
    }
    else if( nGRSType == CELL_TYPE && sCellInfo.format == 1 )
    {
        this->eDataType = GDT_UInt16;
        dfNoData = 65535.0;
    }
    else if( nGRSType == CELL_TYPE )
    {
        this->eDataType = GDT_UInt32;
        dfNoData = 65535.0;
    }
    else if( nGRSType == FCELL_TYPE )
    {
        this->eDataType = GDT_Float32;
        dfNoData = -12345.0;
    }
    else if( nGRSType == DCELL_TYPE )
    {
        this->eDataType = GDT_Float64;
        dfNoData = -12345.0;
    }

    nBlockXSize = poDS->nRasterXSize;
    nBlockYSize = 1;

    hCell = G_open_cell_old((char *) pszCellName, (char *) pszMapset);

/* -------------------------------------------------------------------- */
/*      Do we have a color table?                                       */
/* -------------------------------------------------------------------- */
    struct Colors sGrassColors;

    poCT = NULL;
    if( G_read_colors( (char *) pszCellName, (char *) pszMapset,
                       &sGrassColors ) == 1 )
    {
        poCT = new GDALColorTable();
        for( int iColor = 0; iColor < 256; iColor++ )
        {
            int nRed, nGreen, nBlue;
            GDALColorEntry    sColor;

            if( G_get_color( iColor, &nRed, &nGreen, &nBlue, &sGrassColors ) )
            {
                sColor.c1 = nRed;
                sColor.c2 = nGreen;
                sColor.c3 = nBlue;
                sColor.c4 = 255;

                poCT->SetColorEntry( iColor, &sColor );
            }
            else
            {
                sColor.c1 = 0;
                sColor.c2 = 0;
                sColor.c3 = 0;
                sColor.c4 = 0;

                poCT->SetColorEntry( iColor, &sColor );
            }
        }

        G_free_colors( &sGrassColors );
    }
}

/************************************************************************/
/*                          ~GRASSRasterBand()                          */
/************************************************************************/

GRASSRasterBand::~GRASSRasterBand()

{
    if( poCT != NULL )
        delete poCT;

    if( hCell >= 0 )
        G_close_cell( hCell );
}

/************************************************************************/
/*                             IReadBlock()                             */
/*                                                                      */
/*      We only do "null" testing for floating point values.  We        */
/*      assume integer values are having the null raster entries set    */
/*      to zero which is the "nodata" value for integer layers.         */
/************************************************************************/

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

{
    char *pachNullBuf = (char *) CPLMalloc(nBlockXSize);
    G_get_null_value_row( hCell, pachNullBuf, nBlockYOff );

    if( eDataType == GDT_Float32 || eDataType == GDT_Float64
        || eDataType == GDT_UInt32 )
    {
        G_get_raster_row( hCell, pImage, nBlockYOff, nGRSType  );

        for( int i = 0; i < nBlockXSize; i++ )
        {
            if( pachNullBuf[i] != 0 )
            {
                if( eDataType == GDT_UInt32 )
                    ((GUInt32 *) pImage)[i] = (GUInt32) dfNoData;
                else if( eDataType == GDT_Float32 )
                    ((float *) pImage)[i] = dfNoData;
                else
                    ((double *) pImage)[i] = dfNoData;
            }
        }
    }
    else
    {
        GUInt32 *panRow = (GUInt32 *) CPLMalloc(4 * nBlockXSize);

        G_get_raster_row( hCell, panRow, nBlockYOff, nGRSType  );

        for( int i = 0; i < nBlockXSize; i++ )
        {
            if( pachNullBuf[i] != 0 )
                panRow[i] = (GUInt32) dfNoData;
        }

        GDALCopyWords( panRow, GDT_UInt32, 4,
                       pImage, eDataType, GDALGetDataTypeSize(eDataType)/8,
                       nBlockXSize );

        CPLFree( panRow );
    }

    CPLFree( pachNullBuf );

    return CE_None;
}

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

GDALColorInterp GRASSRasterBand::GetColorInterpretation()

{
    if( poCT != NULL )
        return GCI_PaletteIndex;
    else
        return GCI_GrayIndex;
}

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

GDALColorTable *GRASSRasterBand::GetColorTable()

{
    return poCT;
}

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

double GRASSRasterBand::GetMinimum( int *pbSuccess )

{
    if( pbSuccess )
        *pbSuccess = bHaveMinMax;

    if( bHaveMinMax )
        return dfCellMin;

    else if( eDataType == GDT_Float32 || eDataType == GDT_Float64 )
        return -4294967295.0;
    else
        return 0;
}

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

double GRASSRasterBand::GetMaximum( int *pbSuccess )

{
    if( pbSuccess )
        *pbSuccess = bHaveMinMax;

    if( bHaveMinMax )
        return dfCellMax;

    else if( eDataType == GDT_Float32 || eDataType == GDT_Float64 )
        return 4294967295.0;
    else if( eDataType == GDT_UInt32 )
        return 4294967295.0;
    else if( eDataType == GDT_UInt16 )
        return 65535;
    else
        return 255;
}

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

double GRASSRasterBand::GetNoDataValue( int *pbSuccess )

{
    if( pbSuccess )
        *pbSuccess = TRUE;

    return dfNoData;
}

/************************************************************************/
/* ==================================================================== */
/*                             GRASSDataset                             */
/* ==================================================================== */
/************************************************************************/

/************************************************************************/
/*                            GRASSDataset()                            */
/************************************************************************/

GRASSDataset::GRASSDataset()

{
    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;
}

/************************************************************************/
/*                           ~GRASSDataset()                            */
/************************************************************************/

GRASSDataset::~GRASSDataset()

{
    CPLFree( pszProjection );
}

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

const char *GRASSDataset::GetProjectionRef()
{
    if( pszProjection == NULL )
        return "";
    else
        return pszProjection;
}

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

CPLErr GRASSDataset::GetGeoTransform( double * padfGeoTransform )
{
    memcpy( padfGeoTransform, adfGeoTransform, sizeof(double) * 6 );

    return CE_None;
}

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

typedef int (*GrassErrorHandler)();

GDALDataset *GRASSDataset::Open( GDALOpenInfo * poOpenInfo )

{
    static int  bDoneGISInit = FALSE;
    char        *pszMapset = NULL, *pszCell = NULL;
    char        **papszCells = NULL;
    char        **papszMapsets = NULL;

    if( !bDoneGISInit )
    {
        G_set_error_routine( (GrassErrorHandler) Grass2CPLErrorHook );
        G_gisinit_2( "GDAL", NULL, NULL, NULL );
    }

/* -------------------------------------------------------------------- */
/*      Check if this is a valid grass cell.                            */
/* -------------------------------------------------------------------- */
    if( G_check_cell( poOpenInfo->pszFilename, &pszMapset, &pszCell ) )
    {
        papszCells = CSLAddString( papszCells, pszCell );
        papszMapsets = CSLAddString( papszMapsets, pszMapset );

        G_free( pszMapset );
        G_free( pszCell );
    }

/* -------------------------------------------------------------------- */
/*      Check if this is a valid GRASS imagery group.                   */
/* -------------------------------------------------------------------- */
    else if( I_check_group( poOpenInfo->pszFilename, &pszMapset, &pszCell ) )
    {
        struct Ref ref;

        I_init_group_ref( &ref );
        I_get_group_ref( pszCell, &ref );

        for( int iRef = 0; iRef < ref.nfiles; iRef++ )
        {
            papszCells = CSLAddString( papszCells, ref.file[iRef].name );
            papszMapsets = CSLAddString( papszMapsets, ref.file[iRef].mapset );
        }

        I_free_group_ref( &ref );

        G_free( pszMapset );
        G_free( pszCell );
    }

    else
        return NULL;

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

    /* notdef: should only allow read access to an existing cell, right? */
    poDS->eAccess = poOpenInfo->eAccess;

/* -------------------------------------------------------------------- */
/*      Capture some information from the file that is of interest.     */
/* -------------------------------------------------------------------- */
    struct Cell_head sCellInfo;

    if( G_get_cellhd( papszCells[0], papszMapsets[0], &sCellInfo ) != 0 )
    {
        /* notdef: report failure. */
        return NULL;
    }

    poDS->nRasterXSize = sCellInfo.cols;
    poDS->nRasterYSize = sCellInfo.rows;

    G_set_window( &sCellInfo );

    poDS->adfGeoTransform[0] = sCellInfo.west;
    poDS->adfGeoTransform[1] = sCellInfo.ew_res;
    poDS->adfGeoTransform[2] = 0.0;
    poDS->adfGeoTransform[3] = sCellInfo.north;
    poDS->adfGeoTransform[4] = 0.0;
    poDS->adfGeoTransform[5] = -1 * sCellInfo.ns_res;

/* -------------------------------------------------------------------- */
/*      Try to get a projection definition.                             */
/* -------------------------------------------------------------------- */
    char *pszProj4 = G_get_cell_as_proj4( papszCells[0], papszMapsets[0] );
    if( pszProj4 != NULL )
    {
        OGRSpatialReference   oSRS;

        if( oSRS.importFromProj4( pszProj4 ) == OGRERR_NONE )
        {
            oSRS.exportToWkt( &(poDS->pszProjection) );
        }

        G_free( pszProj4 );
    }

/* -------------------------------------------------------------------- */
/*      Create band information objects.                                */
/* -------------------------------------------------------------------- */
    for( int iBand = 0; papszCells[iBand] != NULL; iBand++ )
    {
        poDS->SetBand( iBand+1,
                       new GRASSRasterBand( poDS, iBand+1,
                                            papszMapsets[iBand],
                                            papszCells[iBand] ) );
    }

/* -------------------------------------------------------------------- */
/*      Confirm the requested access is supported.                      */
/* -------------------------------------------------------------------- */
    if( poOpenInfo->eAccess == GA_Update )
    {
        delete poDS;
        CPLError( CE_Failure, CPLE_NotSupported,
                  "The GRASS driver does not support update access to existing"
                  " datasets.\n" );
        return NULL;
    }

    return poDS;
}

/************************************************************************/
/*                          GDALRegister_GRASS()                        */
/************************************************************************/

void GDALRegister_GRASS()

{
    if( !GDAL_CHECK_VERSION( "GDAL/GRASS driver" ) )
        return;

    if( GDALGetDriverByName( "GRASS" ) != NULL )
        return;

    GDALDriver *poDriver = new GDALDriver();

    poDriver->SetDescription( "GRASS" );
    poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" );
    poDriver->SetMetadataItem( GDAL_DMD_LONGNAME, "GRASS Database Rasters" );
    poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC, "frmt_grass.html" );

    poDriver->pfnOpen = GRASSDataset::Open;

    GetGDALDriverManager()->RegisterDriver( poDriver );
}
