/*----------------------------------------------------------------------------*/
/*!
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 "RveHDF5Dataset.h"
#include <QImage>
#include <QtGlobal>

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

/*----------------------------------------------------------------------------*/
/*!
   Create the dataset object from the dataset of an HDF5 file.
   \param name name of the dataset
   \param dataset handle of the dataset in the hdf5 file
   \param parent pointer to the parent object; can be omitte
*/
/*----------------------------------------------------------------------------*/
RveHDF5Dataset::RveHDF5Dataset( QString name, hid_t dataset, RveHDF5Object *parent )
{
   m_Name = name;
   m_pParent = parent;
   load( dataset );
}


/*----------------------------------------------------------------------------*/
/*!
   Create the dataset object from raw data
   \param name name of the dataset
   \param type data type of the dataset
   \param nDims the number of dimensions
   \param dimensionality pointer to an array of integers which contains the
   number of elements in each dimension (this array must be nDims long)
   \param data pointer to the raw data; the dataset object will keep an own copy
   \param typesize the size of each element in the dataset in bytes
*/
/*----------------------------------------------------------------------------*/
RveHDF5Dataset::RveHDF5Dataset( QString name, RveHDF5Attribute::ATTR_TYPE type, int nDims, int *dimensionality, void *data, int typeSize )
{
   m_Name = name;
   m_Type = type;
   m_NumDims = nDims;
   m_TypeSize = typeSize;

   m_pDimensionality = new int[nDims];
   m_NumBytes = typeSize;

   for( int i = 0; i < nDims; i++ )
   {
      m_pDimensionality[i] = dimensionality[i];
      m_NumBytes *= dimensionality[i];
   }

   m_pData = malloc( m_NumBytes );
   memcpy( m_pData, data, m_NumBytes );
}


/*----------------------------------------------------------------------------*/
/*!
   Destructor
*/
/*----------------------------------------------------------------------------*/
RveHDF5Dataset::~RveHDF5Dataset()
{
   free( m_pData );
   delete m_pDimensionality;
}


/*----------------------------------------------------------------------------*/
/*!
   Retrieve the size of each element in the dataset in bytes
   \return datatype size
*/
/*----------------------------------------------------------------------------*/
int RveHDF5Dataset::dataTypeSize()
{
   return( m_TypeSize );
}


/*----------------------------------------------------------------------------*/
/*!
   Retrieve the data type of the dataset
   \return the datatype
*/
/*----------------------------------------------------------------------------*/
RveHDF5Attribute::ATTR_TYPE RveHDF5Dataset::dataType()
{
   return( m_Type );
}


/*----------------------------------------------------------------------------*/
/*!
   Load the data from a dataset object in an HDF5 file
   \param dset handle of the hdf5 dataset object
   \return true on success
*/
/*----------------------------------------------------------------------------*/
bool RveHDF5Dataset::load( hid_t dset )
{
   // retrieve dataset dataspace and type
   hid_t spc = H5Dget_space( dset );
   hid_t type = H5Dget_type( dset );
   // retrieve total number of elements
   hssize_t nelements = H5Sget_simple_extent_npoints( spc );
   // retrieve number of dimensions
   int rank = H5Sget_simple_extent_ndims( spc );
   // retrieve the dimensionality
   hsize_t *dims = new hsize_t[rank];
   H5Sget_simple_extent_dims( spc, dims, 0 );

   // retrieve the dataset type size
   m_TypeSize = H5Tget_size( type );

   H5T_sign_t sign =  H5Tget_sign( type );

   // this should be extended to support more data types
   hid_t nativeType = type;
   switch( H5Tget_class( type ) )
   {
       case H5T_FLOAT:
         m_Type = RveHDF5Attribute::ATTR_FLOAT;
         if( m_TypeSize == 4 )
            nativeType = H5T_NATIVE_FLOAT;
         else
         if( m_TypeSize == 8 )
            nativeType = H5T_NATIVE_DOUBLE;
         break;

       case H5T_INTEGER:
         m_Type = RveHDF5Attribute::ATTR_INTEGER;
         if( m_TypeSize == 1 )
            nativeType = ( sign == H5T_SGN_NONE ) ? H5T_NATIVE_UINT8 : H5T_NATIVE_INT8;
         else
         if( m_TypeSize == 2 )
            nativeType = ( sign == H5T_SGN_NONE ) ? H5T_NATIVE_UINT16 : H5T_NATIVE_INT16;
         else
         if( m_TypeSize == 4 )
            nativeType = ( sign == H5T_SGN_NONE ) ? H5T_NATIVE_UINT32 : H5T_NATIVE_INT32;
         break;

       case H5T_COMPOUND:
          m_Type = RveHDF5Attribute::ATTR_COMPOUND;
          break;

       default:
         m_Type = RveHDF5Attribute::ATTR_NONE;
         break;
   }

   // read the data
   m_pData = malloc( nelements * H5Tget_size( nativeType ) );
   H5Dread( dset, nativeType, H5S_ALL, H5S_ALL, H5P_DEFAULT, m_pData );

   H5Tclose( type );
   H5Sclose( spc );

   // copy the dimensionality
   m_pDimensionality = new int[rank];
   for( int i = 0; i < rank; i++ )
      m_pDimensionality[i] = dims[i];

   // an delete the original
   delete dims;

   m_NumDims = rank;
   m_NumBytes = nelements;

   return( true );
}


