/******************************************************************************
 * $Id: hdf5dataset.cpp 20438 2010-08-25 17:20:18Z chaitanya $
 *
 * Project:  Hierarchical Data Format Release 5 (HDF5)
 * Purpose:  HDF5 Datasets. Open HDF5 file, fetch metadata and list of
 *           subdatasets.
 *           This driver initially based on code supplied by Markus Neteler
 * Author:  Denis Nadeau <denis.nadeau@gmail.com>
 *
 ******************************************************************************
 * Copyright (c) 2005, 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.
 ****************************************************************************/

#define H5_USE_16_API

#define MAX_METADATA_LEN 32768

#include "hdf5.h"

#include "gdal_priv.h"
#include "cpl_string.h"
#include "hdf5dataset.h"

CPL_CVSID("$Id: hdf5dataset.cpp 20438 2010-08-25 17:20:18Z chaitanya $");

CPL_C_START
void	GDALRegister_HDF5(void);
CPL_C_END



/************************************************************************/
/* ==================================================================== */
/*				HDF5Dataset				*/
/* ==================================================================== */
/************************************************************************/

/************************************************************************/
/*                        GDALRegister_HDF5()				*/
/************************************************************************/
void GDALRegister_HDF5()
    
{
    GDALDriver	*poDriver;
    if( GDALGetDriverByName("HDF5") == NULL )
	{
	    poDriver = new GDALDriver();
	    poDriver->SetDescription("HDF5");
	    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, 
				      "Hierarchical Data Format Release 5");
	    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, 
				      "frmt_hdf5.html");
	    poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "hdf5");
	    poDriver->pfnOpen = HDF5Dataset::Open;
	    poDriver->pfnIdentify = HDF5Dataset::Identify;
	    GetGDALDriverManager()->RegisterDriver(poDriver);
	}
}

/************************************************************************/
/*                           HDF5Dataset()                      	*/
/************************************************************************/
HDF5Dataset::HDF5Dataset()
{
    papszSubDatasets    = NULL;
    papszMetadata       = NULL;
    poH5RootGroup       = NULL;
    nSubDataCount       = 0;
    hHDF5               = -1;
    hDatasetID          = -1;
    hGroupID            = -1;
    bIsHDFEOS           = FALSE;
    nDatasetType        = -1;
}

/************************************************************************/
/*                            ~HDF5Dataset()                            */
/************************************************************************/
HDF5Dataset::~HDF5Dataset()
{
    CSLDestroy( papszMetadata );
    if( hHDF5 > 0 )
	H5Fclose( hHDF5 );
    if( hGroupID > 0 )
	H5Gclose( hGroupID );
    CSLDestroy( papszSubDatasets );
    if( poH5RootGroup != NULL ){
	DestroyH5Objects( poH5RootGroup );
	CPLFree( poH5RootGroup->pszName );
	CPLFree( poH5RootGroup->pszPath );
	CPLFree( poH5RootGroup->pszUnderscorePath );
	CPLFree( poH5RootGroup->poHchild );
	CPLFree( poH5RootGroup );
    }
}

/************************************************************************/
/*                            GetDataType()                             */
/*                                                                      */
/*      Transform HDF5 datatype to GDAL datatype                        */
/************************************************************************/
GDALDataType HDF5Dataset::GetDataType(hid_t TypeID) 
{
    if( H5Tequal( H5T_NATIVE_CHAR,        TypeID ) )
	return GDT_Byte;
    else if( H5Tequal( H5T_NATIVE_UCHAR,  TypeID ) ) 
	return GDT_Byte;
    else if( H5Tequal( H5T_NATIVE_SHORT,  TypeID ) )
	return GDT_Int16;
    else if( H5Tequal( H5T_NATIVE_USHORT, TypeID ) ) 
	return GDT_UInt16;
    else if( H5Tequal( H5T_NATIVE_INT,    TypeID ) ) 
	return GDT_Int32;      
    else if( H5Tequal( H5T_NATIVE_UINT,   TypeID ) ) 
	return GDT_UInt32;
    else if( H5Tequal( H5T_NATIVE_LONG,   TypeID ) ) 
    {
        if( sizeof(long) == 4 )
            return GDT_Int32;      
        else
            return GDT_Unknown;
    }
    else if( H5Tequal( H5T_NATIVE_ULONG,  TypeID ) ) 
    {
        if( sizeof(unsigned long) == 4 )
            return GDT_UInt32;      
        else
            return GDT_Unknown;
    }
    else if( H5Tequal( H5T_NATIVE_FLOAT,  TypeID ) ) 
	return GDT_Float32;
    else if( H5Tequal( H5T_NATIVE_DOUBLE, TypeID ) ) 
	return GDT_Float64;
    else if( H5Tequal( H5T_NATIVE_LLONG,  TypeID ) ) 
	return GDT_Unknown;
    else if( H5Tequal( H5T_NATIVE_ULLONG, TypeID ) ) 
	return GDT_Unknown;
    else if( H5Tequal( H5T_NATIVE_DOUBLE, TypeID ) ) 
	return GDT_Unknown;

    return GDT_Unknown;
}

