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

#include "itkBooleanStdVector.h"
#include "itkFiniteDifferenceImageFilter.h"
#include "itkMultiThreaderBase.h"

namespace itk
{
/**
 * \class DenseFiniteDifferenceImageFilter
 *
 * This filter implements a layer of the finite difference solver hierarchy that
 * performs "dense" iteration, ie. iteration over all pixels in the input and
 * output at each change calculation and update step. Dense iteration is in
 * contrast to a "sparse" iteration over a subset of the pixels.  See
 * documentation for FiniteDifferenceImageFilter for an overview of the
 * iterative finite difference algorithm:
 *
 * \par
 * \f$u_{\mathbf{i}}^{n+1}=u^n_{\mathbf{i}}+\Delta u^n_{\mathbf{i}}\Delta t\f$
 *
 * \par
 * The generic code for performing iterations and updates at each time
 * step is inherited from the parent class.  This class defines an update
 * buffer for \f$ \Delta \f$ and the methods CalculateChange() and
 * ApplyUpdate(). These methods are designed to automatically thread their
 * execution.  \f$ \Delta \f$ is defined as an image of identical size and type
 * as the output image.
 *
 * \par
 * As we descend through each layer in the hierarchy, we know more and more
 * about the specific application of our filter.  At this level, we
 * have committed to iteration over each pixel in an image. We take advantage
 * of that knowledge to multithread the iteration and update methods.
 *
 * \par Inputs and Outputs
 * This is an image to image filter.  The specific types of the images are not
 * fixed at this level in the hierarchy.
 *
 * \par How to use this class
 * This filter is only one layer in a branch the finite difference solver
 * hierarchy.  It does not define the function used in the CalculateChange() and
 * it does not define the stopping criteria (Halt method).  To use this class,
 * subclass it to a specific instance that supplies a function and Halt()
 * method.
 *
 * \ingroup ImageFilters
 * \sa FiniteDifferenceImageFilter
 * \ingroup ITKFiniteDifference
 */
template <typename TInputImage, typename TOutputImage>
class ITK_TEMPLATE_EXPORT DenseFiniteDifferenceImageFilter
  : public FiniteDifferenceImageFilter<TInputImage, TOutputImage>
{
public:
  ITK_DISALLOW_COPY_AND_MOVE(DenseFiniteDifferenceImageFilter);

  /** Standard class type aliases */
  using Self = DenseFiniteDifferenceImageFilter;
  using Superclass = FiniteDifferenceImageFilter<TInputImage, TOutputImage>;

  using Pointer = SmartPointer<Self>;
  using ConstPointer = SmartPointer<const Self>;

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

  /** Convenient type alias */
  using typename Superclass::InputImageType;
  using typename Superclass::OutputImageType;
  using typename Superclass::FiniteDifferenceFunctionType;

  /** Dimensionality of input and output data is assumed to be the same.
   * It is inherited from the superclass. */
  static constexpr unsigned int ImageDimension = Superclass::ImageDimension;

  /** The pixel type of the output image will be used in computations.
   * Inherited from the superclass. */
  using typename Superclass::PixelType;

  /** The value type of a time step.  Inherited from the superclass. */
  using typename Superclass::TimeStepType;

  /** The container type for the update buffer. */
  using UpdateBufferType = OutputImageType;

  itkConceptMacro(OutputTimesDoubleCheck, (Concept::MultiplyOperator<PixelType, double>));
  itkConceptMacro(OutputAdditiveOperatorsCheck, (Concept::AdditiveOperators<PixelType>));
  itkConceptMacro(OutputAdditiveAndAssignOperatorsCheck, (Concept::AdditiveAndAssignOperators<PixelType>));
  itkConceptMacro(InputConvertibleToOutputCheck, (Concept::Convertible<typename TInputImage::PixelType, PixelType>));

protected:
  DenseFiniteDifferenceImageFilter()
    : m_UpdateBuffer(UpdateBufferType::New())
  {}
  ~DenseFiniteDifferenceImageFilter() override = default;
  void
  PrintSelf(std::ostream & os, Indent indent) const override;

  /** A simple method to copy the data from the input to the output.  ( Supports
   * "read-only" image adaptors in the case where the input image type converts
   * to a different output image type. )  */
  void
  CopyInputToOutput() override;

  /** This method applies changes from the m_UpdateBuffer to the output using
   * the ThreadedApplyUpdate() method and a multithreading mechanism.  "dt" is
   * the time step to use for the update of each pixel. */
  void
  ApplyUpdate(const TimeStepType & dt) override;

  /** Method to allow subclasses to get direct access to the update
   * buffer */
  virtual UpdateBufferType *
  GetUpdateBuffer()
  {
    return m_UpdateBuffer;
  }

  /** This method populates an update buffer with changes for each pixel in the
   * output using the ThreadedCalculateChange() method and a multithreading
   * mechanism. Returns value is a time step to be used for the update. */
  TimeStepType
  CalculateChange() override;

  /** This method allocates storage in m_UpdateBuffer.  It is called from
   * Superclass::GenerateData(). */
  void
  AllocateUpdateBuffer() override;

  /** The type of region used for multithreading */
  using ThreadRegionType = typename UpdateBufferType::RegionType;

  /**  Does the actual work of updating the output from the UpdateContainer over
   *  an output region supplied by the multithreading mechanism.
   *  \sa ApplyUpdate
   *  \sa ApplyUpdateThreaderCallback */
  virtual void
  ThreadedApplyUpdate(const TimeStepType & dt, const ThreadRegionType & regionToProcess, ThreadIdType threadId);

  /** Does the actual work of calculating change over a region supplied by
   * the multithreading mechanism.
   * \sa CalculateChange
   * \sa CalculateChangeThreaderCallback */
  virtual TimeStepType
  ThreadedCalculateChange(const ThreadRegionType & regionToProcess, ThreadIdType threadId);

private:
  /** Structure for passing information into static callback methods.  Used in
   * the subclasses' threading mechanisms. */
  struct DenseFDThreadStruct
  {
    DenseFiniteDifferenceImageFilter * Filter;
    TimeStepType                       TimeStep;
    std::vector<TimeStepType>          TimeStepList;

    BooleanStdVectorType ValidTimeStepList;
  };

  /** This callback method uses ImageSource::SplitRequestedRegion to acquire an
   * output region that it passes to ThreadedApplyUpdate for processing. */
  static ITK_THREAD_RETURN_FUNCTION_CALL_CONVENTION
  ApplyUpdateThreaderCallback(void * arg);

  /** This callback method uses SplitUpdateContainer to acquire a region
   * which it then passes to ThreadedCalculateChange for processing. */
  static ITK_THREAD_RETURN_FUNCTION_CALL_CONVENTION
  CalculateChangeThreaderCallback(void * arg);

  /** The buffer that holds the updates for an iteration of the algorithm. */
  typename UpdateBufferType::Pointer m_UpdateBuffer{};
};
} // end namespace itk

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

#endif
