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

#include "itkSingleValuedNonLinearVnlOptimizerv4.h"
#include "vnl/algo/vnl_lbfgs.h"
#include "vnl/algo/vnl_lbfgsb.h"
#include <memory>
#include "ITKOptimizersv4Export.h"

namespace itk
{
/* Necessary forward declaration see below for definition */
/**
 * \class LBFGSOptimizerBaseHelperv4
 * \brief Wrapper helper around vnl optimizer.
 *
 * This class is used to translate iteration events, etc, from
 * vnl_lbfgsb into iteration events in ITK.
 *
 * \ingroup ITKOptimizersv4
 */
// Forward reference because of circular dependencies
template <typename TInternalVnlOptimizerType>
class ITK_TEMPLATE_EXPORT LBFGSOptimizerBaseHelperv4;

/**
 * \class LBFGSOptimizerBasev4
 * \brief Abstract base for vnl lbfgs algorithm optimizers in ITKv4 registration framework.
 *
 * \note The StopConditionDescription returned by this class is directly from the vnl
 * optimizer by calling <tt> m_VnlOptimizer->get_failure_code() </tt>. This seems to
 * return "Failure" even when no error has occurred. The same behavior is observed
 * in the ITKv3 version of this optimizer.
 *
 * \note Local-support (high-density) transforms.
 * Local-support transforms are not supported. To add support for these,
 * the class must be modified thusly:
 *
 * \note 1) Parameter updates:
 * In SingleValuedNonLinearCostFunctionAdaptor, the handling of the gradient
 * must be changed to accommodate the fact that local-support transforms expect
 * a gradient to be added to the transform parameters using the
 * UpdateTransformParameters method of the local support transform. Other optimizers
 * in the v4 framework use this method, but the use of the vnl optimizers here
 * complicates it.
 *
 * \note 2) Efficiency
 * To work efficiently with local-support transforms, this class should be
 * modified to use a single parameter object to avoid the multiple
 * parameter copies that are currently performed. It should work to use
 * the transform parameters pointer.
 *
 * This code has been adapted for the ITKv4 registration framework from the
 * v3 version, itkLBFGSOptimizer.
 *
 * \ingroup ITKOptimizersv4
 */
template <typename TInternalVnlOptimizerType>
class ITK_TEMPLATE_EXPORT LBFGSOptimizerBasev4 : public SingleValuedNonLinearVnlOptimizerv4
{
public:
  ITK_DISALLOW_COPY_AND_MOVE(LBFGSOptimizerBasev4);

  /** Standard "Self" type alias. */
  using Self = LBFGSOptimizerBasev4;
  using Superclass = SingleValuedNonLinearVnlOptimizerv4;
  using Pointer = SmartPointer<Self>;
  using ConstPointer = SmartPointer<const Self>;

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

  using MetricType = Superclass::MetricType;
  using ParametersType = Superclass::ParametersType;
  using ScalesType = Superclass::ScalesType;

  /** Stop condition return string type */
  using StopConditionReturnStringType = Superclass::StopConditionReturnStringType;

  /** Stop condition internal string type */
  using StopConditionDescriptionType = Superclass::StopConditionDescriptionType;

  /** The vnl optimizer */
  using InternalOptimizerType = LBFGSOptimizerBaseHelperv4<TInternalVnlOptimizerType>;

  /** Method for getting access to the internal optimizer. */
  InternalOptimizerType *
  GetOptimizer();

  /** Start optimization with an initial value. */
  void
  StartOptimization(bool doOnlyInitialization = false) override;

  /** Plug in a Cost Function into the optimizer  */
  void
  SetMetric(MetricType * metric) override;

  /** Set/Get the optimizer trace flag. If set to true, the optimizer
   * prints out information every iteration.
   */
  virtual void
  SetTrace(bool flag);

  itkGetConstMacro(Trace, bool);
  itkBooleanMacro(Trace);

