/*============================================================================
  MetaIO
  Copyright 2000-2010 Insight Software Consortium

  Distributed under the OSI-approved BSD License (the "License");
  see accompanying file Copyright.txt for details.

  This software is distributed WITHOUT ANY WARRANTY; without even the
  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  See the License for more information.
============================================================================*/
#include "metaTypes.h"

#ifndef ITKMetaIO_METAIMAGE_H
#  define ITKMetaIO_METAIMAGE_H

#  include "metaUtils.h"
#  include "metaObject.h"

#  include "metaImageTypes.h"
#  include "metaImageUtils.h"

/*!    MetaImage (.h and .cpp)
 *
 * Description:
 *    Reads and Writes MetaImageFiles.
 *    MetaImageFiles can be in one of two possible formats:
 *       a combined header/data format, typically designated .mha files
 *       or as separate header and data files, typically designated
 *       .mhd and .mda files
 *
 * Features:
 *    Header information is in ascii format - for easy creation, editing,
 *    and review. If separate files are used, a specified number of
 *    header-bytes in the datafile can be skipped
 *       - in this manner, different file formats (e.g., bmp, img,
 *          and /usr/Image) can be converted to metaImageFile format by
 *          merely specifying a separate text header (.mhd) file and in that
 *          file specifying how many data-header-bytes should be skipped.
 *          Thereby the data files can serve a dual role (as bitmap files
 *          and as metaImage data files)
 *    Supports all pixel formats
 *    Handles byte ordering (MSB/LSB)
 *    Provides the ability to handle associated medical image
 *          information (element size, element spacing, etc).
 *    Has required and optional header data (provides rapid formation
 *          or extensive documentation).
 *    REQUIRED: NDims, DimSize, ByteOrderMSB, ElementDataType, DataFileName
 *
 * \author Stephen R. Aylward
 *
 * \date August 29, 1999
 *
 * Depends on:
 *    MetaUtils.h
 *    MetaFileLib.h
 */

#  if (METAIO_USE_NAMESPACE)
namespace METAIO_NAMESPACE
{
#  endif

class METAIO_EXPORT MetaImage : public MetaObject
{
public:
  // Constructors & Destructor
  MetaImage();

  explicit MetaImage(const char * _headerName);

  explicit MetaImage(MetaImage * _im); // share memory

  MetaImage(int               _nDims,
            const int *       _dimSize,
            const float *     _elementSpacing,
            MET_ValueEnumType _elementType,
            int               _elementNumberOfChannels = 1,
            void *            _elementData = nullptr);

  MetaImage(int               _nDims,
            const int *       _dimSize,
            const double *    _elementSpacing,
            MET_ValueEnumType _elementType,
            int               _elementNumberOfChannels = 1,
            void *            _elementData = nullptr);

  MetaImage(int               _x,
            int               _y,
            double            _elementSpacingX,
            double            _elementSpacingY,
            MET_ValueEnumType _elementType,
            int               _elementNumberOfChannels = 1,
            void *            _elementData = nullptr);

  MetaImage(int               _x,
            int               _y,
            int               _z,
            double            _elementSpacingX,
            double            _elementSpacingY,
            double            _elementSpacingZ,
            MET_ValueEnumType _elementType,
            int               _elementNumberOfChannels = 1,
            void *            _elementData = nullptr);

  ~MetaImage() override;

  void
  PrintInfo() const override;

  void
  CopyInfo(const MetaObject * _object) override;

  void
  Clear() override;

  // Legacy for floating point elementSpacing
  bool
  InitializeEssential(int               _nDims,
                      const int *       _dimSize,
                      const float *     _elementSpacing,
                      MET_ValueEnumType _elementType,
                      int               _elementNumberOfChannels = 1,
                      void *            _elementData = nullptr,
                      bool              _allocElementMemory = true,
                      bool              _initializePosition = true);

