/*=========================================================================
 *
 *  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.
 *
 *=========================================================================*/
#ifndef itkUnaryFrequencyDomainFilter_h
#define itkUnaryFrequencyDomainFilter_h

#include "itkInPlaceImageFilter.h"
#include "itkFrequencyFFTLayoutImageRegionIteratorWithIndex.h"

namespace itk
{
/**
 * \class UnaryFrequencyDomainFilter
 * \brief Performs a unary operation on a frequency domain image
 *
 * A frequency filtering functor needs to be supplied via one of SetFunctor() overloads.
 * The functor should take FrequencyIteratorType reference as its only parameter.
 * If functor configurability is required,
 * those parameters should be passed directly to the functor at the place of definition.
 *
 *
 * Filters in the module ITKImageFrequency work with input images in the frequency domain.
 * This filter is templated over a TFrequencyIterator depending on the
 * frequency layout of the input image.
 *
 * Images in the dual space can be acquired experimentally, from scattering experiments or other techniques.
 * In that case use FrequencyImageRegionIteratorWithIndex because
 * the layout of dual space images is the same as spatial domain images.
 *
 * Frequency-domain images can be computed from any spatial-domain applying a Fourier Transform.
 * If ForwardFFTImageFilter was used, template this filter with
 * the FrequencyFFTLayoutImageRegionIteratorWithIndex.
 * Please note that FrequencyFFTLayoutImageRegionIteratorWithIndex requires a full FFT,
 * and is not compatible with the Hermitian optimization.
 *
 * To use this filter with Hermitian (halved-frequency) FFTs, use
 * FrequencyHalfHermitianFFTLayoutImageRegionIteratorWithIndex or its const version.
 *
 * If the output of the FFT is shifted, for example after applying FFTShiftImageFilter,
 * use FrequencyShiftedFFTLayoutImageRegionIteratorWithIndex.
 *
 * \sa UnaryGeneratorImageFilter
 *
 * \ingroup ITKImageFrequency
 */
template <typename TImageType, typename TFrequencyIterator = FrequencyFFTLayoutImageRegionIteratorWithIndex<TImageType>>
class ITK_TEMPLATE_EXPORT UnaryFrequencyDomainFilter : public InPlaceImageFilter<TImageType, TImageType>
{
public:
  ITK_DISALLOW_COPY_AND_MOVE(UnaryFrequencyDomainFilter);

  /** Standard class type alias. */
  using Self = UnaryFrequencyDomainFilter;
  using Superclass = InPlaceImageFilter<TImageType, TImageType>;
  using Pointer = SmartPointer<Self>;
  using ConstPointer = SmartPointer<const Self>;

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

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

  /** Typedef to images */
  using ImageType = TImageType;
  using ImagePointer = typename ImageType::Pointer;
  using ImageConstPointer = typename ImageType::ConstPointer;
  using IndexType = typename TImageType::IndexType;
  using PixelType = typename TImageType::PixelType;

  /** Typedef to describe the image region type. */
  using ImageRegionType = typename TImageType::RegionType;

  static constexpr unsigned int ImageDimension = TImageType::ImageDimension;

#ifdef ITK_USE_CONCEPT_CHECKING
  itkConceptMacro(ImageTypeHasNumericTraitsCheck, (Concept::HasNumericTraits<typename TImageType::PixelType>));
#endif

  /** Frequency Iterator types */
  using FrequencyIteratorType = TFrequencyIterator;
  using FrequencyValueType = typename FrequencyIteratorType::FrequencyValueType;

  /** Set to true when the you are dealing with images in the frequency
   * domain that have been computed using RealToHalfHermitianFFT, and the
   * original image in the spatial domain was odd.
   * Only needed when using HermitianFrequencyIterator and the original
   * image was odd. **/
  itkSetMacro(ActualXDimensionIsOdd, bool);
  itkGetConstReferenceMacro(ActualXDimensionIsOdd, bool);
  itkBooleanMacro(ActualXDimensionIsOdd);