  /** Set/Get the maximum number of function evaluations allowed. */
  virtual void
  SetMaximumNumberOfFunctionEvaluations(unsigned int n);

  itkGetConstMacro(MaximumNumberOfFunctionEvaluations, unsigned int);

  /** Set/Get the gradient convergence tolerance. This is a positive
   * real number that determines the accuracy with which the solution is to
   * be found. The optimization terminates when:
   * ||G|| < gtol max(1,||X||) where ||.|| denotes the Euclidean norm.
   */
  virtual void
  SetGradientConvergenceTolerance(double f);

  itkGetConstMacro(GradientConvergenceTolerance, double);

  /** Get the reason for termination */
  const StopConditionReturnStringType
  GetStopConditionDescription() const override;

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

  using CostFunctionAdaptorType = Superclass::CostFunctionAdaptorType;

  bool m_OptimizerInitialized{ false };

  using InternalOptimizerAutoPointer = std::unique_ptr<InternalOptimizerType>;
  InternalOptimizerAutoPointer m_VnlOptimizer{};

  mutable std::ostringstream m_StopConditionDescription{};

  bool         m_Trace{ false };
  unsigned int m_MaximumNumberOfFunctionEvaluations{ 2000 };
  double       m_GradientConvergenceTolerance{ 1e-5 };
  double       m_InfinityNormOfProjectedGradient{ 0.0 };
  double       m_CostFunctionConvergenceFactor{ 1e+7 };

  // give the helper access to member variables, to update iteration
  // counts, etc.
  friend class LBFGSOptimizerBaseHelperv4<TInternalVnlOptimizerType>;
  friend class LBFGSBOptimizerHelperv4;
};

template <typename TInternalVnlOptimizerType>
class ITK_TEMPLATE_EXPORT LBFGSOptimizerBaseHelperv4 : public TInternalVnlOptimizerType
{
public:
  using Self = LBFGSOptimizerBaseHelperv4;
  using Superclass = TInternalVnlOptimizerType;

  LBFGSOptimizerBaseHelperv4(vnl_cost_function & f, LBFGSOptimizerBasev4<TInternalVnlOptimizerType> * itkObj)
    : TInternalVnlOptimizerType(f)
    , m_ItkObj(itkObj)
  {}

protected:
  LBFGSOptimizerBasev4<TInternalVnlOptimizerType> * m_ItkObj;

  /** Handle new iteration event */
  bool
  report_iter() override;
};

} // end namespace itk

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

#endif

/** Explicit instantiations */
#ifndef ITK_TEMPLATE_EXPLICIT_LBFGSOptimizerBasev4
// Explicit instantiation is required to ensure correct dynamic_cast
// behavior across shared libraries.
//
// IMPORTANT: Since within the same compilation unit,
//            ITK_TEMPLATE_EXPLICIT_<classname> defined and undefined states
//            need to be considered. This code *MUST* be *OUTSIDE* the header
//            guards.
//
#if defined(ITKOptimizersv4_EXPORTS)
//   We are building this library
#  define ITKOptimizersv4_EXPORT_EXPLICIT ITK_FORWARD_EXPORT
#else
//   We are using this library
#  define ITKOptimizersv4_EXPORT_EXPLICIT ITKOptimizersv4_EXPORT
#endif
namespace itk
{

ITK_GCC_PRAGMA_DIAG_PUSH()
ITK_GCC_PRAGMA_DIAG(ignored "-Wattributes")

#if defined(_MSC_VER)
#  pragma warning(disable : 4661) // no suitable definition provided for explicit template instantiation request
#endif
extern template class ITKOptimizersv4_EXPORT_EXPLICIT LBFGSOptimizerBasev4<vnl_lbfgs>;
extern template class ITKOptimizersv4_EXPORT_EXPLICIT LBFGSOptimizerBasev4<vnl_lbfgsb>;

ITK_GCC_PRAGMA_DIAG_POP()

} // end namespace itk
#undef ITKOptimizersv4_EXPORT_EXPLICIT
#endif
