/*=========================================================================
 *
 *  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
 *
 *         http://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.
 *
 *=========================================================================*/
#ifndef sitkImageFileReader_h
#define sitkImageFileReader_h

#include "sitkMacro.h"
#include "sitkImage.h"
#include "sitkImageReaderBase.h"
#include "sitkMemberFunctionFactory.h"


namespace itk
{

class MetaDataDictionary;

namespace simple
{

/** \class ImageFileReader
 * \brief Read an image file and return a SimpleITK Image.
 *
 * The reader can handle scalar images, and vector images. Pixel
 * types such as RGB, RGBA are loaded as multi-component
 * images with vector pixel types. Additionally, tensor images are
 * loaded with the pixel type being a 1-d vector.
 *
 * An interface is also provided to access the information from
 * the underlying itk::ImageIO. This information can be loaded
 * with the ReadImageInformation method. The information is from
 * the itk::ImageIO interface. In some degenerate cases reading
 * the bulk data may produce different results. Please see
 * itk::ImageFileReader for more details.
 *
 * Reading takes place by the ITK ImageIO factory mechanism. ITK
 * contains many ImageIO classes which are responsible for reading
 * separate file formats. By default, each ImageIO is asked if it
 * "can read" the file, and the first one which "can read" the
 * format is used. The list of available ImageIOs can be obtained
 * using the GetRegisteredImageIOs method. The ImageIO used can be
 * overridden with the SetImageIO method. This is useful in cases
 * when multiple ImageIOs "can read" the file and the user wants
 * to select a specific IO (not the first).
 *
 * \note DICOM tags are represented as strings in the meta-data
 * dictionary(s), therefore "0020|000D" and "0020|000d" are
 * different when accessing the tag value. This differs from
 * the hexadecimal numbers they represent, 0020|000D and 0020|000d
 * are equivalent. The ITK meta-data dictionary is string based
 * and uses lower case to represent the hexadecimal number read
 * from disk, so 0020|000d will work as a key and 0020|000D will
 * not be found in the dictionary (results in an exception if
 * attempting to access).
 * It is recommended to use lower case when setting and accessing
 * DICOM tags.
 *
 * \sa itk::simple::ReadImage for the procedural interface
 */
class SITKIO_EXPORT ImageFileReader : public ImageReaderBase
{
public:
  using Self = ImageFileReader;

  ~ImageFileReader() override;

  ImageFileReader();

  /** Print ourselves to string */
  std::string
  ToString() const override;

  /** return user readable name of the filter */
  std::string
  GetName() const override
  {
    return std::string("ImageFileReader");
  }

  SITK_RETURN_SELF_TYPE_HEADER
  SetFileName(const PathType & fn);
  PathType
  GetFileName() const;

  Image
  Execute() override;

  // Interface methods to access image file's meta-data and image
  // information after calling Execute or after calling
  // MetaDataRead, which does not load the bulk pixel data.

  /** \brief Read only the meta-data and image information in the file.
   *
   * This method can be used to determine what the size and pixel
   * type of an image file is without reading the whole
   * image. Even if SimpleITK does not support an image of a
   * certain dimension or type, the meta-information can still be
   * read.
   */
  void
  ReadImageInformation();

  /** \brief Image information methods updated via ReadImageInformation
   *
   * These accessor methods are valid after a call to ReadImageInformation
   * or Execute. They contain the image information from the file
   * via the itk::ImageIO. Information such as NumberOfComponents
   * is based of the file format, so the number of components for
   * internal types such as RGB or complex may not be described
   * the same as with SimpleITK's Image interface.
   *
   * The PixelID has been converted from the ITK type to
   * SimpleITK's interpretation, and will not change when loaded
   * as a SimpleITK Image.
   * @{
   */
  PixelIDValueEnum
  GetPixelID() const;
  PixelIDValueType
  GetPixelIDValue() const;
  unsigned int
  GetDimension() const;
  unsigned int
  GetNumberOfComponents() const;
  const std::vector<double> &
  GetOrigin() const;
  const std::vector<double> &
  GetSpacing() const;
  const std::vector<double> &
  GetDirection() const;
  const std::vector<uint64_t> &
  GetSize() const;
  /** @} */

  /** \brief Get the meta-data dictionary keys
   *
   * This is only valid after successful ReadImageInformation or Execute
   * of this filter.
   *
   * Returns a vector of keys to the key/value entries in the
   * file's meta-data dictionary. Iterate through with these keys
   * to get the values.
   **/
  std::vector<std::string>
  GetMetaDataKeys() const;