/************************************************************************/
/*                          GetDataTypeName()                           */
/*                                                                      */
/*      Return the human readable name of data type                     */
/************************************************************************/
const char *HDF5Dataset::GetDataTypeName(hid_t TypeID)
{
    if( H5Tequal( H5T_NATIVE_CHAR,        TypeID ) )
	return "8-bit character";
    else if( H5Tequal( H5T_NATIVE_UCHAR,  TypeID ) ) 
	return "8-bit unsigned character";    
    else if( H5Tequal( H5T_NATIVE_SHORT,  TypeID ) )
	return "16-bit integer";
    else if( H5Tequal( H5T_NATIVE_USHORT, TypeID ) ) 
	return "16-bit unsigned integer";
    else if( H5Tequal( H5T_NATIVE_INT,    TypeID ) ) 
	return "32-bit integer";
    else if( H5Tequal( H5T_NATIVE_UINT,   TypeID ) ) 
	return "32-bit unsigned integer";
    else if( H5Tequal( H5T_NATIVE_LONG,   TypeID ) ) 
	return "32/64-bit integer";
    else if( H5Tequal( H5T_NATIVE_ULONG,  TypeID ) ) 
	return "32/64-bit unsigned integer";
    else if( H5Tequal( H5T_NATIVE_FLOAT,  TypeID ) ) 
	return "32-bit floating-point";
    else if( H5Tequal( H5T_NATIVE_DOUBLE, TypeID ) ) 
	return "64-bit floating-point";
    else if( H5Tequal( H5T_NATIVE_LLONG,  TypeID ) ) 
	return "64-bit integer";
    else if( H5Tequal( H5T_NATIVE_ULLONG, TypeID ) ) 
	return "64-bit unsigned integer";
    else if( H5Tequal( H5T_NATIVE_DOUBLE, TypeID ) ) 
        return "64-bit floating-point";
    
    return "Unknown";
}

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

int HDF5Dataset::Identify( GDALOpenInfo * poOpenInfo )

{
/* -------------------------------------------------------------------- */
/*      Is it an HDF5 file?                                             */
/* -------------------------------------------------------------------- */
    static const char achSignature[] = "\211HDF\r\n\032\n";

    if( poOpenInfo->pabyHeader == NULL
        || memcmp(poOpenInfo->pabyHeader,achSignature,8) != 0 )
        return FALSE;

    return TRUE;
}

/************************************************************************/
/*                                Open()                                */
/************************************************************************/
GDALDataset *HDF5Dataset::Open( GDALOpenInfo * poOpenInfo )
{
    HDF5Dataset *poDS;
    CPLErr      Err;
    
    if( !Identify( poOpenInfo ) )
        return NULL;

/* -------------------------------------------------------------------- */
/*      Create datasource.                                              */
/* -------------------------------------------------------------------- */
    poDS = new HDF5Dataset();

    poDS->SetDescription( poOpenInfo->pszFilename );

/* -------------------------------------------------------------------- */
/*      Try opening the dataset.                                        */
/* -------------------------------------------------------------------- */
    poDS->hHDF5 = H5Fopen( poOpenInfo->pszFilename, 
			   H5F_ACC_RDONLY, 
			   H5P_DEFAULT );
    if( poDS->hHDF5 < 0 )  {
        delete poDS;
   	return NULL;
    }
    
    poDS->hGroupID = H5Gopen( poDS->hHDF5, "/" ); 
    if( poDS->hGroupID < 0 ){
	poDS->bIsHDFEOS=false;
        delete poDS;
	return NULL;
    }
    
    poDS->bIsHDFEOS=true;
    Err = poDS->ReadGlobalAttributes( true );

    poDS->SetMetadata( poDS->papszMetadata  );
    
    if ( CSLCount( poDS->papszSubDatasets ) / 2 >= 1 )
        poDS->SetMetadata( poDS->papszSubDatasets, "SUBDATASETS" );

    // Make sure we don't try to do any pam stuff with this dataset.
    poDS->nPamFlags |= GPF_NOSAVE;

/* -------------------------------------------------------------------- */
/*      If we have single subdataset only, open it immediately          */
/* -------------------------------------------------------------------- */
    int nSubDatasets = CSLCount( poDS->papszSubDatasets ) / 2;
    if( nSubDatasets == 1 )
    {
        CPLString osDSName = CSLFetchNameValue( poDS->papszSubDatasets,
                                                "SUBDATASET_1_NAME" );
        delete poDS;
        return (GDALDataset *) GDALOpen( osDSName, poOpenInfo->eAccess );
    }
    else
    {
/* -------------------------------------------------------------------- */
/*      Confirm the requested access is supported.                      */
/* -------------------------------------------------------------------- */
        if( poOpenInfo->eAccess == GA_Update )
        {
            delete poDS;
            CPLError( CE_Failure, CPLE_NotSupported, 
                      "The HDF5 driver does not support update access to existing"
                      " datasets.\n" );
            return NULL;
        }
    }
    return( poDS );
}

