/*=========================================================================
 *
 *  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 itkImageAlgorithm_h
#define itkImageAlgorithm_h

#include "itkImageRegionIterator.h"

#include <type_traits>

namespace itk
{

template <typename TPixelType, unsigned int VImageDimension > class VectorImage;


/** \class ImageAlgorithm
 *  \brief A container of static functions which can operate on Images
 *  with Iterators.
 *
 *  These methods are modeled after the STL algorithms. They may use
 *  special optimization techniques to implement enhanced versions of
 *  the methods.
 *
 *  \ingroup ITKCommon
 */
struct ImageAlgorithm
{

    using TrueType = std::true_type;
    using FalseType = std::false_type;

/**
 * \brief This generic function copies a region from one image to
 * another. It may perform optimizations on the copy for efficiency.
 *
 * This method performs the equivalent to the following:
   \code
       itk::ImageRegionConstIterator<TInputImage> it( inImage, inRegion );
       itk::ImageRegionIterator<TOutputImage> ot( outImage, outRegion );

       while( !it.IsAtEnd() )
         {
         ot.Set( static_cast< typename TInputImage::PixelType >( it.Get() ) );
         ++ot;
         ++it;
         }
   \endcode
 *
 * \note: It is important not to explicitly pass the template
 * arguments to this method as it may not result in an optimized
 * method being called.
 */
  template<typename InputImageType, typename OutputImageType>
  static void Copy( const InputImageType *inImage, OutputImageType *outImage,
                    const typename InputImageType::RegionType &inRegion,
                    const typename OutputImageType::RegionType &outRegion )
  {
    ImageAlgorithm::DispatchedCopy( inImage, outImage, inRegion, outRegion );
  }

/// \cond HIDE_SPECIALIZATION_DOCUMENTATION
  template<typename TPixel1, typename TPixel2, unsigned int VImageDimension>
  static void Copy( const Image<TPixel1, VImageDimension> * inImage,
                               Image<TPixel2, VImageDimension> * outImage,
                               const typename Image<TPixel1, VImageDimension>::RegionType &inRegion,
                               const typename Image<TPixel2, VImageDimension>::RegionType &outRegion )
  {
    using _ImageType1 = Image<TPixel1, VImageDimension>;
    using _ImageType2 = Image<TPixel2, VImageDimension>;
    ImageAlgorithm::DispatchedCopy( inImage, outImage, inRegion, outRegion,
                                    std::is_convertible<typename _ImageType1::PixelType,
                                    typename _ImageType2::PixelType>()
                                   );
  }

  template<typename TPixel1, typename TPixel2, unsigned int VImageDimension>
  static void Copy( const VectorImage<TPixel1, VImageDimension> * inImage,
                               VectorImage<TPixel2, VImageDimension> * outImage,
                               const typename VectorImage<TPixel1, VImageDimension>::RegionType &inRegion,
                               const typename VectorImage<TPixel2, VImageDimension>::RegionType &outRegion )
  {
    using _ImageType1 = VectorImage<TPixel1, VImageDimension>;
    using _ImageType2 = VectorImage<TPixel2, VImageDimension>;
    ImageAlgorithm::DispatchedCopy( inImage, outImage, inRegion, outRegion,
                                    std::is_convertible<typename _ImageType1::PixelType,
                                    typename _ImageType2::PixelType>()
                                   );
  }

/// \endcond

  /**
   * \brief Sets the output region to the smallest
   * region of the output image that fully contains
   * the physical space covered by the input
   * region of the input image
   */
  template<typename InputImageType, typename OutputImageType>
  static typename OutputImageType::RegionType
  EnlargeRegionOverBox(const typename InputImageType::RegionType & inputRegion,
                       const InputImageType* inputImage,
                       const OutputImageType* outputImage);

  template<typename InputImageType, typename OutputImageType, typename TransformType>
  static typename OutputImageType::RegionType
  EnlargeRegionOverBox(const typename InputImageType::RegionType & inputRegion,
                       const InputImageType* inputImage,
                       const OutputImageType* outputImage,
                       const TransformType* transform);

private:

  /** This is an optimized method which requires the input and
   * output images to be the same, and the pixel being POD (Plain Old
   * Data).
   */
  template<typename InputImageType, typename OutputImageType>
  static void DispatchedCopy( const InputImageType *inImage, OutputImageType *outImage,
                              const typename InputImageType::RegionType &inRegion,
                              const typename OutputImageType::RegionType &outRegion, TrueType isSpecialized );

  /** this is the reference image iterator implementation */
  template<typename InputImageType, typename OutputImageType>
  static void DispatchedCopy( const InputImageType *inImage, OutputImageType *outImage,
                              const typename InputImageType::RegionType &inRegion,
                              const typename OutputImageType::RegionType &outRegion, FalseType isSpecialized = FalseType() );


  /** A utility class to get the number of internal pixels to make up
   * a pixel.
   */
  template <typename TImageType>
  struct PixelSize
  {
    static size_t Get( const TImageType *)
      {
      return 1;
      }
  };

/// \cond HIDE_SPECIALIZATION_DOCUMENTATION
  template <typename TPixelType, unsigned int VImageDimension>
  struct PixelSize< VectorImage<TPixelType, VImageDimension> >
  {
    using ImageType = VectorImage<TPixelType, VImageDimension>;
    static size_t Get( const  ImageType * i )
      {
      const size_t vectorLength = ImageType::AccessorFunctorType::GetVectorLength(i);
      return vectorLength;
      }
  };
/// \endcond

  /** Unary functor just for static_cast operator */
  template<typename TInputType, typename TOutputType>
  struct StaticCast
    {
    TOutputType operator() (const TInputType i) { return static_cast<TOutputType>(i); }
    };


  /** Function to dispatch to std::copy or std::transform. */
  template<typename TType>
  static TType* CopyHelper(const TType *first, const TType *last, TType *result)
    {
    // Note: On some MS compilers the following may generate a
    // warning. Please include itkMacro.h before <algorithm> or
    // another stl header to avoid.
    return std::copy(first, last, result);
    }

/// \cond HIDE_SPECIALIZATION_DOCUMENTATION
  template<typename TInputType, typename TOutputType>
  static TOutputType* CopyHelper(const TInputType *first, const TInputType *last, TOutputType *result)
    {
    return std::transform(first, last, result, StaticCast<TInputType,TOutputType>());
    }
/// \endcond


};
} // end namespace itk


#ifndef ITK_MANUAL_INSTANTIATION
#include "itkImageAlgorithm.hxx"
#endif


#endif //itkImageAlgorithm_h
