/*----------------------------------------------------------------------------*/
/*!
Copyright (c) 2018, 2019 by Christian Nowak a.k.a. Creestee a.k.a. chn
Contact: chnowak@web.de or creestee@creestee.com
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. This application may be used and redistributed in source code and binary
form free of charge for personal and for educational purposes.
4. Redistribution and usage of this application in source code or binary form
for commercial purposes requires explicit written permission from the
author.


THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

The views and conclusions contained in the software and documentation are those
of the authors and should not be interpreted as representing official policies,
either expressed or implied, of the RadarVolumeExplorer project.
*/
/*----------------------------------------------------------------------------*/

#include <hdf5.h>

#include "RveHDF5Object.h"
#include "RveHDF5Group.h"
#include "RveHDF5Attribute.h"
#include "RveHDF5Dataset.h"
#include "RveHDF5Compound.h"

#include <stdlib.h>
#include <iostream>

/*----------------------------------------------------------------------------*/
/*!
   Used for iterating over all attributes of a group by means of the
   H5Aiterate function
   \param loc_id identifying the hdf5 handle of the current attribute
   \param name the name of the current group
   \param opdata pointer to the current group
*/
/*----------------------------------------------------------------------------*/
static herr_t attr_info_group( hid_t loc_id, const char *name, void *opdata )
{
   RveHDF5Object *object = (RveHDF5Object *)opdata;
   RveHDF5Attribute *attribute;
   void *attrdata;
   hid_t attr;
   hid_t type;
   H5T_class_t cl;
   ssize_t sz;
   RveHDF5Attribute::ATTR_TYPE atype;
   bool isVariable = false;

   // Open the attribute and determine its type, size and class
   attr = H5Aopen_name( loc_id, name );
   hsize_t attrsize = H5Aget_storage_size( attr );
   type = H5Aget_type( attr );
   isVariable = H5Tis_variable_str( type ) == 1;
   sz = H5Tget_size( type );
   cl = H5Tget_class( type );

   // Handle different data types
   switch( cl )
   {
      case H5T_NO_CLASS:
         atype = RveHDF5Attribute::ATTR_NONE;
         break;
      case H5T_INTEGER:
         atype = RveHDF5Attribute::ATTR_INTEGER;
         break;
      case H5T_FLOAT:
         atype = RveHDF5Attribute::ATTR_FLOAT;
         break;
      case H5T_TIME:
         atype = RveHDF5Attribute::ATTR_TIME;
         break;
      case H5T_STRING:
         atype = RveHDF5Attribute::ATTR_STRING;
         break;
      case H5T_BITFIELD:
         atype = RveHDF5Attribute::ATTR_BITFIELD;
         break;
      case H5T_OPAQUE:
         atype = RveHDF5Attribute::ATTR_OPAQUE;
         break;
      case H5T_COMPOUND:
         atype = RveHDF5Attribute::ATTR_COMPOUND;
         break;
      case H5T_REFERENCE:
         atype = RveHDF5Attribute::ATTR_REFERENCE;
         break;
      case H5T_ENUM:
         atype = RveHDF5Attribute::ATTR_ENUM;
         break;
      case H5T_VLEN:
         atype = RveHDF5Attribute::ATTR_VLEN;
         break;
      case H5T_ARRAY:
         atype = RveHDF5Attribute::ATTR_ARRAY;
         break;
      default:
         atype = RveHDF5Attribute::ATTR_NONE;
         break;
   }

   // If we're dealing with a string, add space for the null byte
   if( atype == RveHDF5Attribute::ATTR_STRING )
      attrsize++;

   // Read the data. How this is done depends on whether the data
   // is stored with a variable or fixed attribute size.
   if( attrsize )
   {
      hid_t ntype = H5Tget_native_type( type, H5T_DIR_ASCEND );

      if( isVariable )
      {
         H5Aread( attr, ntype, &attrdata );
         if( atype == RveHDF5Attribute::ATTR_STRING )
            attrsize = strlen( (char*)attrdata ) + 1;
      } else
      {
         attrdata = calloc( attrsize, 1 );
         H5Aread( attr, ntype, attrdata );
         H5Tclose( ntype );
      }
   } else
   {
      attrdata = 0;
   }

   // Create and store the Attribute object
   attribute = new RveHDF5Attribute( QString( name ), atype, attrsize, sz, attrdata );
   object->add( attribute );

   // Free used resources
   if( attrdata )
      free( attrdata );
   H5Tclose( type );
   H5Aclose( attr );

   return( 0 );
}


