/*=========================================================================
 *
 *  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 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 travelling
 * 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