/************************************************************************/
/*                          DestroyH5Objects()                          */
/*                                                                      */
/*      Erase all objects                                               */
/************************************************************************/
void HDF5Dataset::DestroyH5Objects( HDF5GroupObjects *poH5Object )
{
    int i;


/* -------------------------------------------------------------------- */
/*      Visit all objects                                               */
/* -------------------------------------------------------------------- */

    for( i=0; i < poH5Object->nbObjs; i++ )
	if( poH5Object->poHchild+i != NULL )
	    DestroyH5Objects( poH5Object->poHchild+i );

    if( poH5Object->poHparent ==NULL )
	return;

/* -------------------------------------------------------------------- */
/*      Erase some data                                                 */
/* -------------------------------------------------------------------- */
    CPLFree( poH5Object->paDims );
    poH5Object->paDims = NULL; 

    CPLFree( poH5Object->pszPath );
    poH5Object->pszPath = NULL;

    CPLFree( poH5Object->pszName );
    poH5Object->pszName = NULL;

    CPLFree( poH5Object->pszUnderscorePath );
    poH5Object->pszUnderscorePath = NULL;
/* -------------------------------------------------------------------- */
/*      All Children are visited and can be deleted.                    */
/* -------------------------------------------------------------------- */
    if( ( i==poH5Object->nbObjs ) && ( poH5Object->nbObjs!=0 ) ) {
	CPLFree( poH5Object->poHchild );
        poH5Object->poHchild = NULL;
    }

}

/************************************************************************/
/*                             CreatePath()                             */
/*                                                                      */
/*      Find Dataset path for HDopen                                    */
/************************************************************************/
char* CreatePath( HDF5GroupObjects *poH5Object )
{
    char pszPath[8192];
    char pszUnderscoreSpaceInName[8192];
    char *popszPath;
    int  i;
    char **papszPath;

/* -------------------------------------------------------------------- */
/*      Recurse to the root path                                        */
/* -------------------------------------------------------------------- */
    pszPath[0]='\0';
    if( poH5Object->poHparent !=NULL ) {
	popszPath=CreatePath( poH5Object->poHparent );
	strcpy( pszPath,popszPath );
    }
	
/* -------------------------------------------------------------------- */
/*      add name to the path                                            */
/* -------------------------------------------------------------------- */
    if( !EQUAL( poH5Object->pszName,"/" ) ){
	strcat( pszPath,"/" );
	strcat( pszPath,poH5Object->pszName );
    }

/* -------------------------------------------------------------------- */
/*      fill up path for each object                                    */
/* -------------------------------------------------------------------- */
    if( poH5Object->pszPath == NULL ) {

	if( strlen( poH5Object->pszName ) == 1 ) {
	    strcat(pszPath, poH5Object->pszName );
	    strcpy(pszUnderscoreSpaceInName, poH5Object->pszName);
	}
	else {
/* -------------------------------------------------------------------- */
/*      Change space for underscore                                     */
/* -------------------------------------------------------------------- */
	    papszPath = CSLTokenizeString2( pszPath,
					    " ", CSLT_HONOURSTRINGS );
	    
	    strcpy(pszUnderscoreSpaceInName,papszPath[0]);
	    for( i=1; i < CSLCount( papszPath ); i++ ) {
		strcat( pszUnderscoreSpaceInName, "_" );
		strcat( pszUnderscoreSpaceInName, papszPath[ i ] );
	    }
	    CSLDestroy(papszPath);

	}
	poH5Object->pszUnderscorePath  = 
	    CPLStrdup( pszUnderscoreSpaceInName );
	poH5Object->pszPath  = CPLStrdup( pszPath );
    }

    return( poH5Object->pszPath );
}

/************************************************************************/
/*                      HDF5GroupCheckDuplicate()                       */
/*                                                                      */
/*      Returns TRUE if an ancestor has the same objno[] as passed      */
/*      in - used to avoid looping in files with "links up" #(3218).    */
/************************************************************************/