/*----------------------------------------------------------------------------*/
/*!
   Retrieve total number of bytes in the dataset
   \return total number of bytes
*/
/*----------------------------------------------------------------------------*/
size_t RveHDF5Dataset::size()
{
   return( m_NumBytes );
}


/*----------------------------------------------------------------------------*/
/*!
   Retrieve a pointer to the raw data
   \return pointer to the raw data
*/
/*----------------------------------------------------------------------------*/
void *RveHDF5Dataset::data()
{
   return( m_pData );
}


/*----------------------------------------------------------------------------*/
/*!
   Retrieve the number of dimensions of the dataset
   \return number of dimensions
*/
/*----------------------------------------------------------------------------*/
int RveHDF5Dataset::numDimensions()
{
   return( m_NumDims );
}


/*----------------------------------------------------------------------------*/
/*!
   Retrieve the number of elements in one specific dimension
   \param n the dimension
   \return the number of elements in dimension n
*/
/*----------------------------------------------------------------------------*/
int RveHDF5Dataset::dim( int n )
{
   if( n < 0 || n >= m_NumDims )
      return( -1 );

   return( m_pDimensionality[n] );
}


/*----------------------------------------------------------------------------*/
/*!
   Retrieve the dataset element type
   \return the dataset element type
*/
/*----------------------------------------------------------------------------*/
RveHDF5Object::OBJ_TYPE RveHDF5Dataset::type()
{
   return( RveHDF5Object::TYPE_DATASET );
}


/*----------------------------------------------------------------------------*/
/*!
   Print some information about the dataset
*/
/*----------------------------------------------------------------------------*/
void RveHDF5Dataset::print()
{
   std::cout << "(DATASET) " << path().toStdString() << std::endl;
   printf( "%d bytes\n", size() * dataTypeSize() );
   printf( "Number of dimensions: %d\n", m_NumDims );
   for( int i = 0; i < m_NumDims; i++ )
      printf( "Dim[%d] = %d\n", i, m_pDimensionality[i] );
   RveHDF5Object::print();
   printf( "** Data:\n" );

   for( size_t i = 0; i < size(); i++ )
   {
      if( i % 45 == 0 )
      {
         printf( "\n%08d ", i );
      }
      if( dataType() == RveHDF5Attribute::ATTR_INTEGER )
         printf( "%02x ", ((quint8 *)data())[i] );
      else
      if( dataType() == RveHDF5Attribute::ATTR_FLOAT )
      {
         float v = ((float *)data())[i];
         printf( "%f ", v );
      }
   }

   printf( "**\n" );
}


/*----------------------------------------------------------------------------*/
/*!
   Add an attribute to the dataset
   \param obj pointer to the attribute
   \return true on success
*/
/*----------------------------------------------------------------------------*/
bool RveHDF5Dataset::add( RveHDF5Attribute *obj )
{
   return( RveHDF5Object::add( (RveHDF5Object *)obj ) );
}


/*----------------------------------------------------------------------------*/
/*!
   Retrieve an attribute of the dataset by its name
   \param name the name of the attribute to retrieve
   \return pointer to the attribute or 0 on failure
*/
/*----------------------------------------------------------------------------*/
RveHDF5Attribute *RveHDF5Dataset::get( QString name )
{
   return( (RveHDF5Attribute *)RveHDF5Object::get( name ) );
}


/*----------------------------------------------------------------------------*/
/*!
   Retrieve all direct children names of the dataset
   \return vector of object names
*/
/*----------------------------------------------------------------------------*/
QStringList RveHDF5Dataset::names()
{
   return( RveHDF5Object::names() );
}


/*----------------------------------------------------------------------------*/
/*!
   Retrieve all direct children attributes
   \return vector of pointers to the dataset's children attributes
*/
/*----------------------------------------------------------------------------*/
QVector<RveHDF5Attribute *> RveHDF5Dataset::attributes()
{
   QVector<RveHDF5Attribute *> objects;
   QMap<QString, RveHDF5Object *>::iterator iter;

   for( iter = m_Objects.begin(); iter != m_Objects.end(); iter++)
      objects.push_back( (RveHDF5Attribute *)iter.value() );

   return( objects );
}


