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

#include "itkFastMarchingImageFilter.h"
#include "itkImage.h"

namespace itk
{
/**
 *\class FastMarchingUpwindGradientImageFilter
 *
 * \brief Generates the upwind gradient field of fast marching arrival times.
 *
 * This filter adds some extra functionality to its base class. While the
 * solution T(x) of the Eikonal equation is being generated by the base class
 * with the fast marching method, the filter generates the upwind gradient
 * vectors of T(x), storing them in an image.
 *
 * Since the Eikonal equation generates the arrival times of a wave traveling
 * at a given speed, the generated gradient vectors can be interpreted as the
 * slowness (1/velocity) vectors of the front (the quantity inside the modulus
 * operator in the Eikonal equation).
 *
 * Gradient vectors are computed using upwind finite differences, that is,
 * information only propagates from points where the wavefront has already
 * passed. This is consistent with how the fast marching method works.
 *
 * One more extra feature is the possibility to define a set of Target points
 * where the propagation stops. This can be used to avoid computing the Eikonal
 * solution for the whole domain.  The front can be stopped either when one
 * Target point is reached or all Target points are reached. The propagation
 * can stop after a time TargetOffset has passed since the stop condition is
 * met. This way the solution is computed a bit downstream the Target points,
 * so that the level sets of T(x) corresponding to the Target are smooth.
 *
 * For an alternative implementation, see itk::FastMarchingUpwindGradientImageFilterBase.
 *
 * \author Luca Antiga Ph.D.  Biomedical Technologies Laboratory,
 *                            Bioengineering Department, Mario Negri Institute, Italy.
 *
 * \ingroup ITKFastMarching
 */
template <typename TLevelSet, typename TSpeedImage = Image<float, TLevelSet ::ImageDimension>>
class ITK_TEMPLATE_EXPORT FastMarchingUpwindGradientImageFilter : public FastMarchingImageFilter<TLevelSet, TSpeedImage>
{
public:
  ITK_DISALLOW_COPY_AND_ASSIGN(FastMarchingUpwindGradientImageFilter);

  /** Standard class typdedefs. */
  using Self = FastMarchingUpwindGradientImageFilter;
  using Superclass = FastMarchingImageFilter<TLevelSet, TSpeedImage>;
  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(FastMarchingUpwindGradientImageFilter, FastMarchingImageFilter);

  /** Inherited type alias. */
  using LevelSetType = typename Superclass::LevelSetType;
  using SpeedImageType = typename Superclass::SpeedImageType;
  using LevelSetImageType = typename Superclass::LevelSetImageType;
  using LevelSetPointer = typename Superclass::LevelSetPointer;
  using SpeedImageConstPointer = typename Superclass::SpeedImageConstPointer;
  using LabelImageType = typename Superclass::LabelImageType;
  using PixelType = typename Superclass::PixelType;
  using AxisNodeType = typename Superclass::AxisNodeType;
  using NodeType = typename Superclass::NodeType;
  using NodeContainer = typename Superclass::NodeContainer;
  using NodeContainerPointer = typename Superclass::NodeContainerPointer;

  using IndexType = typename Superclass::IndexType;
  using OutputSpacingType = typename Superclass::OutputSpacingType;
  using LevelSetIndexType = typename Superclass::LevelSetIndexType;

  using PointType = typename Superclass::OutputPointType;

  /** The dimension of the level set. */
  static constexpr unsigned int SetDimension = Superclass::SetDimension;

  /** Set the container of Target Points.
   * If a target point is reached, the propagation stops.
   * Trial points are represented as a VectorContainer of LevelSetNodes. */
  void
  SetTargetPoints(NodeContainer * points)
  {
    m_TargetPoints = points;
    this->Modified();
  }

  /** Get the container of Target Points. */
  NodeContainerPointer
  GetTargetPoints()
  {
    return m_TargetPoints;
  }

  /** Get the container of Reached Target Points. */
  NodeContainerPointer
  GetReachedTargetPoints()
  {
    return m_ReachedTargetPoints;
  }