static int HDF5GroupCheckDuplicate( HDF5GroupObjects *poHparent,
                                    unsigned long *objno )

{
    while( poHparent != NULL )
    {
        if( poHparent->objno[0] == objno[0]
            && poHparent->objno[1] == objno[1] )
            return TRUE;

        poHparent = poHparent->poHparent;
    }

    return FALSE;
}

/************************************************************************/
/*                      HDF5CreateGroupObjs()                           */
/*                                                                      */
/*      Create HDF5 hierarchy into a linked list                        */
/************************************************************************/
herr_t HDF5CreateGroupObjs(hid_t hHDF5, const char *pszObjName, 
			   void *poHObjParent)
{
    herr_t	ret;			/* error return status */
    hid_t	hGroupID;		/* identifier of group */
    hid_t	hDatasetID;		/* identifier of dataset */
    hsize_t     nbObjs=0;		/* number of objects in a group */
    int         nbAttrs=0;		/* number of attributes in object */
    int		idx;
    int         n_dims;
    H5G_stat_t  oStatbuf;
    hsize_t     *dims=NULL;
    hsize_t     *maxdims=NULL;
    hid_t       datatype;
    hid_t       dataspace;
    hid_t       native;
    herr_t status;

    char *CreatePath( HDF5GroupObjects *poH5Object );

    HDF5GroupObjects *poHchild;
    HDF5GroupObjects *poHparent;

    poHparent = ( HDF5GroupObjects * ) poHObjParent;
    poHchild=poHparent->poHchild;

    if( H5Gget_objinfo( hHDF5, pszObjName, FALSE, &oStatbuf ) < 0  )
	return -1;

    
/* -------------------------------------------------------------------- */
/*      Look for next child                                             */
/* -------------------------------------------------------------------- */
    for( idx=0; idx < poHparent->nbObjs; idx++ )	{
	if( poHchild->pszName == NULL ) break;
	poHchild++;
    }

    if( idx == poHparent->nbObjs ) 
	return -1;  // all children parsed
    
/* -------------------------------------------------------------------- */
/*      Save child information                                          */
/* -------------------------------------------------------------------- */
    poHchild->pszName  = CPLStrdup( pszObjName );
    
    poHchild->nType  = oStatbuf.type;
    poHchild->nIndex = idx;
    poHchild->poHparent = poHparent;
    poHchild->nRank     = 0;
    poHchild->paDims    = 0;
    poHchild->HDatatype = 0;
    poHchild->objno[0]  = oStatbuf.objno[0];
    poHchild->objno[1]  = oStatbuf.objno[1];
    if( poHchild->pszPath == NULL ) {
	poHchild->pszPath  = CreatePath( poHchild );
    }
    if( poHparent->pszPath == NULL ) {
	poHparent->pszPath = CreatePath( poHparent );
    }


    switch ( oStatbuf.type ) 
	{
	case H5G_LINK:
	    poHchild->nbAttrs = 0;
	    poHchild->nbObjs = 0;
	    poHchild->poHchild = NULL;
	    poHchild->nRank      = 0;
	    poHchild->paDims    = 0;
	    poHchild->HDatatype = 0;
	    break;
	    
	case H5G_GROUP:
	    if( ( hGroupID = H5Gopen( hHDF5, pszObjName ) ) == -1  ) {
		printf( "Error: unable to access \"%s\" group.\n", 
			pszObjName );
		return -1;
	    }
	    nbAttrs          = H5Aget_num_attrs( hGroupID );
	    ret              = H5Gget_num_objs( hGroupID, &nbObjs );
	    poHchild->nbAttrs= nbAttrs;
	    poHchild->nbObjs = nbObjs;
	    poHchild->nRank      = 0;
	    poHchild->paDims    = 0;
	    poHchild->HDatatype = 0;
	    
	    if( nbObjs > 0 ) {
		poHchild->poHchild =( HDF5GroupObjects * )
		    CPLCalloc( nbObjs, sizeof( HDF5GroupObjects ) );
		memset( poHchild->poHchild,0,
			sizeof( HDF5GroupObjects ) * nbObjs );
	    }
	    else 
		poHchild->poHchild = NULL;

            if( !HDF5GroupCheckDuplicate( poHparent, oStatbuf.objno ) )
                H5Giterate( hHDF5, pszObjName, NULL, 
                            HDF5CreateGroupObjs,  (void*) poHchild );
            else
                CPLDebug( "HDF5", "avoiding link looping on node '%s'.", 
                          pszObjName );
	    H5Gclose( hGroupID );
	    break;
	    
	case H5G_DATASET:
	    
	    if( ( hDatasetID = H5Dopen( hHDF5, pszObjName ) ) == -1  ) {
		printf( "Error: unable to access \"%s\" dataset.\n", 
		       pszObjName );
		return -1;
	    }
	    nbAttrs      = H5Aget_num_attrs( hDatasetID );
	    datatype     = H5Dget_type( hDatasetID );
	    dataspace    = H5Dget_space( hDatasetID );
	    n_dims       = H5Sget_simple_extent_ndims( dataspace );
	    native       = H5Tget_native_type( datatype, H5T_DIR_ASCEND );
	    
	    if( n_dims > 0 ) {
		dims     = (hsize_t *) CPLCalloc( n_dims,sizeof( hsize_t ) );
		maxdims  = (hsize_t *) CPLCalloc( n_dims,sizeof( hsize_t ) );
	    }
	    status     = H5Sget_simple_extent_dims( dataspace, dims, maxdims );
	    if( maxdims != NULL )
		CPLFree( maxdims );
	    
	    if( n_dims > 0 ) {
		poHchild->nRank     = n_dims;   // rank of the array
		poHchild->paDims    = dims;      // dimmension of the array.
		poHchild->HDatatype = datatype;  // HDF5 datatype
	    }
	    else  {
		poHchild->nRank     = -1;
		poHchild->paDims    = NULL;
		poHchild->HDatatype = 0;
	}
	    poHchild->nbAttrs   = nbAttrs;
	    poHchild->nbObjs    = 0;
	    poHchild->poHchild  = NULL;
	    poHchild->native    = native;
	    ret                 = H5Dclose( hDatasetID );
	    break;
	    
	case H5G_TYPE:
	    poHchild->nbAttrs = 0;
	    poHchild->nbObjs = 0;
	    poHchild->poHchild = NULL;
	    poHchild->nRank      = 0;
	    poHchild->paDims    = 0;
	    poHchild->HDatatype = 0;
	    break;
	    
	default:
	    break;
	}
    
    return 0;
}


