/*=========================================================================
 *
 *  Copyright NumFOCUS
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *         https://www.apache.org/licenses/LICENSE-2.0.txt
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *=========================================================================*/
/**
 *         The specification for this file format is taken from the
 *         web site https://analyzedirect.com/support/10.0Documents/Analyze_Resource_01.pdf
 * \author Hans J. Johnson
 *         The University of Iowa 2002
 */

#ifndef itkHDF5ImageIO_h
#define itkHDF5ImageIO_h
#include "ITKIOHDF5Export.h"
#include "itkMetaDataObjectBase.h"
#include "itkMetaDataDictionary.h"
#include <memory> // For unique_ptr.

// itk namespace first suppresses
// kwstyle error for the H5 namespace below
namespace itk
{}
namespace H5
{
class H5File;
class DataSpace;
class DataSet;
} // namespace H5

#include "itkStreamingImageIOBase.h"

namespace itk
{
/**
 * \class HDF5ImageIO
 *
 * \author Kent Williams
 * \brief Class that defines how to read HDF5 file format.
 * HDF5 IMAGE FILE FORMAT - As much information as I can determine from sourceforge.net/projects/HDF5lib
 *
 * \ingroup ITKIOHDF5
 *
 * HDF5 paths for elements in file
 * \li N is dimension of image
 * \li \/ITKVersion                   ITK Library Version string
 * \li \/HDFVersion                   HDF Version String
 * \li \/ITKImage                     Root Image Group
 * \li \/ITKImage\/\<name\>             name is arbitrary, but to parallel Transform I/O
 *                                    keep an image in a subgroup. The idea is to
 *                                    parallel transform file structure.
 * \li \/ITKImage\/\<name\>\/Origin     N-D point double
 * \li \/ITKImage\/\<name\>\/Directions N N-vectors double
 * \li \/ITKImage\/\<name\>\/Spacing    N-vector double
 * \li \/ITKImage\/\<name\>\/Dimensions N-vector itk::SizeValueType
 * \li \/ITKImage\/\<name\>\/VoxelType  String representing voxel type.
 *                             This can be inferred from the VoxelData
 *                             type info, but it makes the file more
 *                             user friendly with respect to HDF5 viewers.
 * \li \/ITKImage\/\<name\>\/VoxelData  multi-dim array of voxel data
 *                             in the case of non-scalar voxel type,
 *                             keep voxel components together, to make
 *                             loading possible without
 * \li \/ITKImage\/\<name\>\/MetaData   Group for storing metadata from
 *                             MetaDataDictionary
 * \li \/ITKImage\/\<name\>\/MetaData\/\<item-name\>
 *                             Dataset containing data for item-name
 *                             in the MetaDataDictionary
 * re-arrangement.
 *
 *
 */

class ITKIOHDF5_EXPORT HDF5ImageIO : public StreamingImageIOBase
{
public:
  ITK_DISALLOW_COPY_AND_MOVE(HDF5ImageIO);

  /** Standard class type aliases. */
  using Self = HDF5ImageIO;
  using Superclass = StreamingImageIOBase;
  using Pointer = SmartPointer<Self>;

  /** Method for creation through the object factory. */
  itkNewMacro(Self);

  /** \see LightObject::GetNameOfClass() */
  itkOverrideGetNameOfClassMacro(HDF5ImageIO);

  /*-------- This part of the interfaces deals with reading data. ----- */

  /** Determine if the file can be read with this ImageIO implementation.
   * \author Hans J Johnson
   * \param FileNameToRead The name of the file to test for reading.
   * \post Sets classes ImageIOBase::m_FileName variable to be FileNameToWrite
   * \return Returns true if this ImageIO can read the file specified.
   */
  bool
  CanReadFile(const char * FileNameToRead) override;

  /** Set the spacing and dimension information for the set filename. */
  void
  ReadImageInformation() override;

  /** Reads the data from disk into the memory buffer provided. */
  void
  Read(void * buffer) override;

  /*-------- This part of the interfaces deals with writing data. ----- */

  /** Determine if the file can be written with this ImageIO implementation.
   * \param name The name of the file to test for writing.
   * \author Hans J. Johnson
   * \post Sets classes ImageIOBase::m_FileName variable to be FileNameToWrite
   * \return Returns true if this ImageIO can write the file specified.
   */
  bool
  CanWriteFile(const char * name) override;

  /** Set the spacing and dimension information for the set filename. */
  void
  WriteImageInformation() override;

  /** Writes the data to disk from the memory buffer provided. Make sure
   * that the IORegions has been set properly. */
  void
  Write(const void * buffer) override;

protected:
  HDF5ImageIO();
  ~HDF5ImageIO() override;

  SizeType
  GetHeaderSize() const override;

  void
  PrintSelf(std::ostream & os, Indent indent) const override;

private:
  void
  WriteString(const std::string & path, const std::string & value);
  void
  WriteString(const std::string & path, const char * s);
  std::string
  ReadString(const std::string & path);

  void
  WriteScalar(const std::string & path, const bool value);
  void
  WriteScalar(const std::string & path, const long value);
  void
  WriteScalar(const std::string & path, const unsigned long value);
  void
  WriteScalar(const std::string & path, const long long value);
  void
  WriteScalar(const std::string & path, const unsigned long long value);

  template <typename TScalar>
  void
  WriteScalar(const std::string & path, const TScalar & value);

  template <typename TScalar>
  TScalar
  ReadScalar(const std::string & DataSetName);

  template <typename TScalar>
  void
  WriteVector(const std::string & path, const std::vector<TScalar> & vec);

  template <typename TScalar>
  std::vector<TScalar>
  ReadVector(const std::string & DataSetName);

  void
  WriteDirections(const std::string & path, const std::vector<std::vector<double>> & dir);

  std::vector<std::vector<double>>
  ReadDirections(const std::string & path);

  template <typename TType>
  bool
  WriteMeta(const std::string & name, MetaDataObjectBase * metaObjBase);
  template <typename TType>
  bool
  WriteMetaArray(const std::string & name, MetaDataObjectBase * metaObjBase);
  template <typename TType>
  void
  StoreMetaData(MetaDataDictionary * metaDict,
                const std::string &  HDFPath,
                const std::string &  name,
                unsigned long        numElements);
  void
  SetupStreaming(H5::DataSpace * imageSpace, H5::DataSpace * slabSpace);

  /* A convenience function to ensure that the
   * state of the HDF5ImageIO object is returned
   * to a state similar to constructing a new
   * object.  This is needed to ensure that
   * an HDF5ImageIO object can be used multiple
   * times to read/write many images.
   */
  void
  ResetToInitialState();

  std::unique_ptr<H5::H5File>  m_H5File;
  std::unique_ptr<H5::DataSet> m_VoxelDataSet;
  bool                         m_ImageInformationWritten{ false };
};
} // end namespace itk

#endif // itkHDF5ImageIO_h