/*----------------------------------------------------------------------------*/
/*!
   Used for iterating over all objects of a group by means of the
   H5Giterate function
   \param loc_id identifying the hdf5 handle of the current group
   \param name the name of the current group
   \param opdata pointer to the current group
*/
/*----------------------------------------------------------------------------*/
static herr_t file_info( hid_t loc_id, const char *name, void *opdata )
{
   RveHDF5Object *group = (RveHDF5Group *)opdata;
   H5G_stat_t stat;

   // Find out the object's type
   H5Gget_objinfo( loc_id, name, 0, &stat );

   switch( stat.type )
   {
      case H5G_LINK: // Cannot handle these with HDF5 v1.6?!
         break;

      case H5G_GROUP:
      {
         // Add a group object and iterate over all children
         hid_t grp = H5Gopen( loc_id, name );

         if( grp >= 0 )
         {
            RveHDF5Group *ngrp = new RveHDF5Group( QString( name ) );
            group->add( ngrp );

            // Iterate over the attributes
            H5Aiterate( grp, 0, attr_info_group, (void *)ngrp );
            // Iterate over the groups and datasets
            H5Giterate( loc_id, name, 0, file_info, (void *)ngrp );
            H5Gclose( grp );
         }
         break;
      }

      case H5G_DATASET:
      {
         // Create a new dataset object containing the data
         hid_t dset = H5Dopen( loc_id, name );

         if( dset >= 0 )
         {
            hid_t type = H5Dget_type( dset );
            if( H5Tget_class( type ) == H5T_COMPOUND )
            {
               RveHDF5Compound *compound = new RveHDF5Compound( QString( name ), dset );
               group->add( compound );
            } else
            {
               RveHDF5Dataset *dataset = new RveHDF5Dataset( QString( name ), dset );
               group->add( dataset );
               H5Aiterate( dset, 0, attr_info_group, (void *)dataset );
            }

            H5Tclose( type );
            H5Dclose( dset );
         }
         break;
      }

      case H5G_TYPE:
         break;

      default:
         break;
   }

   return( 0 );
}


/*----------------------------------------------------------------------------*/
/*!
   Standard constructor and destructor
*/
/*----------------------------------------------------------------------------*/
RveHDF5Object::RveHDF5Object()
{
}

RveHDF5Object::~RveHDF5Object()
{
}

RveHDF5Object::RveHDF5Object( QString name, RveHDF5Object *parent ) :
   m_Name( name ), m_pParent( parent )
{
}


/*----------------------------------------------------------------------------*/
/*!
   Retrieves the name of the object
*/
/*----------------------------------------------------------------------------*/
QString RveHDF5Object::name()
{
   return( m_Name );
}


/*----------------------------------------------------------------------------*/
/*!
   Retrieves the complete path within the graph structure to the object
*/
/*----------------------------------------------------------------------------*/
QString RveHDF5Object::path()
{
   QString path = "";

   if( m_Name != "" )
      path = "/" + m_Name;

   // Build up the path recursively
   if( m_pParent )
      path = m_pParent->path() + path;

   return( path );
}


/*----------------------------------------------------------------------------*/
/*!
   Set the parent object of the current object
   \param parent the parent
*/
/*----------------------------------------------------------------------------*/
void RveHDF5Object::setParent( RveHDF5Object *parent )
{
   m_pParent = parent;
}


/*----------------------------------------------------------------------------*/
/*!
   Load an HDF5 file from disk.
   \param fname the path to the hdf5 file
   \param obj A pointer to the pointer of the root object which is created
   \return An Error value which evaluates to "true" on success
*/
/*----------------------------------------------------------------------------*/
bool RveHDF5Object::load( QString fname, RveHDF5Object **obj )
{
   RveHDF5Group *grp = 0;

   // Open the HDF5 file
   hid_t h5f = H5Fopen( fname.toLatin1(), H5F_ACC_RDONLY, H5P_DEFAULT );

   // Couldn't open the file
   if( h5f < 0 )
   {
      return( false );
   }

   // Create the root object if it does not exist
   if( *obj )
   {
      if( (*obj)->type() == TYPE_GROUP )
      {
         grp = (RveHDF5Group *)*obj;
      } else
      {
         grp = new RveHDF5Group( "" );
      }
   } else
   {
      grp = new RveHDF5Group( "" );
   }

   // Load the file
   H5Giterate( h5f, "/", 0, file_info, (void *)grp );
   H5Fclose( h5f );
   *obj = grp;

   return( true );
}