  bool
  InitializeEssential(int               _nDims,
                      const int *       _dimSize,
                      const double *    _elementSpacing,
                      MET_ValueEnumType _elementType,
                      int               _elementNumberOfChannels = 1,
                      void *            _elementData = nullptr,
                      bool              _allocElementMemory = true,
                      bool              _initializePosition = true);

  int
  HeaderSize() const;
  void
  HeaderSize(int _headerSize);

  MET_ImageModalityEnumType
  Modality() const;
  void
  Modality(MET_ImageModalityEnumType _modality);

  //    DimSize(...)
  //       REQUIRED Field
  //       Number of elements along each dimension
  const int *
  DimSize() const;
  int
  DimSize(int _i) const;
  // void  DimSize(const int * _dimSize);
  // void  DimSize(int _i, int _value);

  //    Quantity()
  //       Not a field in file
  //       Total number of elements in image (Prod(dimSize[i]))
  std::streamoff
  Quantity() const;

  //    SubQuantity(...)
  //       Not a field in file
  //       Number of elements in image spanning sub-dimensions
  //       E.g., elements per line, 2D sub-image, 3D sub-volume,
  const std::streamoff *
  SubQuantity() const;
  std::streamoff
  SubQuantity(int _i) const;

  //    SequenceID(...)
  //       Optional Field
  //       DICOM designation of this image relative to other images
  //         acquired at the same time
  const float *
  SequenceID() const;
  float
  SequenceID(int _i) const;
  void
  SequenceID(const float * _sequenceID);
  void
  SequenceID(int _i, float _value);

  //    ElemSize(...)
  //       Optional Field
  //       Physical size (in MM) of each element in the image
  //       (0 = xSize, 1 = ySize, 2 = zSize)
  bool
  ElementSizeValid() const;
  void
  ElementSizeValid(bool _elementSizeValid);
  const double *
  ElementSize() const;
  double
  ElementSize(int _i) const;
  void
  ElementSize(const double * _elementSize);
  void
  ElementSize(const float * _elementSize); // legacy
  void
  ElementSize(int _i, double _value);

  MET_ValueEnumType
  ElementType() const;
  void
  ElementType(MET_ValueEnumType _elementType);

  int
  ElementNumberOfChannels() const;
  void
  ElementNumberOfChannels(int _elementNumberOfChannels);

  //    ElemMakeByteOrderMSB(), ElemMakeByteOrderLSB(),
  //    ElemByteOrderSwap(), ElemByteOrderFix()
  //       The following functions are available only after
  //       ReadImageData() or if _read_and_close=TRUE when read
  // if streaming is used, then the size of buffer in total number
  // of elements, should be passed as an argument, otherwise the
  // internal value Quantity() will be used
  void
  ElementByteOrderSwap(std::streamoff _quantity = 0);
  bool
  ElementByteOrderFix(std::streamoff _quantity = 0);

  //    Min(...) Max(...)
  //       The default max returned is the largest allowed by
  //         ElemNBytes (12 bit uint16_t will give 4096 max).
  //       This may not represent the true max.   Use _reCalc=true
  //         to force a calcuation of the actual max element value.
  bool
  ElementMinMaxValid() const;
  void
  ElementMinMaxValid(bool _elementMinMaxValid);
  void
  ElementMinMaxRecalc();
  double
  ElementMin() const;
  void
  ElementMin(double _elementMin);
  double
  ElementMax() const;
  void
  ElementMax(double _elementMax);

  double
  ElementToIntensityFunctionSlope() const;
  void
  ElementToIntensityFunctionSlope(double _elementToIntensityFunctionSlope);
  double
  ElementToIntensityFunctionOffset() const;
  void
  ElementToIntensityFunctionOffset(double _elementOffset);

