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

#include "itkMultipleValuedNonLinearOptimizer.h"
#include "itkCumulativeGaussianCostFunction.h"
#include "ITKOptimizersExport.h"

namespace itk
{
/** \class CumulativeGaussianOptimizer
 * \brief This is an optimizer specific to estimating
 * the parameters of Cumulative Gaussian sampled data.
 *
 * This optimizer will only work if the data array is
 * sampled from a Cumulative Gaussian curve. It's more
 * of a curve fitter than an optimizer, with the
 * advantage of being fast and specific. It works by
 * taking the derivative of the Cumulative Gaussian sample
 * then repeatedly extending the tails of the Gaussian
 * and recalculating the Gaussian parameters until
 * the change in iterations is within tolerance or very small.
 * The Gaussian is then integrated to reproduce the
 * Cumulative Gaussian and the asymptotes are estimated
 * by using least squares fit to estimate the constant
 * from integration.
 *
 * \ingroup Numerics Optimizers
 * \ingroup ITKOptimizers
 */

class ITKOptimizers_EXPORT CumulativeGaussianOptimizer : public MultipleValuedNonLinearOptimizer
{
public:
  /** Standard type alias. */
  using Self = CumulativeGaussianOptimizer;
  using Superclass = MultipleValuedNonLinearOptimizer;
  using Pointer = SmartPointer<Self>;
  using ConstPointer = SmartPointer<const Self>;

  /** Cost function type alias. NOTE: This optimizer is specific to fitting a
    Cumulative Gaussian. */
  using CostFunctionType = CumulativeGaussianCostFunction;

  /** Data array type alias. */
  using MeasureType = CostFunctionType::MeasureType;

  /** Method for creation through the object factory. */
  itkNewMacro(Self);

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

  /** Set and get macros. */
  itkSetMacro(DifferenceTolerance, double);
  itkGetMacro(DifferenceTolerance, double);
  itkSetMacro(Verbose, bool);
  itkGetMacro(Verbose, bool);
  itkBooleanMacro(Verbose);
  itkGetMacro(ComputedMean, double);
  itkGetMacro(ComputedStandardDeviation, double);
  itkGetMacro(UpperAsymptote, double);
  itkGetMacro(LowerAsymptote, double);
  itkGetMacro(FinalSampledArray, MeasureType *);
  itkGetMacro(FitError, double);

  void
  SetDataArray(MeasureType * cumGaussianArray);

  /** Start the optimizer. */
  void
  StartOptimization() override;

  /** Print an array. */
  void
  PrintArray(MeasureType * array);

  /** Report the reason for stopping. */
  const std::string
  GetStopConditionDescription() const override;

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

private:
  /** When to stop the iteration for the Gaussian extension loop. */
  double m_DifferenceTolerance{};

  /** The final mean of the Cumulative Gaussian. */
  double m_ComputedMean{};

  /** The final standard deviation of the Cumulative Gaussian. */
  double m_ComputedStandardDeviation{};

  /** The final amplitude of the Gaussian. */
  double m_ComputedAmplitude{};

  /** The transition height (distance between upper and lower
   * asymptotes) of the Cumulative Gaussian. */
  double m_ComputedTransitionHeight{};

  /** The final upper asymptote of the Cumulative Gaussian. */
  double m_UpperAsymptote{};

  /** The final lower asymptote of the Cumulative Gaussian. */
  double m_LowerAsymptote{};

  /** Offset for the mean calculation. */
  double m_OffsetForMean{};

  /** Flag to print iteration results. */
  bool m_Verbose{};

  /** Least squares fit error as a measure of goodness. */
  double m_FitError{};

  /** Array of values computed from the final parameters of the
   * Cumulative Gaussian. */
  MeasureType * m_FinalSampledArray{};

  /** Original data array. */
  MeasureType * m_CumulativeGaussianArray{};

  /** Extend the tails of the Gaussian. */
  MeasureType *
  ExtendGaussian(MeasureType * originalArray, MeasureType * extendedArray, int startingPointForInsertion);

  /** Recalculate the parameters of the extended Gaussian array. */
  MeasureType *
  RecalculateExtendedArrayFromGaussianParameters(MeasureType * originalArray,
                                                 MeasureType * extendedArray,
                                                 int           startingPointForInsertion) const;

  /** Calculates the squared difference error between each Gaussian
   * iteration loop. */
  double
  FindAverageSumOfSquaredDifferences(MeasureType * array1, MeasureType * array2);

  /** Given an array sampled from a Gaussian, compute the final parameters. */
  void
  FindParametersOfGaussian(MeasureType * sampledGaussianArray);

  /** Measure the parameters of a Gaussian sampled array. */
  void
  MeasureGaussianParameters(MeasureType * array);

  /** Print the header for output table. */
  void
  PrintComputedParameterHeader();

  /** Print the computed parameters. */
  void
  PrintComputedParameters() const;

  /** Find the constant of the integrated sample. */
  double
  VerticalBestShift(MeasureType * originalArray, MeasureType * newArray);

  /** Describe the stop condition */
  std::ostringstream m_StopConditionDescription{};
};
} // end namespace itk

#endif