/*----------------------------------------------------------------------------*/
/*!
   Retrieve an attribute one of a set of paths. The first valid path is
   used to retrieve the attribute.
   \param paths a set of paths
   \return pointer to the found attribute or 0 on failure
*/
/*----------------------------------------------------------------------------*/
RveHDF5Attribute *RveHDF5Dataset::getByPath( const QStringList &paths )
{
   return( (RveHDF5Attribute *)RveHDF5Object::getByPath( paths ) );
}


/*----------------------------------------------------------------------------*/
/*!
   Retrieve an attribute by its path
   \return pointer to the found attribute or 0 on failure
*/
/*----------------------------------------------------------------------------*/
RveHDF5Attribute *RveHDF5Dataset::getByPath( QString path )
{
   return( (RveHDF5Attribute *)RveHDF5Object::getByPath( path ) );
}


/*----------------------------------------------------------------------------*/
/*!
   Save the dataset to an hdf5 file
   \param f handle of the hdf5 file or object
   \return an HDF5::Error object
*/
/*----------------------------------------------------------------------------*/
bool RveHDF5Dataset::save( hid_t f )
{
   bool ok = true;

   // translate our datatype to an hdf5 datatype
   hid_t t = H5T_NO_CLASS;
   switch( dataType() )
   {
      case RveHDF5Attribute::ATTR_INTEGER:
      {
         t = H5T_NATIVE_INT;
         break;
      }

      case RveHDF5Attribute::ATTR_FLOAT:
      {
         t = H5T_NATIVE_FLOAT;
         break;
      }

      case RveHDF5Attribute::ATTR_STRING:
      {
         t = H5T_C_S1;
         break;
      }

      case RveHDF5Attribute::ATTR_UCHAR:
      {
         t = H5T_NATIVE_UCHAR;
         if( m_TypeSize == 1 )
            t = H5T_NATIVE_UINT8;
         else
         if( m_TypeSize == 2 )
            t = H5T_NATIVE_UINT16;
         else
         if( m_TypeSize == 4 )
            t = H5T_NATIVE_UINT32;
         break;
      }

      case RveHDF5Attribute::ATTR_COMPOUND:
      {
         t = H5T_COMPOUND;
         break;
      }

      default:
      {
         break;
      }
   }

   // create a datatype object
   hid_t mtype = H5Tcopy( t );
   // set total size of datatype
   H5Tset_size( mtype, dataTypeSize() );
   // create the dataspace
   hsize_t *dimSizes = new hsize_t[numDimensions()];
   for( int i = 0; i < numDimensions(); i++ )
      dimSizes[i] = dim( i );
   hid_t spc = H5Screate_simple( numDimensions(), dimSizes, 0 );

   // Enable compression
   int xs, ys;
   if( numDimensions() == 1 )
   {
      xs = dimSizes[0];
      ys = 1;
   } else
   {
      xs = dimSizes[0];
      ys = 1;
      for( int i = 1; i < numDimensions(); i++ )
      {
         ys = ys * dimSizes[i];
      }
   }

   hsize_t chunk[ 2 ] = { (hsize_t) xs, (hsize_t) ys };
   unsigned int filter_info;
   hid_t dcpl = H5P_DEFAULT;
   if( H5Zfilter_avail( H5Z_FILTER_DEFLATE ) )
   {
      H5Zget_filter_info( H5Z_FILTER_DEFLATE, &filter_info );
      if( ( filter_info & H5Z_FILTER_CONFIG_ENCODE_ENABLED ) &&
          ( filter_info & H5Z_FILTER_CONFIG_DECODE_ENABLED ) )
      {
         dcpl = H5Pcreate( H5P_DATASET_CREATE );
         H5Pset_deflate( dcpl, 6 );
         H5Pset_chunk( dcpl, numDimensions(), chunk );
      }
   }

   // create dataset object
   hid_t d = H5Dcreate( f, name().toLatin1(), mtype, spc, dcpl );
   if( d >= 0 )
   {
      // write the data to the dataset
      H5Dwrite( d, t, H5S_ALL, H5S_ALL, H5P_DEFAULT, m_pData );
      // save the children of this dataset
      RveHDF5Object::save( d );

      H5Dclose( d );
   } else
   {
      H5Eprint( stdout );
      ok = false;
   }

   if( dcpl != H5P_DEFAULT )
   {
      H5Pclose( dcpl );
   }

   delete dimSizes;
   // destroy dataspace object
   H5Sclose( spc );
   // destroy datatype object
   H5Tclose( mtype );

   return( ok );
}
