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

#include "itkContinuousIndex.h"
#include "itkFixedArray.h"
#include "itkGaussianDerivativeSpatialFunction.h"
#include "itkGaussianSpatialFunction.h"
#include "itkImage.h"
#include "itkImageFunction.h"
#include "itkNeighborhood.h"
#include "itkOffset.h"
#include "itkVector.h"

#include <vector>

namespace itk
{
/**
 * \class GaussianDerivativeImageFunction
 * \brief Compute the Gaussian derivatives of an the image
 *        at a specific location in space, i.e. point, index or continuous
 *        index.
 * \note From ITK 5, the Evaluate member functions of this class are concurrent
 * thread safe: It is safe to have multiple simultaneous Evaluate calls on a
 * GaussianDerivativeImageFunction object.
 *
 * This class is templated over the input image type.
 * \sa NeighborhoodOperator
 * \sa ImageFunction
 * \ingroup ITKImageFunction
 */
template< typename TInputImage, typename TOutput = double >
class ITK_TEMPLATE_EXPORT GaussianDerivativeImageFunction:
  public ImageFunction< TInputImage,
                        Vector< TOutput, TInputImage::ImageDimension >,
                        TOutput >
{
public:
  ITK_DISALLOW_COPY_AND_ASSIGN(GaussianDerivativeImageFunction);

  /** Standard class type aliases. */
  using Self = GaussianDerivativeImageFunction;
  using Superclass = ImageFunction< TInputImage,
                         Vector< TOutput, TInputImage::ImageDimension >,
                         TOutput >;
  using Pointer = SmartPointer< Self >;
  using ConstPointer = SmartPointer< const Self >;

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

  /** Run-time type information (and related methods). */
  itkTypeMacro(GaussianDerivativeImageFunction, ImageFunction);

  /** InputImageType type alias support */
  using InputImageType = TInputImage;
  using InputPixelType = typename InputImageType::PixelType;
  using IndexType = typename InputImageType::IndexType;

  /** Dimension of the underlying image. */
  static constexpr unsigned int ImageDimension = InputImageType::ImageDimension;

#if !defined( ITK_LEGACY_REMOVE )
  static constexpr unsigned int ImageDimension2 = ImageDimension;
#endif

  using ContinuousIndexType =
      ContinuousIndex< SpacePrecisionType, Self::ImageDimension >;

  using NeighborhoodType = Neighborhood< InputPixelType, Self::ImageDimension >;
  using OperatorNeighborhoodType = Neighborhood< TOutput, Self::ImageDimension >;

  using VectorType = Vector< TOutput, Self::ImageDimension >;
  using OutputType = typename Superclass::OutputType;
  using OperatorArrayType = FixedArray< OperatorNeighborhoodType, Self::ImageDimension >;

  using GaussianDerivativeSpatialFunctionType = GaussianDerivativeSpatialFunction< TOutput, 1 >;
  using GaussianDerivativeSpatialFunctionPointer = typename GaussianDerivativeSpatialFunctionType::Pointer;

#if !defined( ITK_LEGACY_REMOVE )
  using GaussianDerivativeFunctionType = GaussianDerivativeSpatialFunctionType;
  using GaussianDerivativeFunctionPointer = GaussianDerivativeSpatialFunctionPointer;
#endif

  /** Point type alias support */
  // using PointType = Point< TOutput, Self::ImageDimension >;
  using PointType = typename InputImageType::PointType;

  /** Evaluate the function at the specified point. */
  OutputType Evaluate(const PointType & point) const override;

  /** Evaluate the function at specified Index position. */
  OutputType EvaluateAtIndex(const IndexType & index) const override;

  /** Evaluate the function at specified ContinuousIndex position. */
  OutputType EvaluateAtContinuousIndex(
    const ContinuousIndexType & index) const override;

  /**
   * UseImageSpacing controls the extent of the computations.
   * Set UseImageSpacing to true to set the units to physical units of the image.
   * Set UseImageSpacing to false to set the units of pixels. */
  void SetUseImageSpacing(const bool val)
  {
    if(val != this->m_UseImageSpacing)
    {
      this->m_UseImageSpacing = val;
      this->RecomputeGaussianKernel();
    }
  }
  itkBooleanMacro(UseImageSpacing);
  itkGetMacro(UseImageSpacing,bool);

  /** The variance for the discrete Gaussian kernel. Sets the variance
   * independently for each dimension, but see also
   * SetVariance(const double v). The default is 0.0 in each dimension.
   * The extent of the kernel is controlled by UseImageSpacing.
   */
  void SetSigma(const double *sigma);

  void SetSigma(const double sigma);

  const double * GetSigma() const { return m_Sigma; }

  /** Set the extent of the discrete Gaussian kernel. */
  void SetExtent(const double *extent);

  void SetExtent(const double extent);

  const double * GetExtent() const { return m_Extent; }

  /** Set the input image.
   * \warning this method caches BufferedRegion information.
   * If the BufferedRegion has changed, user must call
   * SetInputImage again to update cached values. */
  void SetInputImage(const InputImageType *ptr) override;

protected:
  GaussianDerivativeImageFunction();
  ~GaussianDerivativeImageFunction() override = default;

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

  /** Recompute the Gaussian kernel used to evaluate indexes. This should use
   * a fastest Derivative Gaussian operator. */
  void RecomputeGaussianKernel();

private:

  double                            m_Sigma[ImageDimension];

  /** Array of 1D operators. Contains a derivative kernel for
   * each dimension. Note: A future version of ITK could extend this array
   * to include a Gaussian blurring kernel for each dimension.*/
  OperatorArrayType         m_OperatorArray;

  std::vector<Offset<ImageDimension>> m_ImageNeighborhoodOffsets[ImageDimension];

  double                            m_Extent[ImageDimension];

  /** Flag to indicate whether to use image spacing. */
  bool m_UseImageSpacing{ true };

  /** Neighborhood Image Function. */
  const GaussianDerivativeSpatialFunctionPointer m_GaussianDerivativeSpatialFunction;
};
} // namespace itk

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

#endif