/*----------------------------------------------------------------------------*/
/*!
   Attach the object to an object within an hdf5 file
   \param f hdf5 object to which the current object will be added
   \return true on success
*/
/*----------------------------------------------------------------------------*/
bool RveHDF5Object::save( hid_t f )
{
   QVector<RveHDF5Object *> objs = objects();

   for( int i = 0; i < objs.size(); i++ )
   {
      bool ok = objs[i]->save( f );
      if( !ok )
         return( ok );
   }

   return( false );
}


/*----------------------------------------------------------------------------*/
/*!
   Save the complete structure to an hdf5 file
   \param fname path to the hdf5 file to be created
   \return true on success
*/
/*----------------------------------------------------------------------------*/
bool RveHDF5Object::save( QString fname )
{
   // Create the file..
   hid_t f = H5Fcreate( fname.toLatin1(), H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT );

   if( f < 0 )
      return( false );

   //..save data..
   bool ok = save( f );

   //..and close it.
   H5Fclose( f );

   return( ok );
}


/*----------------------------------------------------------------------------*/
/*!
   Add an object to the current object as a child
   \param obj pointer to the object to be added
   \return true on success
*/
/*----------------------------------------------------------------------------*/
bool RveHDF5Object::add( RveHDF5Object *obj )
{
   if( !obj )
      return( false );

   // Dont add it if another object with the same name already exists
   if( m_Objects.find( obj->name() ) != m_Objects.end() )
      return( false );

   // Add it
   m_Objects.insert( obj->name(), obj );
   obj->setParent( this );

   return( true );
}


bool RveHDF5Object::remove( QString name )
{
   if( !m_Objects.contains( name ) )
   {
      return( false );
   }

   RveHDF5Object *o = m_Objects.value( name );
   m_Objects.remove( name );
   delete o;

   return( true );
}


/*----------------------------------------------------------------------------*/
/*!
   Retrieve direct child object denoted by its name
   \param name name of the child object
   \return pointer to the child object or NULL on failure
*/
/*----------------------------------------------------------------------------*/
RveHDF5Object *RveHDF5Object::get( QString name ) const
{
   QMap<QString, RveHDF5Object *>::const_iterator iter = m_Objects.find( name );

   if( iter == m_Objects.end() )
      return( 0 );

   return( iter.value() );
}

/*----------------------------------------------------------------------------*/
/*!
   Retrieve the object denoted by the first valid path from a list of paths
   \param paths list of paths to check for an object
   \return pointer to the first found object or NULL if none could be found
*/
/*----------------------------------------------------------------------------*/
RveHDF5Object *RveHDF5Object::getByPath( const QStringList &paths ) const
{
   for( int i = 0; i < paths.size(); i++ )
   {
      RveHDF5Object *obj = getByPath( paths[i] );
      if( obj )
         return( obj );
   }

   return( 0 );
}


/*----------------------------------------------------------------------------*/
/*!
   Retrieve the object denoted by its path
   \param path the path where the object shall be retrieved from
   \return pointer to the object or NULL if it could not be found
*/
/*----------------------------------------------------------------------------*/
RveHDF5Object *RveHDF5Object::getByPath( QString path ) const
{
   // Remove leading slash
   if( path[0] == '/' )
      path = path.mid( 1 );

   // No slashes left -> we have the direct object name
   int ps = path.indexOf( '/' );
   if( ps < 0 )
   {
      return( get( path ) );
   }

   // Retrieve the next object from the path and call getByPath recursively
   QString nextObject = path.mid( 0, ps );
   RveHDF5Object *obj = get( nextObject );
   if( !obj )
      return( 0 );

   return( obj->getByPath( path.mid( ps ) ) );
}


/*----------------------------------------------------------------------------*/
/*!
   Retrieves the contents of a string type attribute. The first valid path
   from a given list of paths will be used.
   \param paths a list of paths to check
   \param str reference to a string where the attribute contents will be stored
   \return true on success
*/
/*----------------------------------------------------------------------------*/
bool RveHDF5Object::getAttrString( const QStringList &paths, QString &str ) const
{
   RveHDF5Attribute *attr = (RveHDF5Attribute *)getByPath( paths );

   if( !attr )
      return( false );
   if( attr->type() != TYPE_ATTRIBUTE )
      return( false );
   if( !attr->getString( str ) )
      return( false );

   return( true );
}