/************************************************************************/
/*                          HDF5AttrIterate()                           */
/************************************************************************/

herr_t HDF5AttrIterate( hid_t hH5ObjID, 
			const char *AttrName, 
			void *pDS )
{
    hid_t           hAttrID;
    hid_t           hAttrTypeID;
    hid_t           hAttrNativeType;
    hid_t           hAttrSpace;

    char           *szData = NULL;
    hsize_t        nSize[64];
    unsigned int            nAttrElmts;
    hsize_t        nAttrSize;
    hsize_t        i;
    void           *buf = NULL;
    unsigned int             nAttrDims;


    HDF5Dataset    *poDS;
    char           *szTemp = (char*) CPLMalloc( 8192 );
    char           *szValue = NULL;

    poDS = (HDF5Dataset *) pDS;
    sprintf( szTemp, "%s:%s", poDS->poH5CurrentObject->pszName, 
	     AttrName );

    hAttrID          = H5Aopen_name( hH5ObjID, AttrName );
    hAttrTypeID      = H5Aget_type( hAttrID );
    hAttrNativeType  = H5Tget_native_type( hAttrTypeID, H5T_DIR_DEFAULT );
    hAttrSpace       = H5Aget_space( hAttrID );
    nAttrDims        = H5Sget_simple_extent_dims( hAttrSpace, nSize, NULL );


    if( H5Tget_class( hAttrNativeType ) == H5T_STRING ) {
	nAttrSize = H5Tget_size( hAttrTypeID );
	szData = (char*) CPLMalloc(nAttrSize+1);
	szValue = (char*) CPLMalloc(nAttrSize+1);
	H5Aread( hAttrID, hAttrNativeType, szData  );
	szData[nAttrSize]='\0';
	sprintf( szValue, "%s", szData );

    }
    else {
	nAttrElmts = 1;
	for( i=0; i < nAttrDims; i++ ) {
	    nAttrElmts *= nSize[i];
	}
	if( nAttrElmts > 0 ){
	    buf = (void *) CPLMalloc( nAttrElmts*
				      H5Tget_size( hAttrNativeType ));
	    szData = (char*) CPLMalloc( 8192 );
	    szValue = (char*) CPLMalloc( MAX_METADATA_LEN );
	    szData[0]='\0';
	    szValue[0] ='\0';
	    H5Aread( hAttrID, hAttrNativeType, buf );
	}
	if( H5Tequal( H5T_NATIVE_CHAR, hAttrNativeType ) ){
	    for( i=0; i < nAttrElmts; i++ ) {
		sprintf( szData, "%c ", ((char *) buf)[i]);
		if( CPLStrlcat(szValue,szData,MAX_METADATA_LEN) >= MAX_METADATA_LEN )
		    CPLError( CE_Warning, CPLE_OutOfMemory,
			      "Header data too long. Truncated\n");
	    }
	}
	else if( H5Tequal( H5T_NATIVE_UCHAR,  hAttrNativeType ) ) {
	    for( i=0; i < nAttrElmts; i++ ) {
		sprintf( szData, "%c", ((char *) buf)[i] );
		if( CPLStrlcat(szValue,szData,MAX_METADATA_LEN) >= MAX_METADATA_LEN )
		    CPLError( CE_Warning, CPLE_OutOfMemory,
			      "Header data too long. Truncated\n");
	    }
	}
	else if( H5Tequal( H5T_NATIVE_SHORT,  hAttrNativeType ) ) {
	    for( i=0; i < nAttrElmts; i++ ) {
		sprintf( szData, "%d ", ((short *) buf)[i] );
		if( CPLStrlcat(szValue,szData,MAX_METADATA_LEN) >= MAX_METADATA_LEN )
		    CPLError( CE_Warning, CPLE_OutOfMemory,
			      "Header data too long. Truncated\n");
	    }
	}
	else if( H5Tequal( H5T_NATIVE_USHORT, hAttrNativeType ) ) {
	    for( i=0; i < nAttrElmts; i++ ) {
		sprintf( szData, "%ud ", ((unsigned short *) buf)[i] );
		if( CPLStrlcat(szValue,szData,MAX_METADATA_LEN) >= MAX_METADATA_LEN )
		    CPLError( CE_Warning, CPLE_OutOfMemory,
			      "Header data too long. Truncated\n");
	    }
	}
	else if( H5Tequal( H5T_NATIVE_INT,    hAttrNativeType ) ) {
	    for( i=0; i < nAttrElmts; i++ ) {
		sprintf( szData, "%d ", ((int *) buf)[i] );
		if( CPLStrlcat(szValue,szData,MAX_METADATA_LEN) >= MAX_METADATA_LEN )
		    CPLError( CE_Warning, CPLE_OutOfMemory,
			      "Header data too long. Truncated\n");
	    }
	}
	else if( H5Tequal( H5T_NATIVE_UINT,   hAttrNativeType ) ) {
	    for( i=0; i < nAttrElmts; i++ ) {
		sprintf( szData, "%ud ", ((unsigned int *) buf)[i] );
		if( CPLStrlcat(szValue,szData,MAX_METADATA_LEN) >= MAX_METADATA_LEN )
		    CPLError( CE_Warning, CPLE_OutOfMemory,
			      "Header data too long. Truncated\n");
	    }
	}
	else if( H5Tequal( H5T_NATIVE_LONG,   hAttrNativeType ) ) {
	    for( i=0; i < nAttrElmts; i++ ) {
		sprintf( szData, "%ld ", ((long *)buf)[i] );
		if( CPLStrlcat(szValue,szData,MAX_METADATA_LEN) >= MAX_METADATA_LEN )
		    CPLError( CE_Warning, CPLE_OutOfMemory,
			      "Header data too long. Truncated\n");
	    }
	}
	else if( H5Tequal( H5T_NATIVE_ULONG,  hAttrNativeType ) ) {
	    for( i=0; i < nAttrElmts; i++ ) {
		sprintf( szData, "%ld ", ((unsigned long *)buf)[i] );
		if( CPLStrlcat(szValue,szData,MAX_METADATA_LEN) >= MAX_METADATA_LEN )
		    CPLError( CE_Warning, CPLE_OutOfMemory,
			      "Header data too long. Truncated\n");
	    }
	}
	else if( H5Tequal( H5T_NATIVE_FLOAT,  hAttrNativeType ) ) {
	    for( i=0; i < nAttrElmts; i++ ) {
		sprintf( szData, "%f ",  ((float *)buf)[i] );
		if( CPLStrlcat(szValue,szData,MAX_METADATA_LEN) >= MAX_METADATA_LEN )
		    CPLError( CE_Warning, CPLE_OutOfMemory,
			      "Header data too long. Truncated\n");
	    }
	}
	else if( H5Tequal( H5T_NATIVE_DOUBLE, hAttrNativeType ) ) {
	    for( i=0; i < nAttrElmts; i++ ) {
		sprintf( szData, "%g ",  ((double *)buf)[i] );
		if( CPLStrlcat(szValue,szData,MAX_METADATA_LEN) >= MAX_METADATA_LEN )
		    CPLError( CE_Warning, CPLE_OutOfMemory,
			      "Header data too long. Truncated\n");
	    }
	}
	CPLFree( buf );

    }
    H5Aclose( hAttrID );
    //printf( "%s = %s\n",szTemp, szValue );
    poDS->papszMetadata =
	CSLSetNameValue( poDS->papszMetadata, szTemp,  
			 CPLSPrintf( "%s", szValue ) );
    CPLFree( szTemp );
    CPLFree( szData );
    CPLFree( szValue );

    return 0;
}