  /** GradientPixel type alias support */
  using GradientPixelType = CovariantVector<PixelType, Self::SetDimension>;

  /** GradientImage type alias support */
  using GradientImageType = Image<GradientPixelType, Self::SetDimension>;

  /** GradientImagePointer type alias support */
  using GradientImagePointer = typename GradientImageType::Pointer;

  /** Get the gradient image. */
  GradientImagePointer
  GetGradientImage() const
  {
    return m_GradientImage;
  }

  /** Set the GenerateGradientImage flag. Instrument the algorithm to generate
   * the gradient of the Eikonal equation solution while fast marching. */
  itkSetMacro(GenerateGradientImage, bool);

  /** Get the GenerateGradientImage flag. */
  itkGetConstReferenceMacro(GenerateGradientImage, bool);
  itkBooleanMacro(GenerateGradientImage);

  /** Set how long (in terms of arrival times) after targets are reached the
   * front must stop.  This is useful to ensure that the level set of target
   * arrival time is smooth. */
  itkSetMacro(TargetOffset, double);
  /** Get the TargetOffset ivar. */
  itkGetConstReferenceMacro(TargetOffset, double);

  /** Choose whether the front must stop when the first target has been reached
   * or all targets have been reached.
   */
  itkSetMacro(TargetReachedMode, int);
  itkGetConstReferenceMacro(TargetReachedMode, int);
  void
  SetTargetReachedModeToNoTargets()
  {
    this->SetTargetReachedMode(NoTargets);
  }
  void
  SetTargetReachedModeToOneTarget()
  {
    this->SetTargetReachedMode(OneTarget);
  }
  void
  SetTargetReachedModeToSomeTargets(SizeValueType numberOfTargets)
  {
    this->SetTargetReachedMode(SomeTargets);
    m_NumberOfTargets = numberOfTargets;
  }

  void
  SetTargetReachedModeToAllTargets()
  {
    this->SetTargetReachedMode(AllTargets);
  }

  /** Get the number of targets. */
  itkGetConstReferenceMacro(NumberOfTargets, SizeValueType);

  /** Get the arrival time corresponding to the last reached target.
   *  If TargetReachedMode is set to NoTargets, TargetValue contains
   *  the last (aka largest) Eikonal solution value generated.
   */
  itkGetConstReferenceMacro(TargetValue, double);

  enum
  {
    NoTargets,
    OneTarget,
    SomeTargets,
    AllTargets
  };

#ifdef ITK_USE_CONCEPT_CHECKING
  // Begin concept checking
  itkConceptMacro(LevelSetDoubleDivisionOperatorsCheck,
                  (Concept::DivisionOperators<typename TLevelSet::PixelType, double>));
  itkConceptMacro(LevelSetDoubleDivisionAndAssignOperatorsCheck,
                  (Concept::DivisionAndAssignOperators<typename TLevelSet::PixelType, double>));
  // End concept checking
#endif

protected:
  FastMarchingUpwindGradientImageFilter();
  ~FastMarchingUpwindGradientImageFilter() override = default;
  void
  PrintSelf(std::ostream & os, Indent indent) const override;

  void
  Initialize(LevelSetImageType *) override;

  void
  GenerateData() override;

  void
  UpdateNeighbors(const IndexType & index, const SpeedImageType *, LevelSetImageType *) override;

  virtual void
  ComputeGradient(const IndexType &         index,
                  const LevelSetImageType * output,
                  const LabelImageType *    labelImage,
                  GradientImageType *       gradientImage);

private:
  NodeContainerPointer m_TargetPoints;
  NodeContainerPointer m_ReachedTargetPoints;

  GradientImagePointer m_GradientImage;

  bool m_GenerateGradientImage;

  double m_TargetOffset;

  int m_TargetReachedMode;

  double m_TargetValue;

  SizeValueType m_NumberOfTargets;
};
} // namespace itk

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

#endif