  // (Copied from ITK's definition of Direction and Origin)
  //
  //  The position and orientation of an image is defined by its "Origin"
  // and its "Directions".  The "Origin" is the physical position of the
  // pixel whose "Index" is all zeros. The "Direction" of an image is a
  // matrix whose columns indicate the direction in physical space that
  // each dimension of the image traverses. The first column defines the
  // direction that the fastest moving index in the image traverses in
  // physical space while the last column defines the direction that the
  // slowest moving index in the image traverses in physical space.
  // 
  // Set the direction cosines of the image. The direction cosines
  // are vectors that point from one pixel to the next.
  //
  // Each column of the matrix indicates the direction cosines of the unit vector
  // that is parallel to the lines of the image grid corresponding to that
  // dimension. For example, an image with Direction matrix
  //
  //    0.866   0.500
  //   -0.500   0.866
  //
  // has an image grid were the fastest changing index (dimension[0]) walks
  // over a line that in physical space is oriented parallel to the vector
  // (0.866, -0.5). The second fastest changing index (dimension[1]) walks along
  // a line that in Physical space is oriented parallel to the vector
  // (0.5, 0.866)
  //
  // The columns of the Direction matrix are expected to form an
  // orthogonal right handed coordinate system.  But this is not
  // checked nor enforced in itk::ImageBase.
  //
  // For details, please see:
  //
  // https://www.itk.org/Wiki/Proposals:Orientation#Some_notes_on_the_DICOM_convention_and_current_ITK_usage
  const double *
  ElementOrigin() const;
  double
  ElementOrigin(int _i) const;
  void
  ElementOrigin(const double * _position);
  void
  ElementOrigin(const float * _position);
  void
  ElementOrigin(int _i, double _value);

  const double *
  ElementDirection() const;
  double
  ElementDirection(int _i, int _j) const;
  void
  ElementDirection(const double * _direction);
  void
  ElementDirection(int _i, int _j, double _value);

  bool
  AutoFreeElementData() const;
  void
  AutoFreeElementData(bool _autoFreeElementData);


  const char *
  ElementDataFileName() const;
  void
  ElementDataFileName(const char * _elementDataFileName);

  void *
  ElementData();
  double
  ElementData(std::streamoff _i) const;
  bool
  ElementData(std::streamoff _i, double _v);
  void
  ElementData(void * _elementData, bool _autoFreeElementData = false);

  //    ConverTo(...)
  //       Converts to a new data type
  //       Rescales using Min and Max (see above)
  bool
  ConvertElementDataTo(MET_ValueEnumType _elementType = MET_USHORT, double _toMin = 0, double _toMax = 0);
  bool
  ConvertElementDataToIntensityData(MET_ValueEnumType _elementType = MET_SHORT);
  bool
  ConvertIntensityDataToElementData(MET_ValueEnumType _elementType = MET_USHORT);

  static bool
  CanRead(const char * _headerName = nullptr) ;

  bool
  Read(const char * _headerName = nullptr ) override
  {
    return MetaImage::Read(_headerName, true, nullptr);
  }

  bool
  Read(const char * _headerName, bool _readElements, void * _buffer = nullptr);

  bool
  ReadROI(int *        _indexMin,
          int *        _indexMax,
          const char * _headerName = nullptr,
          bool         _readElements = true,
          void *       _buffer = nullptr,
          unsigned int subSamplingFactor = 1);


  static bool
  CanReadStream(METAIO_STREAM::ifstream * _stream) ;

  bool
  ReadStream(int _nDims, METAIO_STREAM::ifstream * _stream, bool _readElements = true, void * _buffer = nullptr);

  bool
  ReadROIStream(int *           _indexMin,
                int *           _indexMax,
                int             _nDims,
                METAIO_STREAM::ifstream * _stream,
                bool            _readElements = true,
                void *          _buffer = nullptr,
                unsigned int    subSamplingFactor = 1);

  bool
  Write(const char * _headName = nullptr) override
  {
    return MetaImage::Write(_headName, nullptr, true, nullptr, false);
  }

