/*=========================================================================
 *
 *  Copyright Insight Software Consortium
 *
 *  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 itkImageFileReader_hxx
#define itkImageFileReader_hxx
#include "itkImageFileReader.h"

#include "itkObjectFactory.h"
#include "itkImageIOFactory.h"
#include "itkConvertPixelBuffer.h"
#include "itkPixelTraits.h"
#include "itkVectorImage.h"

#include "itksys/SystemTools.hxx"
#include <memory>  // For unique_ptr
#include <fstream>

namespace itk
{
template< typename TOutputImage, typename ConvertPixelTraits >
ImageFileReader< TOutputImage, ConvertPixelTraits >
::ImageFileReader()
{
  m_ImageIO = nullptr;
  this->SetFileName("");
  m_UserSpecifiedImageIO = false;
  m_UseStreaming = true;
}

template< typename TOutputImage, typename ConvertPixelTraits >
void ImageFileReader< TOutputImage, ConvertPixelTraits >
::PrintSelf(std::ostream & os, Indent indent) const
{
  Superclass::PrintSelf(os, indent);

  itkPrintSelfObjectMacro( ImageIO );

  os << indent << "UserSpecifiedImageIO flag: " << m_UserSpecifiedImageIO << "\n";
  os << indent << "m_UseStreaming: " << m_UseStreaming << "\n";
}

template< typename TOutputImage, typename ConvertPixelTraits >
void
ImageFileReader< TOutputImage, ConvertPixelTraits >
::SetImageIO(ImageIOBase *imageIO)
{
  itkDebugMacro("setting ImageIO to " << imageIO);
  if ( this->m_ImageIO != imageIO )
    {
    this->m_ImageIO = imageIO;
    this->Modified();
    }
  m_UserSpecifiedImageIO = true;
}

template< typename TOutputImage, typename ConvertPixelTraits >
void
ImageFileReader< TOutputImage, ConvertPixelTraits >
::GenerateOutputInformation()
{
  typename TOutputImage::Pointer output = this->GetOutput();

  itkDebugMacro(<< "Reading file for GenerateOutputInformation()" << this->GetFileName());

  // Check to see if we can read the file given the name or prefix
  //
  if ( this->GetFileName().empty() )
    {
    throw ImageFileReaderException(__FILE__, __LINE__, "FileName must be specified", ITK_LOCATION);
    }

  // Test if the file exists and if it can be opened.
  // An exception will be thrown otherwise.
  // We catch the exception because some ImageIO's may not actually
  // open a file. Still reports file error if no ImageIO is loaded.

  try
    {
    m_ExceptionMessage = "";
    this->TestFileExistanceAndReadability();
    }
  catch ( itk::ExceptionObject & err )
    {
    m_ExceptionMessage = err.GetDescription();
    }

  if ( m_UserSpecifiedImageIO == false ) //try creating via factory
    {
    m_ImageIO = ImageIOFactory::CreateImageIO(this->GetFileName().c_str(), ImageIOFactory::ReadMode);
    }

  if ( m_ImageIO.IsNull() )
    {
    std::ostringstream msg;
    msg << " Could not create IO object for reading file "
        << this->GetFileName().c_str() << std::endl;
    if ( !m_ExceptionMessage.empty() )
      {
      msg << m_ExceptionMessage;
      }
    else
      {
      std::list< LightObject::Pointer > allobjects =
        ObjectFactoryBase::CreateAllInstance("itkImageIOBase");
      if (!allobjects.empty())
        {
        msg << "  Tried to create one of the following:" << std::endl;
        for (auto & allobject : allobjects)
          {
          auto * io = dynamic_cast< ImageIOBase * >( allobject.GetPointer() );
          msg << "    " << io->GetNameOfClass() << std::endl;
          }
        msg << "  You probably failed to set a file suffix, or" << std::endl;
        msg << "    set the suffix to an unsupported type." << std::endl;
        }
      else
        {
        msg << "  There are no registered IO factories." << std::endl;
        msg << "  Please visit https://www.itk.org/Wiki/ITK/FAQ#NoFactoryException to diagnose the problem." << std::endl;
        }
      }
    ImageFileReaderException e(__FILE__, __LINE__, msg.str().c_str(), ITK_LOCATION);
    throw e;
    return;
    }

  // Got to allocate space for the image. Determine the characteristics of
  // the image.
  //
  m_ImageIO->SetFileName( this->GetFileName().c_str() );
  m_ImageIO->ReadImageInformation();

  SizeType dimSize;
  double   spacing[TOutputImage::ImageDimension];
  double   origin[TOutputImage::ImageDimension];
  typename TOutputImage::DirectionType direction;

  std::vector< std::vector< double > > directionIO;

  const unsigned int numberOfDimensionsIO = m_ImageIO->GetNumberOfDimensions();

  if ( numberOfDimensionsIO > TOutputImage::ImageDimension )
    {
    for ( unsigned int k = 0; k < numberOfDimensionsIO; ++k )
      {
      directionIO.push_back( m_ImageIO->GetDefaultDirection(k) );
      }
    }
  else
    {
    for ( unsigned int k = 0; k < numberOfDimensionsIO; ++k )
      {
      directionIO.push_back( m_ImageIO->GetDirection(k) );
      }
    }

  std::vector< double > axis;

  for ( unsigned int i = 0; i < TOutputImage::ImageDimension; ++i )
    {
    if ( i < numberOfDimensionsIO )
      {
      dimSize[i] = m_ImageIO->GetDimensions(i);
      spacing[i] = m_ImageIO->GetSpacing(i);
      origin[i]  = m_ImageIO->GetOrigin(i);

      // Please note: direction cosines are stored as columns of the
      // direction matrix
      axis = directionIO[i];
      for ( unsigned j = 0; j < TOutputImage::ImageDimension; ++j )
        {
        if ( j < numberOfDimensionsIO )
          {
          direction[j][i] = axis[j];
          }
        else
          {
          direction[j][i] = 0.0;
          }
        }
      }
    else
      {
      // Number of dimensions in the output is more than number of dimensions
      // in the ImageIO object (the file).  Use default values for the size,
      // spacing, origin and direction for the final (degenerate) dimensions.
      dimSize[i] = 1;
      spacing[i] = 1.0;
      origin[i] = 0.0;
      for ( unsigned j = 0; j < TOutputImage::ImageDimension; ++j )
        {
        if ( i == j )
          {
          direction[j][i] = 1.0;
          }
        else
          {
          direction[j][i] = 0.0;
          }
        }
      }
    }
  // Spacing is expected to be greater than 0
  // If negative, flip image direction along this axis.
  for ( unsigned int i = 0; i < TOutputImage::ImageDimension; ++i )
    {
    if(spacing[i] < 0)
      {
      spacing[i] = -spacing[i];
      for ( unsigned j = 0; j < TOutputImage::ImageDimension; ++j )
        {
        direction[j][i] = -direction[j][i];
        }
      }
    }
  output->SetSpacing(spacing);       // Set the image spacing
  output->SetOrigin(origin);         // Set the image origin
  output->SetDirection(direction);   // Set the image direction cosines

  //Copy MetaDataDictionary from instantiated reader to output image.
  output->SetMetaDataDictionary( m_ImageIO->GetMetaDataDictionary() );
  this->SetMetaDataDictionary( m_ImageIO->GetMetaDataDictionary() );

  IndexType start;
  start.Fill(0);

  ImageRegionType region;
  region.SetSize(dimSize);
  region.SetIndex(start);

  // If a VectorImage, this requires us to set the
  // VectorLength before allocate
  if ( strcmp(output->GetNameOfClass(), "VectorImage") == 0 )
    {
    using AccessorFunctorType = typename TOutputImage::AccessorFunctorType;
    AccessorFunctorType::SetVectorLength( output, m_ImageIO->GetNumberOfComponents() );
    }

  output->SetLargestPossibleRegion(region);
}

template< typename TOutputImage, typename ConvertPixelTraits >
void
ImageFileReader< TOutputImage, ConvertPixelTraits >
::TestFileExistanceAndReadability()
{
  // Test if the file exists.
  if ( !itksys::SystemTools::FileExists( this->GetFileName().c_str() ) )
    {
    ImageFileReaderException e(__FILE__, __LINE__);
    std::ostringstream       msg;
    msg << "The file doesn't exist. "
        << std::endl << "Filename = " << this->GetFileName()
        << std::endl;
    e.SetDescription( msg.str().c_str() );
    throw e;
    return;
    }

  // Test if the file can be open for reading access.
  std::ifstream readTester;
  readTester.open( this->GetFileName().c_str() );
  if ( readTester.fail() )
    {
    readTester.close();
    std::ostringstream msg;
    msg << "The file couldn't be opened for reading. "
        << std::endl << "Filename: " << this->GetFileName()
        << std::endl;
    ImageFileReaderException e(__FILE__, __LINE__, msg.str().c_str(), ITK_LOCATION);
    throw e;
    return;
    }
  readTester.close();
}

template< typename TOutputImage, typename ConvertPixelTraits >
void
ImageFileReader< TOutputImage, ConvertPixelTraits >
::EnlargeOutputRequestedRegion(DataObject *output)
{
  itkDebugMacro (<< "Starting EnlargeOutputRequestedRegion() ");
  typename TOutputImage::Pointer out = dynamic_cast< TOutputImage * >( output );
  typename TOutputImage::RegionType largestRegion = out->GetLargestPossibleRegion();
  ImageRegionType streamableRegion;

  // The following code converts the ImageRegion (templated over dimension)
  // into an ImageIORegion (not templated over dimension).
  ImageRegionType imageRequestedRegion = out->GetRequestedRegion();
  ImageIORegion   ioRequestedRegion(TOutputImage::ImageDimension);

  using ImageIOAdaptor = ImageIORegionAdaptor< TOutputImage::ImageDimension >;

  ImageIOAdaptor::Convert( imageRequestedRegion, ioRequestedRegion, largestRegion.GetIndex() );

  // Tell the IO if we should use streaming while reading
  m_ImageIO->SetUseStreamedReading(m_UseStreaming);

  // Delegate to the ImageIO the computation of how the
  // requested region must be enlarged.
  m_ActualIORegion  =
    m_ImageIO->GenerateStreamableReadRegionFromRequestedRegion(ioRequestedRegion);

  // the m_ActualIORegion may be more dimensions then the output
  // Image, in which case we still need to read this larger region to
  // support reading the "first slice" of a larger image
  // see bug 9212

  // convert the IORegion to a ImageRegion (which is dimension templated)
  // if the ImageIO must read a higher dimension region, this will
  // truncate the last dimensions
  ImageIOAdaptor::Convert( m_ActualIORegion, streamableRegion, largestRegion.GetIndex() );

  // Check whether the imageRequestedRegion is fully contained inside the
  // streamable region. Since, ImageRegion::IsInside regards zero
  // sized regions, as not being inside any other region, we must
  // specially check this condition to enable zero sized regions to
  // pass the region propagation phase of the pipeline.
  if ( !streamableRegion.IsInside(imageRequestedRegion)
       && imageRequestedRegion.GetNumberOfPixels() != 0 )
    {
    // we must use a InvalidRequestedRegionError since
    // DataObject::PropagateRequestedRegion() has an exception
    // specification
    std::ostringstream message;
    message << "ImageIO returns IO region that does not fully contain the requested region"
            << "Requested region: " << imageRequestedRegion
            << "StreamableRegion region: " << streamableRegion;
    InvalidRequestedRegionError e(__FILE__, __LINE__);
    e.SetLocation(ITK_LOCATION);
    e.SetDescription( message.str().c_str() );
    throw e;
    }

  itkDebugMacro (
    << "RequestedRegion is set to:" << streamableRegion << " while the m_ActualIORegion is: " << m_ActualIORegion);

  out->SetRequestedRegion(streamableRegion);
}

template< typename TOutputImage, typename ConvertPixelTraits >
void ImageFileReader< TOutputImage, ConvertPixelTraits >
::GenerateData()
{
  this->UpdateProgress( 0.0f );

  typename TOutputImage::Pointer output = this->GetOutput();

  itkDebugMacro (<< "ImageFileReader::GenerateData() \n"
                 << "Allocating the buffer with the EnlargedRequestedRegion \n"
                 << output->GetRequestedRegion() << "\n");

  // allocated the output image to the size of the enlarge requested region
  this->AllocateOutputs();

  // Test if the file exists and if it can be opened.
  // An exception will be thrown otherwise, since we can't
  // successfully read the file. We catch the exception because some
  // ImageIO's may not actually open a file. Still
  // reports file error if no ImageIO is loaded.

  try
    {
    m_ExceptionMessage = "";
    this->TestFileExistanceAndReadability();
    }
  catch ( itk::ExceptionObject & err )
    {
    m_ExceptionMessage = err.GetDescription();
    }

  // Tell the ImageIO to read the file
  m_ImageIO->SetFileName( this->GetFileName().c_str() );

  itkDebugMacro (<< "Setting imageIO IORegion to: " << m_ActualIORegion);
  m_ImageIO->SetIORegion(m_ActualIORegion);

  // the size of the buffer is computed based on the actual number of
  // pixels to be read and the actual size of the pixels to be read
  // (as opposed to the sizes of the output)
  size_t sizeOfActualIORegion = m_ActualIORegion.GetNumberOfPixels()
                                * ( m_ImageIO->GetComponentSize() * m_ImageIO->GetNumberOfComponents() );

  ImageIOBase::IOComponentType ioType =
    ImageIOBase
    ::MapPixelType< typename ConvertPixelTraits::ComponentType >::CType;
  if ( m_ImageIO->GetComponentType() != ioType
        || ( m_ImageIO->GetNumberOfComponents() !=
            ConvertPixelTraits::GetNumberOfComponents() ) )
    {
    // the pixel types don't match so a type conversion needs to be
    // performed
    itkDebugMacro( << "Buffer conversion required from: "
                    << m_ImageIO->GetComponentTypeAsString(m_ImageIO->GetComponentType())
                    << " to: "
                    << m_ImageIO->GetComponentTypeAsString(ioType)
                    << " ConvertPixelTraits::NumComponents "
                    << ConvertPixelTraits::GetNumberOfComponents()
                    << " m_ImageIO->NumComponents "
                    << m_ImageIO->GetNumberOfComponents() );

    const std::unique_ptr<char[]> loadBuffer(new char[sizeOfActualIORegion]);
    m_ImageIO->Read( static_cast< void * >( loadBuffer.get() ) );

    // See note below as to why the buffered region is needed and
    // not actualIOregion
    this->DoConvertBuffer( static_cast< void * >( loadBuffer.get() ),
                            output->GetBufferedRegion().GetNumberOfPixels() );
    }
  else if ( m_ActualIORegion.GetNumberOfPixels() !=
            output->GetBufferedRegion().GetNumberOfPixels() )
    {
    // NOTE:
    // for the number of pixels read and the number of pixels
    // requested to not match, the dimensions of the two regions may
    // be different, therefore we buffer and copy the pixels

    itkDebugMacro(<< "Buffer required because file dimension is greater then image dimension");

    OutputImagePixelType *outputBuffer = output->GetPixelContainer()->GetBufferPointer();

    const std::unique_ptr<char[]> loadBuffer(new char[sizeOfActualIORegion]);
    m_ImageIO->Read( static_cast< void * >( loadBuffer.get() ) );

    // we use std::copy_n here as it should be optimized to memcpy for
    // plain old data, but still is oop
    std::copy_n(reinterpret_cast< const OutputImagePixelType * >( loadBuffer.get() ),
                            output->GetBufferedRegion().GetNumberOfPixels(),
                            outputBuffer);
    }
  else
    {
    itkDebugMacro(<< "No buffer conversion required.");

    OutputImagePixelType *outputBuffer = output->GetPixelContainer()->GetBufferPointer();
    m_ImageIO->Read(outputBuffer);
    }

  this->UpdateProgress( 1.0f );
}

template< typename TOutputImage, typename ConvertPixelTraits >
void
ImageFileReader< TOutputImage, ConvertPixelTraits >
::DoConvertBuffer(void *inputData,
                  size_t numberOfPixels)
{
  // get the pointer to the destination buffer
  OutputImagePixelType *outputData =
    this->GetOutput()->GetPixelContainer()->GetBufferPointer();
  bool isVectorImage(strcmp(this->GetOutput()->GetNameOfClass(),
                            "VectorImage") == 0);
  // TODO:
  // Pass down the PixelType (RGB, VECTOR, etc.) so that any vector to
  // scalar conversion be type specific. i.e. RGB to scalar would use
  // a formula to convert to luminance, VECTOR to scalar would use
  // vector magnitude.

  // Create a macro as this code is a bit lengthy and repetitive
  // if the ImageIO pixel type is typeid(type) then use the ConvertPixelBuffer
  // class to convert the data block to TOutputImage's pixel type
  // see DefaultConvertPixelTraits and ConvertPixelBuffer

  // The first else if block applies only to images of type itk::VectorImage
  // VectorImage needs to copy out the buffer differently.. The buffer is of
  // type InternalPixelType, but each pixel is really 'k' consecutive pixels.

#define ITK_CONVERT_BUFFER_IF_BLOCK(_CType,type)                        \
  else if(m_ImageIO->GetComponentType() == _CType)                      \
    {                                                                   \
    if (isVectorImage)                                                  \
      {                                                                 \
      ConvertPixelBuffer<type,                                          \
                         OutputImagePixelType,                          \
                         ConvertPixelTraits                             \
                         >                                              \
        ::ConvertVectorImage(static_cast< type * >( inputData ),        \
                             m_ImageIO->GetNumberOfComponents(),        \
                             outputData,                                \
                             numberOfPixels);                           \
      }                                                                 \
    else                                                                \
      {                                                                 \
      ConvertPixelBuffer<type,                                          \
                         OutputImagePixelType,                          \
                         ConvertPixelTraits                             \
                         >                                              \
        ::Convert(static_cast< type * >( inputData ),                   \
                  m_ImageIO->GetNumberOfComponents(),                   \
                  outputData,                                           \
                  numberOfPixels);                                      \
      }                                                                 \
    }

  if(false) {}
  ITK_CONVERT_BUFFER_IF_BLOCK(ImageIOBase::UCHAR,unsigned char)
  ITK_CONVERT_BUFFER_IF_BLOCK(ImageIOBase::CHAR,char)
  ITK_CONVERT_BUFFER_IF_BLOCK(ImageIOBase::USHORT,unsigned short)
  ITK_CONVERT_BUFFER_IF_BLOCK(ImageIOBase::SHORT,short)
  ITK_CONVERT_BUFFER_IF_BLOCK(ImageIOBase::UINT,unsigned int)
  ITK_CONVERT_BUFFER_IF_BLOCK(ImageIOBase::INT,int)
  ITK_CONVERT_BUFFER_IF_BLOCK(ImageIOBase::ULONG,unsigned long)
  ITK_CONVERT_BUFFER_IF_BLOCK(ImageIOBase::LONG,long)
  ITK_CONVERT_BUFFER_IF_BLOCK(ImageIOBase::ULONGLONG,unsigned long long)
  ITK_CONVERT_BUFFER_IF_BLOCK(ImageIOBase::LONGLONG,long long)
  ITK_CONVERT_BUFFER_IF_BLOCK(ImageIOBase::FLOAT,float)
  ITK_CONVERT_BUFFER_IF_BLOCK(ImageIOBase::DOUBLE,double)
  else
    {
#define TYPENAME(x)                                     \
    m_ImageIO->GetComponentTypeAsString                 \
      (ImageIOBase::MapPixelType<x>::CType)

    ImageFileReaderException e(__FILE__, __LINE__);
    std::ostringstream       msg;
    msg << "Couldn't convert component type: "
        << std::endl << "    "
        << m_ImageIO->GetComponentTypeAsString( m_ImageIO->GetComponentType() )
        << std::endl << "to one of: "
        << std::endl << "    " << TYPENAME( unsigned char )
        << std::endl << "    " << TYPENAME( char )
        << std::endl << "    " << TYPENAME( unsigned short )
        << std::endl << "    " << TYPENAME( short )
        << std::endl << "    " << TYPENAME( unsigned int )
        << std::endl << "    " << TYPENAME( int )
        << std::endl << "    " << TYPENAME( unsigned long )
        << std::endl << "    " << TYPENAME( long )
        << std::endl << "    " << TYPENAME( unsigned long long )
        << std::endl << "    " << TYPENAME( long long )
        << std::endl << "    " << TYPENAME( float )
        << std::endl << "    " << TYPENAME( double )
        << std::endl;
    e.SetDescription( msg.str().c_str() );
    e.SetLocation(ITK_LOCATION);
    throw e;
    return;
    }
#undef ITK_CONVERT_BUFFER_IF_BLOCK
}
} //namespace ITK

#endif