/************************************************************************/
/*                           CreateMetadata()                           */
/************************************************************************/
CPLErr HDF5Dataset::CreateMetadata( HDF5GroupObjects *poH5Object, int nType)
{
    hid_t	hGroupID;		/* identifier of group */
    hid_t       hDatasetID;
    int         nbAttrs;
    herr_t      ret;

    HDF5Dataset *poDS;

    poDS = this;

    poH5CurrentObject = poH5Object;
    nbAttrs = poH5Object->nbAttrs;

    if( poH5Object->pszPath == NULL || EQUAL(poH5Object->pszPath, "" ) )
	return CE_None;

    switch( nType ) {

    case H5G_GROUP:

	hGroupID = H5Gopen( hHDF5, poH5Object->pszPath );
	if( nbAttrs > 0 ) { 
	    ret = H5Aiterate( hGroupID, NULL, 
			      HDF5AttrIterate, (void *)poDS  );
	    ret = H5Gclose( hGroupID );
	}

	break;

    case H5G_DATASET:

	hDatasetID =  H5Dopen(hHDF5, poH5Object->pszPath );

	if( nbAttrs > 0 ) { 
	    ret = H5Aiterate( hDatasetID, NULL, 
			      HDF5AttrIterate, (void *)poDS );
	    ret = H5Dclose( hDatasetID );
	}
	break;

    default:
	break;
    }

    return CE_None;
}