  bool
  Write(const char * _headName,
        const char * _dataName,
        bool         _writeElements = true,
        const void * _constElementData = nullptr,
        bool         _append = false);

  bool
  WriteROI(int *        _indexMin,
           int *        _indexMax,
           const char * _headName = nullptr,
           const char * _dataName = nullptr,
           bool         _writeElements = true,
           const void * _constElementData = nullptr,
           bool         _append = false);

  bool
  WriteStream(METAIO_STREAM::ofstream * _stream, bool _writeElements = true, const void * _constElementData = nullptr);


  bool
  Append(const char * _headName = nullptr) override;


  typedef std::pair<long, long> CompressionOffsetType;

  // PROTECTED
protected:
  static std::set<std::string> m_ImageReservedKeywords;

  MET_ImageModalityEnumType m_Modality;


  MET_CompressionTableType * m_CompressionTable{};

  int            m_DimSize[10]{};
  std::streamoff m_SubQuantity[10]{};
  std::streamoff m_Quantity{};

  int m_HeaderSize{};

  float m_SequenceID[4]{};

  bool   m_ElementSizeValid{};
  double m_ElementSize[10]{};

  MET_ValueEnumType m_ElementType;

  int m_ElementNumberOfChannels{};

  bool   m_ElementMinMaxValid{};
  double m_ElementMin{};
  double m_ElementMax{};

  double m_ElementToIntensityFunctionSlope{};
  double m_ElementToIntensityFunctionOffset{};

  double m_ElementOrigin[10]{};     // "ElementOrigin = "          0,0,0
  double m_ElementDirection[100]{}; // "ElementDirection = " 1,0,0,0,1,0,0,0,1
                                   
  bool m_AutoFreeElementData{};

  void * m_ElementData{};

  std::string m_ElementDataFileName;


  void
  M_ResetValues();

  void
  M_SetupReadFields() override;

  void
  M_SetupWriteFields() override;

  bool
  M_Read() override;

  // _dataQuantity is expressed in number of pixels. Internally it will be
  // scaled by the number of components and number of bytes per component.
  bool
  M_ReadElements(METAIO_STREAM::ifstream * _fstream, void * _data, std::streamoff _dataQuantity);

  // _totalDataQuantity and _dataQuantity are expressed in number of pixels.
  // Internally they will be scaled by the number of components and number of
  // bytes per component.
  bool
  M_ReadElementsROI(METAIO_STREAM::ifstream * _fstream,
                    void *          _data,
                    std::streamoff  _dataQuantity,
                    int *           _indexMin,
                    int *           _indexMax,
                    unsigned int    subSamplingFactor = 1,
                    std::streamoff  _totalDataQuantity = 0);

  bool
  M_ReadElementData(METAIO_STREAM::ifstream * _fstream, void * _data, std::streamoff _dataQuantity);

  bool
  M_WriteElements(METAIO_STREAM::ofstream * _fstream, const void * _data, std::streamoff _dataQuantity);

  bool
  M_WriteElementsROI(METAIO_STREAM::ofstream * _fstream,
                     const void *    _data,
                     std::streampos  _dataPos,
                     const int *     _indexMin,
                     const int *     _indexMax);

  bool
  M_WriteElementData(METAIO_STREAM::ofstream * _fstream, const void * _data, std::streamoff _dataQuantity);

  static bool
  M_FileExists(const char * filename) ;

  static bool
  FileIsFullPath(const char * in_name) ;

  static std::string
  M_GetTagValue(const std::string & buffer, const char * tag) ;

  // PRIVATE
private:
  void
  InitHelper(int               _nDims,
             const int *       _dimSize,
             const double *    _elementSpacing,
             MET_ValueEnumType _elementType,
             int               _elementNumberOfChannels,
             void *            _elementData);

};

#  if (METAIO_USE_NAMESPACE)
};
#  endif


#endif
