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

#include "itkLBFGSOptimizerBasev4.h"

extern "C"
{
  extern double
  v3p_netlib_dpmeps_();
}

namespace itk
{

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;
};


template <typename TInternalVnlOptimizerType>
bool
LBFGSOptimizerBaseHelperv4<TInternalVnlOptimizerType>::report_iter()
{
  Superclass::report_iter();

  m_ItkObj->InvokeEvent(IterationEvent());
  m_ItkObj->m_CurrentIteration = this->num_iterations_;

  // Return true to terminate the optimization loop.
  if (this->num_iterations_ >= m_ItkObj->m_NumberOfIterations)
  {
    return true;
  }
  else
  {
    return false;
  }
}

template <typename TInternalVnlOptimizerType>
LBFGSOptimizerBasev4<TInternalVnlOptimizerType>::LBFGSOptimizerBasev4()

{
  Superclass::SetNumberOfIterations(500);
}

template <typename TInternalVnlOptimizerType>
void
LBFGSOptimizerBasev4<TInternalVnlOptimizerType>::PrintSelf(std::ostream & os, Indent indent) const
{
  Superclass::PrintSelf(os, indent);
  os << indent << "Trace: ";
  if (m_Trace)
  {
    os << "On";
  }
  else
  {
    os << "Off";
  }
  os << std::endl;
  os << indent << "MaximumNumberOfFunctionEvaluations: " << m_MaximumNumberOfFunctionEvaluations << std::endl;
  os << indent << "GradientConvergenceTolerance: " << m_GradientConvergenceTolerance << std::endl;
}

template <typename TInternalVnlOptimizerType>
void
LBFGSOptimizerBasev4<TInternalVnlOptimizerType>::SetTrace(bool flag)
{
  if (flag == m_Trace)
  {
    return;
  }

  m_Trace = flag;
  if (m_OptimizerInitialized)
  {
    m_VnlOptimizer->set_trace(m_Trace);
  }

  this->Modified();
}

template <typename TInternalVnlOptimizerType>
void
LBFGSOptimizerBasev4<TInternalVnlOptimizerType>::SetMaximumNumberOfFunctionEvaluations(unsigned int n)
{
  if (n == m_MaximumNumberOfFunctionEvaluations)
  {
    return;
  }

  m_MaximumNumberOfFunctionEvaluations = n;
  if (m_OptimizerInitialized)
  {
    m_VnlOptimizer->set_max_function_evals(static_cast<int>(m_MaximumNumberOfFunctionEvaluations));
  }

  this->Modified();
}

template <typename TInternalVnlOptimizerType>
void
LBFGSOptimizerBasev4<TInternalVnlOptimizerType>::SetGradientConvergenceTolerance(double f)
{
  if (Math::ExactlyEquals(f, m_GradientConvergenceTolerance))
  {
    return;
  }

  m_GradientConvergenceTolerance = f;
  if (m_OptimizerInitialized)
  {
    m_VnlOptimizer->set_g_tolerance(m_GradientConvergenceTolerance);
  }

  this->Modified();
}

template <typename TInternalVnlOptimizerType>
void
LBFGSOptimizerBasev4<TInternalVnlOptimizerType>::SetMetric(MetricType * metric)
{
  // assign to base class
  this->m_Metric = metric;

  // assign to vnl cost-function adaptor
  const unsigned int numberOfParameters = metric->GetNumberOfParameters();

  auto * adaptor = new CostFunctionAdaptorType(numberOfParameters);

  adaptor->SetCostFunction(metric);

  this->SetCostFunctionAdaptor(adaptor);

  m_VnlOptimizer.reset(new InternalOptimizerType(*adaptor, this));
}

template <typename TInternalVnlOptimizerType>
void
LBFGSOptimizerBasev4<TInternalVnlOptimizerType>::StartOptimization(bool /* doOnlyInitialization */)
{
  // Check for a local-support transform.
  // These aren't currently supported, see main class documentation.
  if (this->GetMetric()->HasLocalSupport())
  {
    itkExceptionMacro("The assigned transform has local-support. This is not supported for this optimizer. See the "
                      "optimizer documentation.");
  }

  // Perform some verification, check scales,
  // pass settings to cost-function adaptor.
  Superclass::StartOptimization();
}

template <typename TInternalVnlOptimizerType>
typename LBFGSOptimizerBasev4<TInternalVnlOptimizerType>::InternalOptimizerType *
LBFGSOptimizerBasev4<TInternalVnlOptimizerType>::GetOptimizer()
{
  return m_VnlOptimizer.get();
}

template <typename TInternalVnlOptimizerType>
const std::string
LBFGSOptimizerBasev4<TInternalVnlOptimizerType>::GetStopConditionDescription() const
{
  m_StopConditionDescription.str("");
  m_StopConditionDescription << this->GetNameOfClass() << ": ";
  if (m_VnlOptimizer)
  {
    switch (m_VnlOptimizer->get_failure_code())
    {
      case vnl_nonlinear_minimizer::ERROR_FAILURE:
        m_StopConditionDescription << "Failure";
        break;
      case vnl_nonlinear_minimizer::ERROR_DODGY_INPUT:
        m_StopConditionDescription << "Dodgy input";
        break;
      case vnl_nonlinear_minimizer::CONVERGED_FTOL:
        m_StopConditionDescription << "Function tolerance reached after " << m_CurrentIteration << " iterations. "
                                   << "The relative reduction of the cost function <= "
                                   << m_CostFunctionConvergenceFactor * v3p_netlib_dpmeps_()
                                   << " = CostFunctionConvergenceFactor (" << m_CostFunctionConvergenceFactor
                                   << ") * machine precision (" << v3p_netlib_dpmeps_() << ").";
        break;
      case vnl_nonlinear_minimizer::CONVERGED_XTOL:
        m_StopConditionDescription << "Solution tolerance reached";
        break;
      case vnl_nonlinear_minimizer::CONVERGED_XFTOL:
        m_StopConditionDescription << "Solution and Function tolerance both reached";
        break;
      case vnl_nonlinear_minimizer::CONVERGED_GTOL:
        m_StopConditionDescription << "Gradient tolerance reached. "
                                   << "Gradient tolerance is " << m_GradientConvergenceTolerance;
        break;
      case vnl_nonlinear_minimizer::FAILED_TOO_MANY_ITERATIONS:
        m_StopConditionDescription << "Too many function evaluations. Function evaluations  = "
                                   << m_MaximumNumberOfFunctionEvaluations;
        break;
      case vnl_nonlinear_minimizer::FAILED_FTOL_TOO_SMALL:
        m_StopConditionDescription << "Function tolerance too small";
        break;
      case vnl_nonlinear_minimizer::FAILED_XTOL_TOO_SMALL:
        m_StopConditionDescription << "Solution tolerance too small";
        break;
      case vnl_nonlinear_minimizer::FAILED_GTOL_TOO_SMALL:
        m_StopConditionDescription << "Gradient tolerance too small";
        break;
      case vnl_nonlinear_minimizer::FAILED_USER_REQUEST:
        m_StopConditionDescription << "User requested";
        break;
    }
    return m_StopConditionDescription.str();
  }
  else
  {
    return std::string("");
  }
}
} // end namespace itk

#endif