/*----------------------------------------------------------------------------*/
/*!
   Retrieves the contents of a float type attribute. The first valid path
   from a given list of paths will be used.
   \param paths a list of paths to check
   \param flt reference to a float where the attribute contents will be stored
   \return true on success
*/
/*----------------------------------------------------------------------------*/
bool RveHDF5Object::getAttrFloat( const QStringList &paths, double &flt ) const
{
   RveHDF5Attribute *attr = (RveHDF5Attribute *)getByPath( paths );

   if( !attr )
      return( false );
   if( attr->type() != TYPE_ATTRIBUTE )
      return( false );
   if( !attr->getFloat( flt ) )
      return( false );

   return( true );
}


/*----------------------------------------------------------------------------*/
/*!
   Retrieves the contents of an integer type attribute. The first valid path
   from a given list of paths will be used.
   \param paths a list of paths to check
   \param i reference to an integer where the attribute contents will be stored
   \return true on success
*/
/*----------------------------------------------------------------------------*/
bool RveHDF5Object::getAttrInteger( const QStringList &paths, int &i ) const
{
   RveHDF5Attribute *attr = (RveHDF5Attribute *)getByPath( paths );

   if( !attr )
      return( false );
   if( attr->type() != TYPE_ATTRIBUTE )
      return( false );
   if( !attr->getInteger( i ) )
      return( false );

   return( true );
}


/*----------------------------------------------------------------------------*/
/*!
   Retrieves the contents of a string type attribute denoted by its path.
   \param path path of the string
   \param str reference to a string where the attribute contents will be stored
   \return true on success
*/
/*----------------------------------------------------------------------------*/
bool RveHDF5Object::getAttrString( QString path, QString &str ) const
{
   RveHDF5Attribute *attr = (RveHDF5Attribute *)getByPath( path );

   if( !attr )
      return( false );
   if( attr->type() != TYPE_ATTRIBUTE )
      return( false );
   if( !attr->getString( str ) )
      return( false );

   return( true );
}


/*----------------------------------------------------------------------------*/
/*!
   Retrieves the contents of a float type attribute denoted by its path.
   \param path path of the float
   \param str reference to a float where the attribute contents will be stored
   \return true on success
*/
/*----------------------------------------------------------------------------*/
bool RveHDF5Object::getAttrFloat( QString path, double &flt ) const
{
   RveHDF5Attribute *attr = (RveHDF5Attribute *)getByPath( path );

   if( !attr )
      return( false );
   if( attr->type() != TYPE_ATTRIBUTE )
      return( false );
   if( !attr->getFloat( flt ) )
      return( false );

   return( true );
}


/*----------------------------------------------------------------------------*/
/*!
   Retrieves the contents of an integer type attribute denoted by its path.
   \param path path of the integer
   \param str reference to an integer where the attribute contents will be stored
   \return true on success
*/
/*----------------------------------------------------------------------------*/
bool RveHDF5Object::getAttrInteger( QString path, int &i ) const
{
   RveHDF5Attribute *attr = (RveHDF5Attribute *)getByPath( path );

   if( !attr )
      return( false );
   if( attr->type() != TYPE_ATTRIBUTE )
      return( false );
   if( !attr->getInteger( i ) )
      return( false );

   return( true );
}


/*----------------------------------------------------------------------------*/
/*!
   Retrieves the names of the direct object children
   \return a vector of children names
*/
/*----------------------------------------------------------------------------*/
QStringList RveHDF5Object::names()
{
   QStringList n;
   QMap<QString, RveHDF5Object *>::iterator iter;

   for( iter = m_Objects.begin(); iter != m_Objects.end(); iter++ )
      n.push_back( iter.key() );

   return( n );
}


/*----------------------------------------------------------------------------*/
/*!
   Retrieves pointers to all direct object children
   \return a vector of pointers to objects
*/
/*----------------------------------------------------------------------------*/
QVector<RveHDF5Object *> RveHDF5Object::objects()
{
   QVector<RveHDF5Object *> o;
   QMap<QString, RveHDF5Object *>::iterator iter;

   for( iter = m_Objects.begin(); iter != m_Objects.end(); iter++ )
      o.push_back( iter.value() );

   return( o );
}


/*----------------------------------------------------------------------------*/
/*!
   Prints information about the current object and all its children
*/
/*----------------------------------------------------------------------------*/
void RveHDF5Object::print()
{
   QMap<QString, RveHDF5Object *>::iterator iter;

   for( iter = m_Objects.begin(); iter != m_Objects.end(); iter++ )
   {
      RveHDF5Object *o = iter.value();
      o->print();
   }
}