  /** \brief Query a meta-data dictionary for the existence of a key.
   **/
  bool
  HasMetaDataKey(const std::string & key) const;

  /** \brief Get the value of a meta-data dictionary entry as a string.
   *
   * If the key is not in the dictionary then an exception is
   * thrown.
   *
   * String types in the dictionary are returned as their native
   * string. Other types are printed to string before returning.
   **/
  std::string
  GetMetaData(const std::string & key) const;


  /** \brief size of image to extract from file.
   *
   * By default the reader loads the entire image, this is
   * specified when the size has zero length.
   *
   * If specified, then the image returned from `Execute` will be
   * of this size. If the ImageIO and file support reading just a
   * region, then the reader will perform streaming.
   *
   * The dimension of the image can be reduced by specifying a
   * dimension's size as 0. For example a size of $[10,20,30,0,0]$
   * results in a 3D image with size of $[10,20,30]$. This
   * enables reading a 5D image into a 3D image. If the length of
   * the specified size is greater than the dimension of the image
   * file, an exception will be generated. If the size's length is
   * less than the image's dimension then the missing values are
   * assumed to be zero.
   *
   * When the dimension of the image is reduced, the direction
   * cosine matrix will be set to the identity. However, the
   * spacing for the selected axis will remain. The matrix from
   * the file can still be obtained by
   * ImageFileReader::GetDirection.
   *
   * \sa ExtractImageFilter
   */
  SITK_RETURN_SELF_TYPE_HEADER
  SetExtractSize(const std::vector<unsigned int> & size);
  const std::vector<unsigned int> &
  GetExtractSize() const;


  /** \brief starting index from the image on disk to extract.
   *
   * Missing dimensions are treated the same as 0.
   *
   * \sa ExtractImageFilter
   */
  SITK_RETURN_SELF_TYPE_HEADER
  SetExtractIndex(const std::vector<int> & index);
  const std::vector<int> &
  GetExtractIndex() const;

protected:
  template <class TImageType>
  Image
  ExecuteInternal(itk::ImageIOBase *);

  /** Internal method which update's this classes stored meta-data
   * and image information.
   */
  void
  UpdateImageInformationFromImageIO(const itk::ImageIOBase * iobase);

private:
  // Internal method used implement extracting a region from the reader
  template <class TImageType, class TInternalImageType>
  Image
  ExecuteExtract(TInternalImageType * itkImage);

  // function pointer type
  typedef Image (Self::*MemberFunctionType)(itk::ImageIOBase *);

  // friend to get access to executeInternal member
  friend struct detail::MemberFunctionAddressor<MemberFunctionType>;
  std::unique_ptr<detail::MemberFunctionFactory<MemberFunctionType>> m_MemberFactory;


  std::function<std::vector<std::string>()>       m_pfGetMetaDataKeys;
  std::function<bool(const std::string &)>        m_pfHasMetaDataKey;
  std::function<std::string(const std::string &)> m_pfGetMetaData;

  PathType m_FileName;

  std::unique_ptr<MetaDataDictionary> m_MetaDataDictionary;

  PixelIDValueEnum    m_PixelType;
  unsigned int        m_Dimension;
  unsigned int        m_NumberOfComponents;
  std::vector<double> m_Direction;
  std::vector<double> m_Origin;
  std::vector<double> m_Spacing;

  std::vector<uint64_t> m_Size;

  std::vector<unsigned int> m_ExtractSize;
  std::vector<int>          m_ExtractIndex;
};

/**
 * \brief ReadImage is a procedural interface to the ImageFileReader
 *     class which is convenient for most image reading tasks.
 *
 *  \param filename the filename of an Image e.g. "cthead.mha"
 *  \param outputPixelType see ImageReaderBase::SetOutputPixelType
 *  \param imageIO see ImageReaderBase::SetImageIO
 *
 * \sa itk::simple::ImageFileReader for reading a single file.
 * \sa itk::simple::ImageSeriesReader for reading a series and meta-data dictionaries.
 */
SITKIO_EXPORT Image
ReadImage(const PathType & filename, PixelIDValueEnum outputPixelType = sitkUnknown, const std::string & imageIO = "");
} // namespace simple
} // namespace itk

#endif
