/*=========================================================================
 *
 *  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.
 *
 *=========================================================================*/

#include "itkConjugateGradientOptimizer.h"

namespace itk
{
/**
 * Constructor
 */
ConjugateGradientOptimizer::ConjugateGradientOptimizer()
{
  m_OptimizerInitialized = false;
  m_VnlOptimizer = nullptr;
}

/**
 * Destructor
 */
ConjugateGradientOptimizer::~ConjugateGradientOptimizer() = default;

/**
 * Get the Optimizer
 */
vnl_conjugate_gradient *
ConjugateGradientOptimizer::GetOptimizer()
{
  return m_VnlOptimizer.get();
}

/**
 * Connect a Cost Function
 */
void
ConjugateGradientOptimizer::SetCostFunction(SingleValuedCostFunction * costFunction)
{
  const unsigned int numberOfParameters = costFunction->GetNumberOfParameters();

  auto * adaptor = new CostFunctionAdaptorType(numberOfParameters);

  adaptor->SetCostFunction(costFunction);

  this->SetCostFunctionAdaptor(adaptor);

  m_VnlOptimizer = std::make_unique<vnl_conjugate_gradient>(*adaptor);
  m_OptimizerInitialized = true;
}

/** Return Current Value */
ConjugateGradientOptimizer::MeasureType
ConjugateGradientOptimizer::GetValue() const
{
  ParametersType parameters = this->GetCurrentPosition();

  if (m_ScalesInitialized)
  {
    const ScalesType & scales = this->GetScales();
    for (unsigned int i = 0; i < parameters.size(); ++i)
    {
      parameters[i] *= scales[i];
    }
  }
  return this->GetNonConstCostFunctionAdaptor()->f(parameters);
}

/**
 * Start the optimization
 */
void
ConjugateGradientOptimizer::StartOptimization()
{
  this->InvokeEvent(StartEvent());

  if (this->GetMaximize())
  {
    this->GetNonConstCostFunctionAdaptor()->NegateCostFunctionOn();
  }

  ParametersType initialPosition = this->GetInitialPosition();

  ParametersType parameters(initialPosition);

  // If the user provides the scales then we set otherwise we don't
  // for computation speed.
  // We also scale the initial parameters up if scales are defined.
  // This compensates for later scaling them down in the cost function adaptor
  // and at the end of this function.
  if (m_ScalesInitialized)
  {
    const ScalesType & scales = this->GetScales();
    this->GetNonConstCostFunctionAdaptor()->SetScales(scales);
    for (unsigned int i = 0; i < parameters.size(); ++i)
    {
      parameters[i] *= scales[i];
    }
  }

  // vnl optimizers return the solution by reference
  // in the variable provided as initial position
  m_VnlOptimizer->minimize(parameters);

  // we scale the parameters down if scales are defined
  if (m_ScalesInitialized)
  {
    const ScalesType & invScales = this->GetInverseScales();
    for (unsigned int i = 0; i < parameters.size(); ++i)
    {
      parameters[i] *= invScales[i];
    }
  }

  this->SetCurrentPosition(parameters);

  this->InvokeEvent(EndEvent());
}

/**
 * Get the maximum number of evaluations of the function.
 * In vnl this is used instead of a maximum number of iterations
 * given that an iteration could imply several evaluations.
 */
SizeValueType
ConjugateGradientOptimizer::GetNumberOfIterations() const
{
  return m_VnlOptimizer->get_max_function_evals();
}

/**
 * Get the number of iterations in the last optimization.
 */
SizeValueType
ConjugateGradientOptimizer::GetCurrentIteration() const
{
  return m_VnlOptimizer->get_num_iterations();
}
} // end namespace itk