  /** Returns factor with which the current frequency should be multiplied. */
  using ConstRefFunctionType = double(const FrequencyIteratorType &);

  /** Directly modifies the frequency as needed. */
  using ValueFunctionType = void(FrequencyIteratorType &);

#if !defined(ITK_WRAPPING_PARSER)
  /** The functor returns factor with which the current frequency should be multiplied. */
  void
  SetFunctor(const std::function<ConstRefFunctionType> & f)
  {
    auto inPlaceFunctor = [f](FrequencyIteratorType & freq) { freq.Value() *= f(freq); };

    m_DynamicThreadedGenerateDataFunction = [this, inPlaceFunctor](const ImageRegionType & outputRegionForThread) {
      return this->DynamicThreadedGenerateDataWithFunctor(inPlaceFunctor, outputRegionForThread);
    };

    this->Modified();
  }

  /** The functor directly modifies the frequency as needed. */
  void
  SetFunctor(const std::function<ValueFunctionType> & f)
  {
    m_DynamicThreadedGenerateDataFunction = [this, f](const ImageRegionType & outputRegionForThread) {
      return this->DynamicThreadedGenerateDataWithFunctor(f, outputRegionForThread);
    };

    this->Modified();
  }


  /** The functor returns factor with which the current frequency should be multiplied. */
  void
  SetFunctor(ConstRefFunctionType * f)
  {
    auto inPlaceFunctor = [f](FrequencyIteratorType & freq) { freq.Value() *= f(freq); };

    m_DynamicThreadedGenerateDataFunction = [this, inPlaceFunctor](const ImageRegionType & outputRegionForThread) {
      return this->DynamicThreadedGenerateDataWithFunctor(inPlaceFunctor, outputRegionForThread);
    };

    this->Modified();
  }

  /** The functor directly modifies the frequency as needed. */
  void
  SetFunctor(ValueFunctionType * funcPointer)
  {
    m_DynamicThreadedGenerateDataFunction = [this, funcPointer](const ImageRegionType & outputRegionForThread) {
      return this->DynamicThreadedGenerateDataWithFunctor(funcPointer, outputRegionForThread);
    };

    this->Modified();
  }


  /** Set the frequency functor by a "Functor Object"
   *
   * The functor defines an operation done per scalar frequency. A single copy
   * of the argument is created an used for all threads, so the functor
   * must be concurrent thread-safe. The functor must a have an
   * operator() method which accept arguments of FrequencyIteratorType&.
   */
  template <typename TFunctor>
  void
  SetFunctor(const TFunctor & functor)
  {
    m_DynamicThreadedGenerateDataFunction = [this, functor](const ImageRegionType & outputRegionForThread) {
      return this->DynamicThreadedGenerateDataWithFunctor(functor, outputRegionForThread);
    };

    this->Modified();
  }
#endif // !defined( ITK_WRAPPING_PARSER )

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

  /** UnaryFrequencyDomainFilter is implemented as a multithreaded filter.
   * Therefore, this implementation provides a DynamicThreadedGenerateData()
   * routine which is called for each processing thread.
   *
   * The template method is instantiated by the SetFunctor method, and
   * the generated code is run during the update.
   *
   * \sa ImageToImageFilter::ThreadedGenerateData(),
   *     ImageToImageFilter::GenerateData()  */
  template <typename TFunctor>
  void
  DynamicThreadedGenerateDataWithFunctor(const TFunctor &, const ImageRegionType & outputRegionForThread);
  void
  DynamicThreadedGenerateData(const ImageRegionType & outputRegionForThread) override;

private:
  std::function<void(const ImageRegionType &)> m_DynamicThreadedGenerateDataFunction{};

  bool m_ActualXDimensionIsOdd{ false };
};
} // end namespace itk

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

#endif // itkUnaryFrequencyDomainFilter_h
