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

#include "itkShapeSignedDistanceFunction.h"
#include "itkImage.h"
#include "itkInterpolateImageFunction.h"
#include "itkExtrapolateImageFunction.h"
#include "itkTransform.h"

namespace itk
{
/** \class PCAShapeSignedDistanceFunction
 * \brief Compute the signed distance from a N-dimensional PCA Shape.
 *
 * This class computes the signed distance from a N-dimensional shape defined
 * by:
 * (1) a mean signed distance image \f$ M(x) \f$,
 * (2) the first \f$ q \f$ principal components images
 * \f$ P_i(x) \f$ and
 * (3) a transform \f$ T(x) \f$ to define the pose
 * (i.e. position or orientation of the shape).
 *
 * A particular instance of the shape is defined by a set of parameters \f$ p \f$.
 * The first \f$ q \f$ parameters defines the weights applied to each principal components
 * and the remaining parameters is used to define the transform. The user
 * should refer to the documentation of the particular Transform class being used.
 * The first set of parameters are called the ShapeParameters and the remaining
 * parameters the PoseParameters.
 *
 * The method Evaluate( point x ) returns the approximate signed to the
 * shape at point x such that:
 *
 * \f[ s = M(T(x)) + \sum_i^{q} p[i] * \sigma[i] * P_i(T(x)) \f]
 *
 * Where \f$\sigma[i]\f$ are the square root of the eigenvalues. These are defined using
 * method SetPrincipalComponentStandardDeviations().
 *
 * This class is templated over the coordinate representation type
 * (e.g. float or double) and the space dimension.
 *
 * \sa ShapeSignedDistanceFunction
 * \sa Transform
 * \ingroup ImageFunctions
 *
 *
 * \ingroup ITKSignedDistanceFunction
 */
template< typename TCoordRep,
          unsigned int VSpaceDimension,
          typename TImage = Image< double, VSpaceDimension > >
class ITK_TEMPLATE_EXPORT PCAShapeSignedDistanceFunction:
  public ShapeSignedDistanceFunction< TCoordRep, VSpaceDimension >
{
public:
  ITK_DISALLOW_COPY_AND_ASSIGN(PCAShapeSignedDistanceFunction);

  /** Standard class type aliases. */
  using Self = PCAShapeSignedDistanceFunction;
  using Superclass = ShapeSignedDistanceFunction<
    TCoordRep, VSpaceDimension >;

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

  /** Run-time type information (and related methods). */
  itkTypeMacro(PCAShapeSignedDistanceFunction, ShapeSignedDistancFunction);

  /** New macro for creation of through the object factory. */
  itkNewMacro(Self);

  /** Dimension underlying input image. */
  static constexpr unsigned int SpaceDimension = Superclass::SpaceDimension;

  /** CoordRep type alias support */
  using CoordRepType = typename Superclass::CoordRepType;

  /** InputeType type alias support */
  using InputType = typename Superclass::InputType;

  /** OutputType type alias support */
  using OutputType = typename Superclass::OutputType;

  /** Point type alias support */
  using PointType = typename Superclass::PointType;

  /** Parameters type alias support */
  using ParametersType = typename Superclass::ParametersType;

  /** Image type alias support */
  using ImageType = TImage;
  using ImagePointer = typename ImageType::Pointer;
  using ImagePointerVector = std::vector< ImagePointer >;

  /** Transform type alias support */
  using TransformType = Transform< CoordRepType,
                     Self::SpaceDimension,
                     Self::SpaceDimension >;

  /** Interpolator type alias support */
  using InterpolatorType = InterpolateImageFunction< ImageType, CoordRepType >;
  using InterpolatorPointer = typename InterpolatorType::Pointer;
  using InterpolatorPointerVector = std::vector< InterpolatorPointer >;

  /** extrapolator type alias support */
  using ExtrapolatorType = ExtrapolateImageFunction< ImageType, CoordRepType >;
  using ExtrapolatorPointer = typename ExtrapolatorType::Pointer;
  using ExtrapolatorPointerVector = std::vector< ExtrapolatorPointer >;

  /** function type alias support */
  using FunctionType = ImageFunction< ImageType, double, CoordRepType >;
  using FunctionPointer = typename FunctionType::Pointer;
  using FunctionPointerVector = std::vector< FunctionPointer >;

  /** Set/Get the number of principal components
   * SetNumberOfPrincipalComponents must be called before SetParameters */
  void SetNumberOfPrincipalComponents(unsigned int n);

  itkGetConstMacro(NumberOfPrincipalComponents, unsigned int);

  /** Set/Get the mean image. */
  itkSetObjectMacro(MeanImage, ImageType);
  itkGetModifiableObjectMacro(MeanImage, ImageType);

  /** Set/Get the principal component images. */
  void SetPrincipalComponentImages(ImagePointerVector v)
  { m_PrincipalComponentImages = v; }
//  ImagePointerVector & GetPrincipalComponentImages()
//    { return m_PrincipalComponentImages; }

  /** Set/Get the principal component standard deviations. These values corresponds
   * to the square root of the eigenvalues of the principal components. */
  itkSetMacro(PrincipalComponentStandardDeviations, ParametersType);
  itkGetConstMacro(PrincipalComponentStandardDeviations, ParametersType);

  /** Set/Get transform. */
  itkSetObjectMacro(Transform, TransformType);
  itkGetModifiableObjectMacro(Transform, TransformType);

  /** A PCAShape is defined by a set of shape and pose parameters. */
  void SetParameters(const ParametersType &) override;

  unsigned int GetNumberOfShapeParameters() const override
  { return m_NumberOfPrincipalComponents; }
  unsigned int GetNumberOfPoseParameters() const override
  { return m_Transform ? m_Transform->GetNumberOfParameters() : 0; }

  /** Evaluate the signed distance from a shape at a given position. */
  OutputType Evaluate(const PointType & point) const override;

  /** Initialize must be called before the first call of
   Evaluate() to allow the class to validate any inputs. */
  void Initialize() override;

protected:
  PCAShapeSignedDistanceFunction();
  ~PCAShapeSignedDistanceFunction() override = default;

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

private:
  /** intrinsic data members */
  unsigned int m_NumberOfPrincipalComponents;
  unsigned int m_NumberOfTransformParameters;

  ImagePointer       m_MeanImage;
  ImagePointerVector m_PrincipalComponentImages;
  ParametersType     m_PrincipalComponentStandardDeviations;

  /** transform and interpolator/extrapolator for image interpolation */
  typename TransformType::Pointer m_Transform;

  InterpolatorPointerVector     m_Interpolators;
  ExtrapolatorPointerVector     m_Extrapolators;

  /** shape and pose parameters */
  ParametersType m_WeightOfPrincipalComponents;
  ParametersType m_TransformParameters;
};
} // end namespace itk

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

#endif