/************************************************************************/
/*                       HDF5FindDatasetObjectsbyPath()                 */
/*      Find object by name                                             */
/************************************************************************/
HDF5GroupObjects* HDF5Dataset::HDF5FindDatasetObjectsbyPath
    ( HDF5GroupObjects *poH5Objects, char* pszDatasetPath )
{
    int i;
    HDF5Dataset *poDS;
    HDF5GroupObjects *poObjectsFound;
    poDS=this;
    
    if( poH5Objects->nType == H5G_DATASET &&
       EQUAL( poH5Objects->pszUnderscorePath,pszDatasetPath ) )	{
	    /*      printf("found it! %ld\n",(long) poH5Objects);*/
	    return( poH5Objects );
	}
    
    if( poH5Objects->nbObjs >0 )
	for( i=0; i <poH5Objects->nbObjs; i++ )   {
	    poObjectsFound=
		poDS->HDF5FindDatasetObjectsbyPath( poH5Objects->poHchild+i, 
						    pszDatasetPath );
/* -------------------------------------------------------------------- */
/*      Is this our dataset??                                           */
/* -------------------------------------------------------------------- */
	    if( poObjectsFound != NULL ) return( poObjectsFound );
	}
/* -------------------------------------------------------------------- */
/*      Dataset has not been found!                                     */
/* -------------------------------------------------------------------- */
    return( NULL );
    
}


/************************************************************************/
/*                       HDF5FindDatasetObjects()                       */
/*      Find object by name                                             */
/************************************************************************/
HDF5GroupObjects* HDF5Dataset::HDF5FindDatasetObjects
    ( HDF5GroupObjects *poH5Objects, char* pszDatasetName )
{
    int i;
    HDF5Dataset *poDS;
    HDF5GroupObjects *poObjectsFound;
    poDS=this;
    
    if( poH5Objects->nType == H5G_DATASET &&
       EQUAL( poH5Objects->pszName,pszDatasetName ) )	{
	    /*      printf("found it! %ld\n",(long) poH5Objects);*/
	    return( poH5Objects );
	}
    
    if( poH5Objects->nbObjs >0 )
	for( i=0; i <poH5Objects->nbObjs; i++ )   {
	    poObjectsFound=
		poDS->HDF5FindDatasetObjects( poH5Objects->poHchild+i, 
					      pszDatasetName );
/* -------------------------------------------------------------------- */
/*      Is this our dataset??                                           */
/* -------------------------------------------------------------------- */
	    if( poObjectsFound != NULL ) return( poObjectsFound );
	    
	}
/* -------------------------------------------------------------------- */
/*      Dataset has not been found!                                     */
/* -------------------------------------------------------------------- */
    return( NULL );
    
}


