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

#include "itkGPUInPlaceImageFilter.h"
#include "itkGPUFiniteDifferenceFunction.h"
#include "itkFiniteDifferenceImageFilter.h"
#include "itkTimeProbe.h"

namespace itk
{
/**
 * \class GPUFiniteDifferenceImageFilter
 *
 * \brief Base class for GPU Finite Difference Image Filters.
 *
 * \ingroup ITKGPUFiniteDifference
 * \sa GPUDenseFiniteDifferenceImageFilter */
template< typename TInputImage, typename TOutputImage, typename TParentImageFilter =
            FiniteDifferenceImageFilter< TInputImage, TOutputImage > >
class ITK_TEMPLATE_EXPORT GPUFiniteDifferenceImageFilter :
  public GPUInPlaceImageFilter< TInputImage, TOutputImage, TParentImageFilter >
{
public:
  ITK_DISALLOW_COPY_AND_ASSIGN(GPUFiniteDifferenceImageFilter);

  /** Standard class type aliases. */
  using Self = GPUFiniteDifferenceImageFilter;
  using GPUSuperclass = GPUInPlaceImageFilter< TInputImage, TOutputImage, TParentImageFilter >;
  using CPUSuperclass = TParentImageFilter;
  using Pointer = SmartPointer< Self >;
  using ConstPointer = SmartPointer< const Self >;

  /** Run-time type information (and related methods) */
  itkTypeMacro(GPUFiniteDifferenceImageFilter, GPUInPlaceImageFilter);

  /** Input and output image types. */
  using InputImageType = TInputImage;
  using OutputImageType = TOutputImage;

  /** Dimensionality of input and output data is assumed to be the same. */
  static constexpr unsigned int ImageDimension = OutputImageType::ImageDimension;

  /** The pixel type of the output image will be used in computations. */
  using OutputPixelType = typename TOutputImage::PixelType;
  using InputPixelType = typename TInputImage::PixelType;
  using PixelType = OutputPixelType;

  /** Extract value type in case the pixel is of vector type */
  using OutputPixelValueType = typename NumericTraits< OutputPixelType >::ValueType;
  using InputPixelValueType = typename NumericTraits< InputPixelType >::ValueType;

  /** The value type of the time step.  This is distinct from PixelType
   * because PixelType may often be a vector value, while the TimeStep is
   * a scalar value. */
  using FiniteDifferenceFunctionType = typename GPUFiniteDifferenceFunction< TOutputImage >::DifferenceFunctionType;
  using TimeStepType = typename FiniteDifferenceFunctionType::TimeStepType;
  using RadiusType = typename FiniteDifferenceFunctionType::RadiusType;
  using NeighborhoodScalesType = typename FiniteDifferenceFunctionType::NeighborhoodScalesType;

/** This method returns a pointer to a FiniteDifferenceFunction object that
  * will be used by the filter to calculate updates at image pixels.
  * \returns A FiniteDifferenceObject pointer. */
  const typename FiniteDifferenceFunctionType::Pointer &GetDifferenceFunction() const override
    {
    return this->m_DifferenceFunction;
    }

 /** This method sets the pointer to a FiniteDifferenceFunction object that
  * will be used by the filter to calculate updates at image pixels.
  * \returns A FiniteDifferenceObject pointer. */
  void SetDifferenceFunction ( FiniteDifferenceFunctionType *differenceFunction ) override
    {
    itkDebugMacro("setting m_DifferenceFunction to " << differenceFunction);
    if ( this->m_DifferenceFunction != differenceFunction )
      {
      this->m_DifferenceFunction = differenceFunction;
      this->Modified();
      }
    }

  typedef enum { UNINITIALIZED = 0, INITIALIZED = 1 } FilterStateType;

  /** Set the state of the filter to INITIALIZED */
  void SetStateToInitialized()
  {
    this->SetState(INITIALIZED);
  }

  /** Set the state of the filter to UNINITIALIZED */
  void SetStateToUninitialized()
  {
    this->SetState(UNINITIALIZED);
  }

  /** Set/Get the state of the filter. */
#if !defined( ITK_WRAPPING_PARSER )
  itkSetMacro(State, FilterStateType);
  itkGetConstReferenceMacro(State, FilterStateType);
#endif

#ifdef ITK_USE_CONCEPT_CHECKING
  // Begin concept checking
  itkConceptMacro( OutputPixelIsFloatingPointCheck,
                   ( Concept::IsFloatingPoint< OutputPixelValueType > ) );
  // End concept checking
#endif

  /** Methods to get timers */
  itkGetConstReferenceMacro(InitTime,           TimeProbe);
  itkGetConstReferenceMacro(ComputeUpdateTime,  TimeProbe);
  itkGetConstReferenceMacro(ApplyUpdateTime,    TimeProbe);
  itkGetConstReferenceMacro(SmoothFieldTime,    TimeProbe);

protected:
  GPUFiniteDifferenceImageFilter();
  ~GPUFiniteDifferenceImageFilter() override;

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

  /** This method allocates a temporary update container in the subclass. */
  void AllocateUpdateBuffer() override = 0;

  /** This method is defined by a subclass to apply changes to the output
   * from an update buffer and a time step value "dt".
   * \param dt Time step value. */
  virtual void GPUApplyUpdate(const TimeStepType& dt) = 0;

  /** This method is defined by a subclass to populate an update buffer
   * with changes for the pixels in the output.  It returns a time
   * step value to be used for the update.
   * \returns A time step to use in updating the output with the changes
   * calculated from this method. */
  virtual TimeStepType GPUCalculateChange() = 0;

  /** This method can be defined in subclasses as needed to copy the input
   * to the output. See DenseFiniteDifferenceImageFilter for an
   * implementation. */
  void CopyInputToOutput() override = 0;

  /** This is the default, high-level algorithm for calculating finite
   * difference solutions.  It calls virtual methods in its subclasses
   * to implement the major steps of the algorithm. */
  void GPUGenerateData() override;

  /** FiniteDifferenceImageFilter needs a larger input requested region than
   * the output requested region.  As such, we need to provide
   * an implementation for GenerateInputRequestedRegion() in order to inform
   * the pipeline execution model.
   *
   * \par
   * The filter will ask for a padded region to perform its neighborhood
   * calculations.  If no such region is available, the boundaries will be
   * handled as described in the FiniteDifferenceFunction defined by the
   * subclass.
   * \sa ProcessObject::GenerateInputRequestedRegion() */
  void GenerateInputRequestedRegion() override;

  /** This method returns true when the current iterative solution of the
   * equation has met the criteria to stop solving.  Defined by a subclass. */
  bool Halt() override;

  /** This method is similar to Halt(), and its default implementation in this
   * class is simply to call Halt(). However, this method takes as a parameter
   * a void pointer to the MultiThreaderBase::WorkUnitInfo structure. If you
   * override this method instead of overriding Halt, you will be able to get
   * the current thread ID and handle the Halt method accordingly. This is useful
   * if you are doing a lot of processing in Halt that you don't want parallelized.
   * Notice that ThreadedHalt is only called by the multithreaded filters, so you
   * still should implement Halt, just in case a non-threaded filter is used.
   */
  bool ThreadedHalt( void *itkNotUsed(threadInfo) ) override {
    return this->Halt();
  }

  /** This method is optionally defined by a subclass and is called before
   * the loop of iterations of calculate_change & upate. It does the global
   * initialization, i.e. in the SparseFieldLevelSetImageFilter, initialize
   * the list of layers.
   */
  void Initialize() override {
  }

  /** This method is optionally defined by a subclass and is called immediately
   * prior to each iterative CalculateChange-ApplyUpdate cycle.  It can be
   * used to set global variables needed for the next iteration (ie. average
   * gradient magnitude of the image in anisotropic diffusion functions), or
   * otherwise prepare for the next iteration.
   */
  void InitializeIteration() override
  {
    m_DifferenceFunction->InitializeIteration();
  }

  /** Virtual method for resolving a single time step from a set of time steps
   * returned from processing threads.
   * \return Time step (dt) for the iteration update based on a list
   * of time steps generated from the threaded calculated change method (one
   * for each region processed).
   *
   * \param timeStepList The set of time changes compiled from all the threaded calls
   * to ThreadedGenerateData.
   * \param valid The set of flags indicating which of "list" elements are
   *  valid
   *
   * The default is to return the minimum value in the list. */
  virtual TimeStepType ResolveTimeStep(const std::vector<TimeStepType >& timeStepList,
                                     const std::vector< bool >& valid) const override;

  /** This method is called after the solution has been generated to allow
   * subclasses to apply some further processing to the output. */
  void PostProcessOutput()  override {
  }

  /** The maximum number of iterations this filter will run */

  //unsigned int m_NumberOfIterations;

  /** A counter for keeping track of the number of elapsed
      iterations during filtering. */
  //unsigned int m_ElapsedIterations;

  /** Indicates whether the filter automatically resets to UNINITIALIZED state
      after completing, or whether filter must be manually reset */
  bool m_ManualReinitialization;

  double m_RMSChange;
  double m_MaximumRMSError;

  /** Timers for statistics */
  TimeProbe m_InitTime, m_ComputeUpdateTime, m_ApplyUpdateTime, m_SmoothFieldTime;

private:

  /** Initialize the values of the Function coefficients. This function will
   * also take care of checking whether the image spacing should be taken into
   * account or not. */
  void InitializeFunctionCoefficients();

  /** The function that will be used in calculating updates for each pixel. */
  typename FiniteDifferenceFunctionType::Pointer m_DifferenceFunction;

  /** Control whether derivatives use spacing of the input image in
      its calculation. */
  bool m_UseImageSpacing;

  /** State that the filter is in, i.e. UNINITIALIZED or INITIALIZED */
  FilterStateType m_State;
};
} // end namespace itk

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

#endif
