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

#include "itkNormalVectorFunctionBase.h"
#include "itkNumericTraits.h"
#include <cmath>

namespace itk
{
/**
 * \class NormalVectorDiffusionFunction
 *
 * \brief This class defines all the necessary functionality for performing
 * isotropic and anisotropic diffusion operations on vector neighborhoods from
 * a sparse image.
 *
 * \par
 * This class implements the actual computations for performing isotropic and
 * anisotropic diffusion operations on a neighborhood of unit length
 * vectors. Moreover, this processing is intrinsic to a manifold as specified
 * by the ManifoldNormal member variables in the nodes of the sparse image.
 *
 * \par
 * Since the only difference between isotropic and anisotropic diffusion is the
 * execution of 1 extra line of code, we have implemented both in this class
 * and made the choice between the two depend on a parameter (see below).

 * \par PARAMETERS
 * The choice between is isotropic/anisotropic diffusion is made by the
 * parameter NormalProcessType. A value of 0 corresponds to isotropic diffusion
 * whereas a value of 1 corresponds to anisotropic diffusion. If anisotropic
 * diffusion is chosen, the parameter ConductanceParameter should be set. This
 * conductance parameter determines the level of feature preservation.
 *
 * \par IMPORTANT
 * This class works on SparseImage neighborhoods. Before using this class
 * please read the documentation for SparseImage. Also the documentation for
 * ImplicitManifoldNormalVectorField class will be helpful in understanding how
 * to use this class as a function object.
 * \ingroup ITKLevelSets
 */
template <typename TSparseImageType>
class ITK_TEMPLATE_EXPORT NormalVectorDiffusionFunction : public NormalVectorFunctionBase<TSparseImageType>
{
public:
  ITK_DISALLOW_COPY_AND_MOVE(NormalVectorDiffusionFunction);

  /** Standard class type alias. */
  using Self = NormalVectorDiffusionFunction;
  using Superclass = NormalVectorFunctionBase<TSparseImageType>;
  using Pointer = SmartPointer<Self>;
  using ConstPointer = SmartPointer<const Self>;

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

  /** Image dimension derived from the superclass. */
  static constexpr unsigned int ImageDimension = Superclass::ImageDimension;

  /** Standard New macro. */
  itkNewMacro(Self);

  /** Typedefs from the superclass. */
  using typename Superclass::TimeStepType;
  using typename Superclass::RadiusType;
  using typename Superclass::NeighborhoodType;
  using typename Superclass::NeighborhoodScalesType;
  using typename Superclass::FloatOffsetType;
  using typename Superclass::IndexType;
  using typename Superclass::SparseImageType;
  using typename Superclass::NodeType;
  using typename Superclass::NodeValueType;
  using typename Superclass::NormalVectorType;

  /** This method is used to choose between isotropic/anisotropic filtering. A
      parameter value of 0 indicates isotropic diffusion and is the
      default. Parameter value 1 is anisotropic diffusion. When using
      anisotropic diffusion the conductance parameter should also be set. */
  void
  SetNormalProcessType(int npt)
  {
    m_NormalProcessType = npt;
  }

  /** This method returns the isotropic/anisotropic filtering parameter. */
  [[nodiscard]] int
  GetNormalProcessType() const
  {
    return m_NormalProcessType;
  }

  /** This method sets the conductance parameter used in anisotropic
   * filtering. Useful values for processing 2D and 3D shapes are between
   *  0.1 and 0.25. Lower values preserve more shape features, higher values
   *  smooth more. As the conductance parameter large, the processing becomes
   *  isotropic. Default is 0. */
  void
  SetConductanceParameter(NodeValueType cp)
  {
    m_ConductanceParameter = cp + static_cast<NodeValueType>(0.001);
    // we add a minimum conductance to avoid divide by zero
    // can make this a parameter.
    m_FluxStopConstant = static_cast<NodeValueType>(-1.0 / (m_ConductanceParameter * m_ConductanceParameter));
  }

  /** This method returns the conductance parameter. */
  NodeValueType
  GetConductanceParameter() const
  {
    return m_ConductanceParameter;
  }

  /** This method returns the internal variable FluxStopConstant. */
  NodeValueType
  GetFluxStopConstant() const
  {
    return m_FluxStopConstant;
  }

  /** This function is called from LevelSetNormalImageFilter for all of the
   *  nodes to compute and store the flux vectors (first derivatives of the
   *  normal vectors. ComputeUpdateNormal then takes derivatives of the flux
   *  vectors. This way we avoid repeating the same flux computations. */
  void
  PrecomputeSparseUpdate(NeighborhoodType & it) const override;

  /** The actual update rule for the normal vectors. */
  NormalVectorType
  ComputeSparseUpdate(NeighborhoodType & it, void * globalData, const FloatOffsetType & offset) const override;

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

  /** The method called in anisotropic diffusion to inhibit diffusion across
      areas with large curvature. */
  NodeValueType
  FluxStopFunction(const NodeValueType v) const
  {
    // the slow exp function could be replaced with a lookup table
    if (v <= 0.0)
    {
      return NumericTraits<NodeValueType>::OneValue();
    }

    return static_cast<NodeValueType>(std::exp(m_FluxStopConstant * v));
  }

private:
  /** The conductance parameter used for anisotropic diffusion. */
  NodeValueType m_ConductanceParameter{};

  /** The internal variable used in the FluxStopFunction. It is computed from
   * ConductanceParameter. */
  NodeValueType m_FluxStopConstant{};

  /** The isotropic/anisotropic filtering choice parameter. */
  int m_NormalProcessType{};
};
} // end namespace itk

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

#endif