/************************************************************************/
/*                        HDF5ListGroupObjects()                        */
/*                                                                      */
/*      List all objects in HDF5                                        */
/************************************************************************/
CPLErr HDF5Dataset::HDF5ListGroupObjects( HDF5GroupObjects *poRootGroup,
					  int bSUBDATASET )
{
    int i;
    char szTemp[8192];
    char szDim[8192];
    HDF5Dataset *poDS;
    poDS=this;
    
    if( poRootGroup->nbObjs >0 )  
	for( i=0; i < poRootGroup->nbObjs; i++ ) {
	    poDS->HDF5ListGroupObjects( poRootGroup->poHchild+i, bSUBDATASET );
	}
    

    if( poRootGroup->nType == H5G_GROUP ) {
	CreateMetadata( poRootGroup, H5G_GROUP );
    }

/* -------------------------------------------------------------------- */
/*      Create Sub dataset list                                         */
/* -------------------------------------------------------------------- */
    if( (poRootGroup->nType == H5G_DATASET ) && bSUBDATASET 
        && poDS->GetDataType( poRootGroup->native ) == GDT_Unknown )
    {
        CPLDebug( "HDF5", "Skipping unsupported %s of type %s", 
                  poRootGroup->pszUnderscorePath, 
                  poDS->GetDataTypeName( poRootGroup->native ) );
    }
    else if( (poRootGroup->nType == H5G_DATASET ) && bSUBDATASET ) 
    {
	szDim[0]='\0';
	switch( poRootGroup->nRank ) {
	case 3: 
	    sprintf( szTemp,"%dx%dx%d",
		    (int)poRootGroup->paDims[0],
		    (int)poRootGroup->paDims[1],
		    (int)poRootGroup->paDims[2] );
	    break;
	    
  	case 2: 
	    sprintf( szTemp,"%dx%d",
		    (int)poRootGroup->paDims[0],
		    (int)poRootGroup->paDims[1] );
	  break;
        default:
	    return CE_None;
	    
	}
	strcat( szDim,szTemp );
	
	sprintf( szTemp, "SUBDATASET_%d_NAME", ++(poDS->nSubDataCount) );

	poDS->papszSubDatasets =
	    CSLSetNameValue( poDS->papszSubDatasets, szTemp,
			    CPLSPrintf( "HDF5:\"%s\":%s",
					poDS->GetDescription(),
					poRootGroup->pszUnderscorePath ) );
	
	sprintf(  szTemp, "SUBDATASET_%d_DESC", poDS->nSubDataCount );
	
	poDS->papszSubDatasets =
	    CSLSetNameValue( poDS->papszSubDatasets, szTemp,
			    CPLSPrintf( "[%s] %s (%s)", 
					szDim,
					poRootGroup->pszUnderscorePath,
					poDS->GetDataTypeName
					( poRootGroup->native ) ) );

    }
    
    return CE_None;
}


/************************************************************************/
/*                       ReadGlobalAttributes()                         */
/************************************************************************/
CPLErr HDF5Dataset::ReadGlobalAttributes(int bSUBDATASET)
{
    
    HDF5GroupObjects *poRootGroup;
    
    poRootGroup = (HDF5GroupObjects*) CPLCalloc(sizeof(HDF5GroupObjects), 1);
    
    poH5RootGroup=poRootGroup;
    poRootGroup->pszName   = CPLStrdup( "/" );
    poRootGroup->nType     = H5G_GROUP;
    poRootGroup->poHparent = NULL;
    poRootGroup->pszPath = NULL;
    poRootGroup->pszUnderscorePath = NULL;

    if( hHDF5 < 0 )  {
	printf( "hHDF5 <0!!\n" );
	return CE_None;
    }
    
    H5G_stat_t  oStatbuf;
    if( H5Gget_objinfo( hHDF5, "/", FALSE, &oStatbuf ) < 0  )
	return CE_Failure;
    poRootGroup->objno[0] = oStatbuf.objno[0];
    poRootGroup->objno[1] = oStatbuf.objno[1];
    
    hGroupID = H5Gopen( hHDF5, "/" ); 
    if( hGroupID < 0 ){
	printf( "hGroupID <0!!\n" );
	return CE_None;
    }
    
    poRootGroup->nbAttrs = H5Aget_num_attrs( hGroupID );

    H5Gget_num_objs( hGroupID, (hsize_t *) &( poRootGroup->nbObjs ) );
    
    if( poRootGroup->nbObjs > 0 ) {
	poRootGroup->poHchild = ( HDF5GroupObjects * ) 
	    CPLCalloc( poRootGroup->nbObjs,
		sizeof( HDF5GroupObjects ) );
	H5Giterate( hGroupID, "/", NULL, 
		   HDF5CreateGroupObjs, (void *)poRootGroup );
    }
    else poRootGroup->poHchild = NULL;
    
    HDF5ListGroupObjects( poRootGroup, bSUBDATASET );
    return CE_None;
}
